diff --git a/module/bdev/nvme/Makefile b/module/bdev/nvme/Makefile index 145d5c79c8..6a02b96f6a 100644 --- a/module/bdev/nvme/Makefile +++ b/module/bdev/nvme/Makefile @@ -34,7 +34,7 @@ SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../..) include $(SPDK_ROOT_DIR)/mk/spdk.common.mk -C_SRCS = bdev_nvme.c bdev_nvme_rpc.c nvme_rpc.c common.c bdev_ocssd.c +C_SRCS = bdev_nvme.c bdev_nvme_rpc.c nvme_rpc.c common.c bdev_ocssd.c bdev_ocssd_rpc.c C_SRCS-$(CONFIG_NVME_CUSE) += bdev_nvme_cuse_rpc.c ifeq ($(OS),Linux) diff --git a/module/bdev/nvme/bdev_ocssd.c b/module/bdev/nvme/bdev_ocssd.c index d82b7cc374..4804f0c63f 100644 --- a/module/bdev/nvme/bdev_ocssd.c +++ b/module/bdev/nvme/bdev_ocssd.c @@ -42,10 +42,20 @@ #include "common.h" #include "bdev_ocssd.h" +struct ocssd_bdev { + struct nvme_bdev nvme_bdev; +}; + struct bdev_ocssd_ns { struct spdk_ocssd_geometry_data geometry; }; +static struct bdev_ocssd_ns * +bdev_ocssd_get_ns_from_nvme(struct nvme_bdev_ns *nvme_ns) +{ + return nvme_ns->type_ctx; +} + static int bdev_ocssd_library_init(void) { @@ -79,6 +89,217 @@ static struct spdk_bdev_module ocssd_if = { SPDK_BDEV_MODULE_REGISTER(ocssd, &ocssd_if); +static void +bdev_ocssd_free_bdev(struct ocssd_bdev *ocssd_bdev) +{ + if (!ocssd_bdev) { + return; + } + + free(ocssd_bdev->nvme_bdev.disk.name); + free(ocssd_bdev); +} + +static int +bdev_ocssd_destruct(void *ctx) +{ + struct ocssd_bdev *ocssd_bdev = ctx; + struct nvme_bdev *nvme_bdev = &ocssd_bdev->nvme_bdev; + + nvme_bdev_detach_bdev_from_ns(nvme_bdev); + bdev_ocssd_free_bdev(ocssd_bdev); + + return 0; +} + +static void +bdev_ocssd_submit_request(struct spdk_io_channel *ioch, struct spdk_bdev_io *bdev_io) +{ + spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED); +} + +static bool +bdev_ocssd_io_type_supported(void *ctx, enum spdk_bdev_io_type type) +{ + return false; +} + +static struct spdk_io_channel * +bdev_ocssd_get_io_channel(void *ctx) +{ + struct ocssd_bdev *ocssd_bdev = ctx; + + return spdk_get_io_channel(ocssd_bdev->nvme_bdev.nvme_bdev_ctrlr); +} + +static struct spdk_bdev_fn_table ocssdlib_fn_table = { + .destruct = bdev_ocssd_destruct, + .submit_request = bdev_ocssd_submit_request, + .io_type_supported = bdev_ocssd_io_type_supported, + .get_io_channel = bdev_ocssd_get_io_channel, +}; + +void +bdev_ocssd_create_bdev(const char *ctrlr_name, const char *bdev_name, uint32_t nsid, + bdev_ocssd_create_cb cb_fn, void *cb_arg) +{ + struct nvme_bdev_ctrlr *nvme_bdev_ctrlr; + struct nvme_bdev *nvme_bdev = NULL; + struct ocssd_bdev *ocssd_bdev = NULL; + struct spdk_nvme_ns *ns; + struct nvme_bdev_ns *nvme_ns; + struct bdev_ocssd_ns *ocssd_ns; + struct spdk_ocssd_geometry_data *geometry; + int rc = 0; + + nvme_bdev_ctrlr = nvme_bdev_ctrlr_get_by_name(ctrlr_name); + if (!nvme_bdev_ctrlr) { + SPDK_ERRLOG("Unable to find controller %s\n", ctrlr_name); + rc = -ENODEV; + goto finish; + } + + ns = spdk_nvme_ctrlr_get_ns(nvme_bdev_ctrlr->ctrlr, nsid); + if (!ns) { + SPDK_ERRLOG("Unable to retrieve namespace %"PRIu32"\n", nsid); + rc = -ENODEV; + goto finish; + } + + if (!spdk_nvme_ns_is_active(ns)) { + SPDK_ERRLOG("Namespace %"PRIu32" is inactive\n", nsid); + rc = -EACCES; + goto finish; + } + + assert(nsid <= nvme_bdev_ctrlr->num_ns); + nvme_ns = nvme_bdev_ctrlr->namespaces[nsid - 1]; + if (nvme_ns == NULL) { + SPDK_ERRLOG("Namespace %"PRIu32" is not initialized\n", nsid); + rc = -EINVAL; + goto finish; + } + + ocssd_ns = bdev_ocssd_get_ns_from_nvme(nvme_ns); + if (ocssd_ns == NULL) { + SPDK_ERRLOG("Namespace %"PRIu32" is not an OCSSD namespace\n", nsid); + rc = -EINVAL; + goto finish; + } + + if (spdk_bdev_get_by_name(bdev_name) != NULL) { + SPDK_ERRLOG("Device with provided name (%s) already exists\n", bdev_name); + rc = -EEXIST; + goto finish; + } + + /* Only allow one bdev per namespace for now */ + if (!TAILQ_EMPTY(&nvme_ns->bdevs)) { + SPDK_ERRLOG("Namespace %"PRIu32" was already claimed by bdev %s\n", + nsid, TAILQ_FIRST(&nvme_ns->bdevs)->disk.name); + rc = -EEXIST; + goto finish; + } + + ocssd_bdev = calloc(1, sizeof(*ocssd_bdev)); + if (!ocssd_bdev) { + rc = -ENOMEM; + goto finish; + } + + nvme_bdev = &ocssd_bdev->nvme_bdev; + nvme_bdev->nvme_ns = nvme_ns; + nvme_bdev->nvme_bdev_ctrlr = nvme_bdev_ctrlr; + geometry = &ocssd_ns->geometry; + + nvme_bdev->disk.name = strdup(bdev_name); + if (!nvme_bdev->disk.name) { + rc = -ENOMEM; + goto finish; + } + + nvme_bdev->disk.product_name = "Open Channel SSD"; + nvme_bdev->disk.ctxt = ocssd_bdev; + nvme_bdev->disk.fn_table = &ocssdlib_fn_table; + nvme_bdev->disk.module = &ocssd_if; + nvme_bdev->disk.blocklen = spdk_nvme_ns_get_extended_sector_size(ns); + nvme_bdev->disk.zoned = true; + nvme_bdev->disk.blockcnt = geometry->num_grp * geometry->num_pu * + geometry->num_chk * geometry->clba; + nvme_bdev->disk.zone_size = geometry->clba; + nvme_bdev->disk.max_open_zones = geometry->maxoc; + nvme_bdev->disk.optimal_open_zones = geometry->num_grp * geometry->num_pu; + nvme_bdev->disk.write_unit_size = geometry->ws_opt; + + if (geometry->maxocpu != 0 && geometry->maxocpu != geometry->maxoc) { + SPDK_WARNLOG("Maximum open chunks per PU is not zero. Reducing the maximum " + "number of open zones: %"PRIu32" -> %"PRIu32"\n", + geometry->maxoc, geometry->maxocpu); + nvme_bdev->disk.max_open_zones = geometry->maxocpu; + } + + rc = spdk_bdev_register(&nvme_bdev->disk); + if (spdk_unlikely(rc != 0)) { + SPDK_ERRLOG("Failed to register bdev %s\n", nvme_bdev->disk.name); + goto finish; + } + + nvme_bdev_attach_bdev_to_ns(nvme_ns, nvme_bdev); +finish: + if (spdk_unlikely(rc != 0)) { + bdev_ocssd_free_bdev(ocssd_bdev); + bdev_name = NULL; + } + + cb_fn(bdev_name, rc, cb_arg); +} + +struct bdev_ocssd_delete_ctx { + bdev_ocssd_delete_cb cb_fn; + void *cb_arg; +}; + +static void +bdev_ocssd_unregister_cb(void *cb_arg, int status) +{ + struct bdev_ocssd_delete_ctx *delete_ctx = cb_arg; + + delete_ctx->cb_fn(status, delete_ctx->cb_arg); + free(delete_ctx); +} + +void +bdev_ocssd_delete_bdev(const char *bdev_name, bdev_ocssd_delete_cb cb_fn, void *cb_arg) +{ + struct spdk_bdev *bdev; + struct bdev_ocssd_delete_ctx *delete_ctx; + + bdev = spdk_bdev_get_by_name(bdev_name); + if (!bdev) { + SPDK_ERRLOG("Unable to find bdev %s\n", bdev_name); + cb_fn(-ENODEV, cb_arg); + return; + } + + if (bdev->module != &ocssd_if) { + SPDK_ERRLOG("Specified bdev %s is not an OCSSD bdev\n", bdev_name); + cb_fn(-EINVAL, cb_arg); + return; + } + + delete_ctx = calloc(1, sizeof(*delete_ctx)); + if (!delete_ctx) { + SPDK_ERRLOG("Unable to allocate deletion context\n"); + cb_fn(-ENOMEM, cb_arg); + return; + } + + delete_ctx->cb_fn = cb_fn; + delete_ctx->cb_arg = cb_arg; + + spdk_bdev_unregister(bdev, bdev_ocssd_unregister_cb, delete_ctx); +} + struct bdev_ocssd_populate_ns_ctx { struct nvme_async_probe_ctx *nvme_ctx; struct nvme_bdev_ns *nvme_ns; @@ -144,6 +365,12 @@ bdev_ocssd_populate_namespace(struct nvme_bdev_ctrlr *nvme_bdev_ctrlr, void bdev_ocssd_depopulate_namespace(struct nvme_bdev_ns *ns) { + struct nvme_bdev *bdev, *tmp; + + TAILQ_FOREACH_SAFE(bdev, &ns->bdevs, tailq, tmp) { + spdk_bdev_unregister(&bdev->disk, NULL, NULL); + } + free(ns->type_ctx); ns->populated = false; ns->type_ctx = NULL; diff --git a/module/bdev/nvme/bdev_ocssd.h b/module/bdev/nvme/bdev_ocssd.h index fdfb507b8a..493701e6a9 100644 --- a/module/bdev/nvme/bdev_ocssd.h +++ b/module/bdev/nvme/bdev_ocssd.h @@ -37,6 +37,13 @@ #include "spdk/stdinc.h" #include "common.h" +typedef void (*bdev_ocssd_create_cb)(const char *bdev_name, int status, void *ctx); +typedef void (*bdev_ocssd_delete_cb)(int status, void *ctx); + +void bdev_ocssd_create_bdev(const char *ctrlr_name, const char *bdev_name, uint32_t nsid, + bdev_ocssd_create_cb cb_fn, void *cb_arg); +void bdev_ocssd_delete_bdev(const char *bdev_name, bdev_ocssd_delete_cb cb_fn, void *cb_arg); + void bdev_ocssd_populate_namespace(struct nvme_bdev_ctrlr *nvme_bdev_ctrlr, struct nvme_bdev_ns *nvme_ns, struct nvme_async_probe_ctx *ctx); diff --git a/module/bdev/nvme/bdev_ocssd_rpc.c b/module/bdev/nvme/bdev_ocssd_rpc.c new file mode 100644 index 0000000000..8271b1d3a7 --- /dev/null +++ b/module/bdev/nvme/bdev_ocssd_rpc.c @@ -0,0 +1,178 @@ +/*- + * 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/rpc.h" +#include "spdk/string.h" +#include "spdk/util.h" +#include "spdk/log.h" +#include "spdk/likely.h" +#include "bdev_ocssd.h" + +#define BDEV_OCSSD_DEFAULT_NSID 1 + +struct rpc_create_ocssd_bdev { + char *ctrlr_name; + char *bdev_name; + uint32_t nsid; +}; + +static const struct spdk_json_object_decoder rpc_create_ocssd_bdev_decoders[] = { + {"ctrlr_name", offsetof(struct rpc_create_ocssd_bdev, ctrlr_name), spdk_json_decode_string}, + {"bdev_name", offsetof(struct rpc_create_ocssd_bdev, bdev_name), spdk_json_decode_string}, + {"nsid", offsetof(struct rpc_create_ocssd_bdev, nsid), spdk_json_decode_uint32, true}, +}; + +static void +free_rpc_create_ocssd_bdev(struct rpc_create_ocssd_bdev *rpc) +{ + free(rpc->ctrlr_name); + free(rpc->bdev_name); +} + +struct rpc_bdev_ocssd_create_ctx { + struct spdk_jsonrpc_request *request; + struct rpc_create_ocssd_bdev rpc; +}; + +static void +rpc_bdev_ocssd_create_done(const char *bdev_name, int status, void *_ctx) +{ + struct rpc_bdev_ocssd_create_ctx *ctx = _ctx; + struct spdk_json_write_ctx *w; + + if (status != 0) { + spdk_jsonrpc_send_error_response(ctx->request, status, spdk_strerror(-status)); + goto out; + } + + w = spdk_jsonrpc_begin_result(ctx->request); + spdk_json_write_string(w, bdev_name); + spdk_jsonrpc_end_result(ctx->request, w); +out: + free_rpc_create_ocssd_bdev(&ctx->rpc); + free(ctx); +} + +static void +rpc_bdev_ocssd_create(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params) +{ + struct rpc_bdev_ocssd_create_ctx *ctx; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + spdk_jsonrpc_send_error_response(request, -ENOMEM, spdk_strerror(ENOMEM)); + return; + } + + ctx->rpc.nsid = BDEV_OCSSD_DEFAULT_NSID; + ctx->request = request; + + if (spdk_json_decode_object(params, rpc_create_ocssd_bdev_decoders, + SPDK_COUNTOF(rpc_create_ocssd_bdev_decoders), + &ctx->rpc)) { + spdk_jsonrpc_send_error_response(request, -EINVAL, "Failed to parse the request"); + free_rpc_create_ocssd_bdev(&ctx->rpc); + free(ctx); + return; + } + + bdev_ocssd_create_bdev(ctx->rpc.ctrlr_name, ctx->rpc.bdev_name, ctx->rpc.nsid, + rpc_bdev_ocssd_create_done, ctx); +} + +SPDK_RPC_REGISTER("bdev_ocssd_create", rpc_bdev_ocssd_create, SPDK_RPC_RUNTIME) + +struct rpc_delete_ocssd_bdev { + char *name; +}; + +static const struct spdk_json_object_decoder rpc_delete_ocssd_bdev_decoders[] = { + {"name", offsetof(struct rpc_delete_ocssd_bdev, name), spdk_json_decode_string}, +}; + +static void +free_rpc_delete_ocssd_bdev(struct rpc_delete_ocssd_bdev *rpc) +{ + free(rpc->name); +} + +struct rpc_bdev_ocssd_delete_ctx { + struct spdk_jsonrpc_request *request; + struct rpc_delete_ocssd_bdev rpc; +}; + +static void +rpc_bdev_ocssd_delete_done(int status, void *_ctx) +{ + struct rpc_bdev_ocssd_delete_ctx *ctx = _ctx; + struct spdk_json_write_ctx *w; + + if (status != 0) { + spdk_jsonrpc_send_error_response(ctx->request, status, spdk_strerror(-status)); + goto out; + } + + w = spdk_jsonrpc_begin_result(ctx->request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(ctx->request, w); +out: + free_rpc_delete_ocssd_bdev(&ctx->rpc); + free(ctx); +} + +static void +rpc_bdev_ocssd_delete(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params) +{ + struct rpc_bdev_ocssd_delete_ctx *ctx; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + spdk_jsonrpc_send_error_response(request, -ENOMEM, spdk_strerror(ENOMEM)); + return; + } + + ctx->request = request; + if (spdk_json_decode_object(params, rpc_delete_ocssd_bdev_decoders, + SPDK_COUNTOF(rpc_delete_ocssd_bdev_decoders), + &ctx->rpc)) { + spdk_jsonrpc_send_error_response(request, -EINVAL, "Failed to parse the request"); + free_rpc_delete_ocssd_bdev(&ctx->rpc); + free(ctx); + return; + } + + bdev_ocssd_delete_bdev(ctx->rpc.name, rpc_bdev_ocssd_delete_done, ctx); +} + +SPDK_RPC_REGISTER("bdev_ocssd_delete", rpc_bdev_ocssd_delete, SPDK_RPC_RUNTIME) diff --git a/scripts/rpc.py b/scripts/rpc.py index 6c8b95fb54..49632bf60e 100755 --- a/scripts/rpc.py +++ b/scripts/rpc.py @@ -2065,6 +2065,30 @@ Format: 'user:u1 secret:s1 muser:mu1 msecret:ms1,user:u2 secret:s2 muser:mu2 mse p.add_argument('name', help='Virtio device name. E.g. VirtioUser0') p.set_defaults(func=bdev_virtio_detach_controller) + # OCSSD + def bdev_ocssd_create(args): + nsid = int(args.nsid) if args.nsid is not None else None + print_json(rpc.bdev.bdev_ocssd_create(args.client, + ctrlr_name=args.ctrlr_name, + bdev_name=args.name, + nsid=nsid)) + + p = subparsers.add_parser('bdev_ocssd_create', + help='Creates zoned bdev on specified Open Channel controller') + p.add_argument('-c', '--ctrlr_name', help='Name of the OC NVMe controller', required=True) + p.add_argument('-b', '--name', help='Name of the bdev to create', required=True) + p.add_argument('-n', '--nsid', help='Namespace ID', required=False) + p.set_defaults(func=bdev_ocssd_create) + + def bdev_ocssd_delete(args): + print_json(rpc.bdev.bdev_ocssd_delete(args.client, + name=args.name)) + + p = subparsers.add_parser('bdev_ocssd_delete', + help='Deletes Open Channel bdev') + p.add_argument('name', help='Name of the Open Channel bdev') + p.set_defaults(func=bdev_ocssd_delete) + # ioat def ioat_scan_copy_engine(args): pci_whitelist = [] diff --git a/scripts/rpc/bdev.py b/scripts/rpc/bdev.py index 7743d03ac6..3377d968a1 100644 --- a/scripts/rpc/bdev.py +++ b/scripts/rpc/bdev.py @@ -914,6 +914,34 @@ def bdev_ftl_delete(client, name): return client.call('bdev_ftl_delete', params) +def bdev_ocssd_create(client, ctrlr_name, bdev_name, nsid=None): + """Creates Open Channel zoned bdev on specified Open Channel controller + + Args: + ctrlr_name: name of the OC NVMe controller + bdev_name: name of the bdev to create + nsid: namespace ID + """ + params = {'ctrlr_name': ctrlr_name, + 'bdev_name': bdev_name} + + if nsid is not None: + params['nsid'] = nsid + + return client.call('bdev_ocssd_create', params) + + +def bdev_ocssd_delete(client, name): + """Deletes Open Channel bdev + + Args: + name: name of the bdev + """ + params = {'name': name} + + return client.call('bdev_ocssd_delete', params) + + @deprecated_alias('get_bdevs') def bdev_get_bdevs(client, name=None): """Get information about block devices. diff --git a/test/unit/lib/bdev/Makefile b/test/unit/lib/bdev/Makefile index 303efaa50d..4c6eadfb07 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 vbdev_zone_block.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 bdev_ocssd.c DIRS-$(CONFIG_CRYPTO) += crypto.c diff --git a/test/unit/lib/bdev/bdev_ocssd.c/.gitignore b/test/unit/lib/bdev/bdev_ocssd.c/.gitignore new file mode 100644 index 0000000000..906b8067c8 --- /dev/null +++ b/test/unit/lib/bdev/bdev_ocssd.c/.gitignore @@ -0,0 +1 @@ +bdev_ocssd_ut diff --git a/test/unit/lib/bdev/bdev_ocssd.c/Makefile b/test/unit/lib/bdev/bdev_ocssd.c/Makefile new file mode 100644 index 0000000000..7106d46fc6 --- /dev/null +++ b/test/unit/lib/bdev/bdev_ocssd.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 = bdev_ocssd_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/test/unit/lib/bdev/bdev_ocssd.c/bdev_ocssd_ut.c b/test/unit/lib/bdev/bdev_ocssd.c/bdev_ocssd_ut.c new file mode 100644 index 0000000000..a8f7a57f89 --- /dev/null +++ b/test/unit/lib/bdev/bdev_ocssd.c/bdev_ocssd_ut.c @@ -0,0 +1,568 @@ +/*- + * 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/nvme_ocssd_spec.h" +#include "spdk/thread.h" +#include "spdk/bdev_module.h" +#include "spdk/util.h" +#include "spdk_internal/mock.h" + +#include "bdev/nvme/bdev_ocssd.c" +#include "bdev/nvme/common.c" +#include "common/lib/test_env.c" + +DEFINE_STUB_V(spdk_bdev_module_list_add, (struct spdk_bdev_module *bdev_module)); +DEFINE_STUB(spdk_nvme_ctrlr_is_ocssd_ns, bool, (struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid), + true); +DEFINE_STUB(spdk_nvme_ns_get_extended_sector_size, uint32_t, (struct spdk_nvme_ns *ns), 4096); +DEFINE_STUB(spdk_nvme_ns_is_active, bool, (struct spdk_nvme_ns *ns), true); +DEFINE_STUB_V(spdk_opal_close, (struct spdk_opal_dev *dev)); +DEFINE_STUB(spdk_opal_revert_poll, int, (struct spdk_opal_dev *dev), 0); + +struct nvme_request { + spdk_nvme_cmd_cb cb_fn; + void *cb_arg; + TAILQ_ENTRY(nvme_request) tailq; +}; + +struct spdk_nvme_qpair { + TAILQ_HEAD(, nvme_request) requests; +}; + +struct spdk_nvme_ns { + uint32_t nsid; +}; + +struct spdk_nvme_ctrlr { + struct spdk_nvme_transport_id trid; + struct spdk_ocssd_geometry_data geometry; + struct spdk_nvme_qpair *admin_qpair; + struct spdk_nvme_ns *ns; + uint32_t ns_count; + + LIST_ENTRY(spdk_nvme_ctrlr) list; +}; + +static LIST_HEAD(, spdk_nvme_ctrlr) g_ctrlr_list = LIST_HEAD_INITIALIZER(g_ctrlr_list); +static TAILQ_HEAD(, spdk_bdev) g_bdev_list = TAILQ_HEAD_INITIALIZER(g_bdev_list); +static struct spdk_thread *g_thread; + +static struct spdk_nvme_ctrlr * +find_controller(const struct spdk_nvme_transport_id *trid) +{ + struct spdk_nvme_ctrlr *ctrlr; + + LIST_FOREACH(ctrlr, &g_ctrlr_list, list) { + if (!spdk_nvme_transport_id_compare(trid, &ctrlr->trid)) { + return ctrlr; + } + } + + return NULL; +} + +static void +free_controller(struct spdk_nvme_ctrlr *ctrlr) +{ + CU_ASSERT(!nvme_bdev_ctrlr_get(&ctrlr->trid)); + LIST_REMOVE(ctrlr, list); + spdk_nvme_ctrlr_free_io_qpair(ctrlr->admin_qpair); + free(ctrlr->ns); + free(ctrlr); +} + +static struct spdk_nvme_ctrlr * +create_controller(const struct spdk_nvme_transport_id *trid, uint32_t ns_count, + const struct spdk_ocssd_geometry_data *geo) +{ + struct spdk_nvme_ctrlr *ctrlr; + uint32_t nsid; + + SPDK_CU_ASSERT_FATAL(!find_controller(trid)); + + ctrlr = calloc(1, sizeof(*ctrlr)); + SPDK_CU_ASSERT_FATAL(ctrlr != NULL); + + ctrlr->ns = calloc(ns_count, sizeof(*ctrlr->ns)); + SPDK_CU_ASSERT_FATAL(ctrlr->ns != NULL); + + for (nsid = 0; nsid < ns_count; ++nsid) { + ctrlr->ns[nsid].nsid = nsid + 1; + } + + ctrlr->geometry = *geo; + ctrlr->trid = *trid; + ctrlr->ns_count = ns_count; + ctrlr->admin_qpair = spdk_nvme_ctrlr_alloc_io_qpair(ctrlr, NULL, 0); + SPDK_CU_ASSERT_FATAL(ctrlr->admin_qpair != NULL); + + LIST_INSERT_HEAD(&g_ctrlr_list, ctrlr, list); + + return ctrlr; +} + +static int +io_channel_create_cb(void *io_device, void *ctx_buf) +{ + return 0; +} + +static void +io_channel_destroy_cb(void *io_device, void *ctx_buf) +{} + +void +nvme_ctrlr_populate_namespace_done(struct nvme_async_probe_ctx *ctx, + struct nvme_bdev_ns *ns, int rc) +{ + CU_ASSERT_EQUAL(rc, 0); +} + +static struct nvme_bdev_ctrlr * +create_nvme_bdev_controller(const struct spdk_nvme_transport_id *trid, const char *name) +{ + struct spdk_nvme_ctrlr *ctrlr; + struct nvme_bdev_ctrlr *nvme_bdev_ctrlr; + uint32_t nsid; + + ctrlr = find_controller(trid); + + SPDK_CU_ASSERT_FATAL(ctrlr != NULL); + SPDK_CU_ASSERT_FATAL(!nvme_bdev_ctrlr_get(trid)); + + nvme_bdev_ctrlr = calloc(1, sizeof(*nvme_bdev_ctrlr)); + SPDK_CU_ASSERT_FATAL(nvme_bdev_ctrlr != NULL); + + nvme_bdev_ctrlr->namespaces = calloc(ctrlr->ns_count, sizeof(struct nvme_bdev_ns *)); + SPDK_CU_ASSERT_FATAL(nvme_bdev_ctrlr->namespaces != NULL); + + nvme_bdev_ctrlr->ctrlr = ctrlr; + nvme_bdev_ctrlr->num_ns = ctrlr->ns_count; + nvme_bdev_ctrlr->ref = 0; + nvme_bdev_ctrlr->trid = *trid; + nvme_bdev_ctrlr->name = strdup(name); + + for (nsid = 0; nsid < ctrlr->ns_count; ++nsid) { + nvme_bdev_ctrlr->namespaces[nsid] = calloc(1, sizeof(struct nvme_bdev_ns)); + SPDK_CU_ASSERT_FATAL(nvme_bdev_ctrlr->namespaces[nsid] != NULL); + + nvme_bdev_ctrlr->namespaces[nsid]->id = nsid + 1; + nvme_bdev_ctrlr->namespaces[nsid]->ctrlr = nvme_bdev_ctrlr; + nvme_bdev_ctrlr->namespaces[nsid]->type = NVME_BDEV_NS_OCSSD; + TAILQ_INIT(&nvme_bdev_ctrlr->namespaces[nsid]->bdevs); + + bdev_ocssd_populate_namespace(nvme_bdev_ctrlr, nvme_bdev_ctrlr->namespaces[nsid], NULL); + } + + while (spdk_thread_poll(g_thread, 0, 0) > 0) {} + + spdk_io_device_register(nvme_bdev_ctrlr, io_channel_create_cb, + io_channel_destroy_cb, 0, name); + + TAILQ_INSERT_TAIL(&g_nvme_bdev_ctrlrs, nvme_bdev_ctrlr, tailq); + + return nvme_bdev_ctrlr; +} + +static struct nvme_request * +alloc_request(spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + struct nvme_request *ctx; + + ctx = calloc(1, sizeof(*ctx)); + SPDK_CU_ASSERT_FATAL(ctx != NULL); + + ctx->cb_fn = cb_fn; + ctx->cb_arg = cb_arg; + + return ctx; +} + +uint32_t +spdk_nvme_ctrlr_get_num_ns(struct spdk_nvme_ctrlr *ctrlr) +{ + return ctrlr->ns_count; +} + +struct spdk_nvme_ns * +spdk_nvme_ctrlr_get_ns(struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid) +{ + if (nsid == 0 || nsid > ctrlr->ns_count) { + return NULL; + } + + return &ctrlr->ns[nsid - 1]; +} + +struct spdk_nvme_ctrlr * +spdk_nvme_connect(const struct spdk_nvme_transport_id *trid, + const struct spdk_nvme_ctrlr_opts *opts, + size_t opts_size) +{ + return find_controller(trid); +} + +int +spdk_nvme_detach(struct spdk_nvme_ctrlr *ctrlr) +{ + return 0; +} + +struct spdk_bdev * +spdk_bdev_get_by_name(const char *bdev_name) +{ + struct spdk_bdev *bdev; + + SPDK_CU_ASSERT_FATAL(bdev_name != NULL); + + TAILQ_FOREACH(bdev, &g_bdev_list, internal.link) { + if (!strcmp(bdev->name, bdev_name)) { + return bdev; + } + } + + return NULL; +} + +const char * +spdk_bdev_get_name(const struct spdk_bdev *bdev) +{ + return bdev->name; +} + +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) +{ + int rc; + + CU_ASSERT_EQUAL(spdk_bdev_get_by_name(bdev->name), bdev); + TAILQ_REMOVE(&g_bdev_list, bdev, internal.link); + + rc = bdev->fn_table->destruct(bdev->ctxt); + if (rc <= 0 && cb_fn != NULL) { + cb_fn(cb_arg, 0); + } +} + +int +spdk_nvme_ocssd_ctrlr_cmd_geometry(struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid, + void *payload, uint32_t payload_size, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + struct spdk_nvme_cpl cpl = {}; + + CU_ASSERT_EQUAL(payload_size, sizeof(ctrlr->geometry)); + memcpy(payload, &ctrlr->geometry, sizeof(ctrlr->geometry)); + + cb_fn(cb_arg, &cpl); + + return 0; +} + +int +spdk_nvme_transport_id_compare(const struct spdk_nvme_transport_id *trid1, + const struct spdk_nvme_transport_id *trid2) +{ + return memcmp(trid1, trid2, sizeof(*trid1)); +} + +void +spdk_bdev_io_get_buf(struct spdk_bdev_io *bdev_io, spdk_bdev_io_get_buf_cb cb, uint64_t len) +{ +} + +void +spdk_bdev_io_complete(struct spdk_bdev_io *bdev_io, enum spdk_bdev_io_status status) +{ +} + +int32_t +spdk_nvme_ctrlr_process_admin_completions(struct spdk_nvme_ctrlr *ctrlr) +{ + return spdk_nvme_qpair_process_completions(ctrlr->admin_qpair, 0); +} + +struct spdk_nvme_qpair * +spdk_nvme_ctrlr_alloc_io_qpair(struct spdk_nvme_ctrlr *ctrlr, + const struct spdk_nvme_io_qpair_opts *opts, + size_t opts_size) +{ + struct spdk_nvme_qpair *qpair; + + qpair = calloc(1, sizeof(*qpair)); + SPDK_CU_ASSERT_FATAL(qpair != NULL); + + TAILQ_INIT(&qpair->requests); + return qpair; +} + +int +spdk_nvme_ctrlr_free_io_qpair(struct spdk_nvme_qpair *qpair) +{ + CU_ASSERT(TAILQ_EMPTY(&qpair->requests)); + free(qpair); + + return 0; +} + +int32_t +spdk_nvme_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions) +{ + struct nvme_request *req; + struct spdk_nvme_cpl cpl = {}; + int32_t num_requests = 0; + + while ((req = TAILQ_FIRST(&qpair->requests))) { + TAILQ_REMOVE(&qpair->requests, req, tailq); + + req->cb_fn(req->cb_arg, &cpl); + free(req); + + num_requests++; + } + + return num_requests; +} + +int +spdk_nvme_ns_cmd_readv_with_md(struct spdk_nvme_ns *ns, struct spdk_nvme_qpair *qpair, + uint64_t lba, uint32_t lba_count, + spdk_nvme_cmd_cb cb_fn, void *cb_arg, uint32_t io_flags, + spdk_nvme_req_reset_sgl_cb reset_sgl_fn, + spdk_nvme_req_next_sge_cb next_sge_fn, void *metadata, + uint16_t apptag_mask, uint16_t apptag) +{ + struct nvme_request *req; + + req = alloc_request(cb_fn, cb_arg); + TAILQ_INSERT_TAIL(&qpair->requests, req, tailq); + + return 0; +} + +static void +create_bdev_cb(const char *bdev_name, int status, void *ctx) +{ + *(int *)ctx = status; +} + +static int +create_bdev(const char *ctrlr_name, const char *bdev_name, uint32_t nsid) +{ + int status; + + bdev_ocssd_create_bdev(ctrlr_name, bdev_name, nsid, create_bdev_cb, &status); + + return status; +} + +static void +delete_nvme_bdev_controller(struct nvme_bdev_ctrlr *nvme_bdev_ctrlr) +{ + struct nvme_bdev *nvme_bdev, *tmp; + struct nvme_bdev_ns *nvme_ns; + bool empty = true; + uint32_t nsid; + + nvme_bdev_ctrlr->destruct = true; + + for (nsid = 0; nsid < nvme_bdev_ctrlr->num_ns; ++nsid) { + nvme_ns = nvme_bdev_ctrlr->namespaces[nsid]; + + if (!TAILQ_EMPTY(&nvme_ns->bdevs)) { + TAILQ_FOREACH_SAFE(nvme_bdev, &nvme_ns->bdevs, tailq, tmp) { + spdk_bdev_unregister(&nvme_bdev->disk, NULL, NULL); + } + + empty = false; + } + + bdev_ocssd_depopulate_namespace(nvme_bdev_ctrlr->namespaces[nsid]); + } + + if (empty) { + nvme_bdev_ctrlr_destruct(nvme_bdev_ctrlr); + } + + while (spdk_thread_poll(g_thread, 0, 0) > 0) {} + + CU_ASSERT(TAILQ_EMPTY(&g_nvme_bdev_ctrlrs)); +} + +static void +test_create_controller(void) +{ + struct spdk_nvme_ctrlr *ctrlr; + struct nvme_bdev_ctrlr *nvme_bdev_ctrlr; + struct spdk_nvme_transport_id trid = { .traddr = "00:00:00" }; + struct spdk_ocssd_geometry_data geometry = {}; + struct spdk_bdev *bdev; + const char *controller_name = "nvme0"; + const size_t ns_count = 16; + char namebuf[128]; + uint32_t nsid; + int rc; + + ctrlr = create_controller(&trid, ns_count, &geometry); + nvme_bdev_ctrlr = create_nvme_bdev_controller(&trid, controller_name); + + for (nsid = 1; nsid <= ns_count; ++nsid) { + snprintf(namebuf, sizeof(namebuf), "%sn%"PRIu32, controller_name, nsid); + rc = create_bdev(controller_name, namebuf, nsid); + CU_ASSERT_EQUAL(rc, 0); + + bdev = spdk_bdev_get_by_name(namebuf); + SPDK_CU_ASSERT_FATAL(bdev != NULL); + CU_ASSERT_TRUE(bdev->zoned); + } + + delete_nvme_bdev_controller(nvme_bdev_ctrlr); + + /* Verify that after deletion the bdevs can still be created */ + nvme_bdev_ctrlr = create_nvme_bdev_controller(&trid, controller_name); + + for (nsid = 1; nsid <= ns_count; ++nsid) { + snprintf(namebuf, sizeof(namebuf), "%sn%"PRIu32, controller_name, nsid); + rc = create_bdev(controller_name, namebuf, nsid); + CU_ASSERT_EQUAL(rc, 0); + + bdev = spdk_bdev_get_by_name(namebuf); + SPDK_CU_ASSERT_FATAL(bdev != NULL); + CU_ASSERT_TRUE(bdev->zoned); + } + + delete_nvme_bdev_controller(nvme_bdev_ctrlr); + + nvme_bdev_ctrlr = create_nvme_bdev_controller(&trid, controller_name); + + /* Verify it's not possible to create a bdev on non-existent namespace */ + rc = create_bdev(controller_name, "invalid", ns_count + 1); + CU_ASSERT_EQUAL(rc, -ENODEV); + + delete_nvme_bdev_controller(nvme_bdev_ctrlr); + + free_controller(ctrlr); +} + +static void +test_device_geometry(void) +{ + struct spdk_nvme_ctrlr *ctrlr; + struct nvme_bdev_ctrlr *nvme_bdev_ctrlr; + struct spdk_nvme_transport_id trid = { .traddr = "00:00:00" }; + const char *controller_name = "nvme0"; + const char *bdev_name = "nvme0n1"; + struct spdk_ocssd_geometry_data geometry; + struct spdk_bdev *bdev; + int rc; + + geometry = (struct spdk_ocssd_geometry_data) { + .clba = 512, + .num_chk = 64, + .num_pu = 8, + .num_grp = 4, + .maxoc = 69, + .maxocpu = 68, + .ws_opt = 86, + }; + + ctrlr = create_controller(&trid, 1, &geometry); + nvme_bdev_ctrlr = create_nvme_bdev_controller(&trid, controller_name); + + rc = create_bdev(controller_name, bdev_name, 1); + CU_ASSERT_EQUAL(rc, 0); + + bdev = spdk_bdev_get_by_name(bdev_name); + CU_ASSERT_EQUAL(bdev->blockcnt, geometry.clba * + geometry.num_chk * + geometry.num_pu * + geometry.num_grp); + CU_ASSERT_EQUAL(bdev->zone_size, geometry.clba); + CU_ASSERT_EQUAL(bdev->optimal_open_zones, geometry.num_pu * geometry.num_grp); + CU_ASSERT_EQUAL(bdev->max_open_zones, geometry.maxocpu); + CU_ASSERT_EQUAL(bdev->write_unit_size, geometry.ws_opt); + + delete_nvme_bdev_controller(nvme_bdev_ctrlr); + + free_controller(ctrlr); +} + +int +main(int argc, const char **argv) +{ + CU_pSuite suite = NULL; + unsigned int num_failures; + + if (CU_initialize_registry() != CUE_SUCCESS) { + return CU_get_error(); + } + + suite = CU_add_suite("ocssd", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_create_controller", test_create_controller) == NULL || + CU_add_test(suite, "test_device_geometry", test_device_geometry) == 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 cc7b702568..3af009a7ae 100755 --- a/test/unit/unittest.sh +++ b/test/unit/unittest.sh @@ -178,6 +178,8 @@ if [ -e $testdir/lib/nvmf/fc_ls.c/fc_ls_ut ]; then $valgrind $testdir/lib/nvmf/fc_ls.c/fc_ls_ut fi +$valgrind $testdir/lib/bdev/bdev_ocssd.c/bdev_ocssd_ut + # local unit test coverage if [ "$cov_avail" = "yes" ]; then $LCOV -q -d . -c -t "$(hostname)" -o $UT_COVERAGE/ut_cov_test.info