diff options
Diffstat (limited to 'src/spdk/test')
456 files changed, 76741 insertions, 0 deletions
diff --git a/src/spdk/test/Makefile b/src/spdk/test/Makefile new file mode 100644 index 00000000..9fcd0801 --- /dev/null +++ b/src/spdk/test/Makefile @@ -0,0 +1,47 @@ +# +# 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)/..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +# These directories contain tests. +TESTDIRS = app bdev blobfs cpp_headers env event nvme unit rpc_client + +DIRS-y = $(TESTDIRS) + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/app/Makefile b/src/spdk/test/app/Makefile new file mode 100644 index 00000000..e2ba6d0f --- /dev/null +++ b/src/spdk/test/app/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y += bdev_svc histogram_perf jsoncat stub + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/app/bdev_svc/.gitignore b/src/spdk/test/app/bdev_svc/.gitignore new file mode 100644 index 00000000..77ddb987 --- /dev/null +++ b/src/spdk/test/app/bdev_svc/.gitignore @@ -0,0 +1 @@ +bdev_svc diff --git a/src/spdk/test/app/bdev_svc/Makefile b/src/spdk/test/app/bdev_svc/Makefile new file mode 100644 index 00000000..43ec095a --- /dev/null +++ b/src/spdk/test/app/bdev_svc/Makefile @@ -0,0 +1,65 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = bdev_svc + +C_SRCS := bdev_svc.c + +SPDK_LIB_LIST = event_bdev event_copy +SPDK_LIB_LIST += nvmf event log trace conf thread util bdev copy rpc jsonrpc json +SPDK_LIB_LIST += app_rpc log_rpc bdev_rpc + +ifeq ($(OS),Linux) +SPDK_LIB_LIST += event_nbd nbd +endif + +LIBS += $(BLOCKDEV_MODULES_LINKER_ARGS) \ + $(COPY_MODULES_LINKER_ARGS) \ + $(SOCK_MODULES_LINKER_ARGS) \ + $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) + +all : $(APP) + @: + +$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(SPDK_WHOLE_LIBS) $(COPY_MODULES_FILES) $(BLOCKDEV_MODULES_FILES) $(SOCK_MODULES_FILES) $(LINKER_MODULES) $(ENV_LIBS) + $(LINK_C) + +clean : + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/app/bdev_svc/bdev_svc.c b/src/spdk/test/app/bdev_svc/bdev_svc.c new file mode 100644 index 00000000..2db96d6b --- /dev/null +++ b/src/spdk/test/app/bdev_svc/bdev_svc.c @@ -0,0 +1,112 @@ +/*- + * 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/env.h" +#include "spdk/event.h" + +static char g_path[256]; +static bool g_unaffinitize_thread = false; + +static void +bdev_svc_usage(void) +{ +} + +static void +bdev_svc_parse_arg(int ch, char *arg) +{ +} + +static void +bdev_svc_start(void *arg1, void *arg2) +{ + int fd; + int shm_id = (intptr_t)arg1; + + if (g_unaffinitize_thread) { + spdk_unaffinitize_thread(); + } + + snprintf(g_path, sizeof(g_path), "/var/run/spdk_bdev%d", shm_id); + fd = open(g_path, O_CREAT | O_EXCL | O_RDWR, S_IFREG); + if (fd < 0) { + fprintf(stderr, "could not create sentinel file %s\n", g_path); + exit(1); + } + close(fd); +} + +static void +bdev_svc_shutdown(void) +{ + unlink(g_path); + spdk_app_stop(0); +} + +int +main(int argc, char **argv) +{ + int rc; + struct spdk_app_opts opts = {}; + + /* default value in opts structure */ + spdk_app_opts_init(&opts); + + opts.name = "bdev_svc"; + opts.shutdown_cb = bdev_svc_shutdown; + opts.max_delay_us = 1000 * 1000; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "", NULL, + bdev_svc_parse_arg, bdev_svc_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + exit(rc); + } + + /* User did not specify a reactor mask. Test scripts may do this when using + * bdev_svc as a primary process to speed up nvme test programs by running + * them as secondary processes. In that case, we will unaffinitize the thread + * in the bdev_svc_start routine, which will allow the scheduler to move this + * thread so it doesn't conflict with pinned threads in the secondary processes. + */ + if (opts.reactor_mask == NULL) { + g_unaffinitize_thread = true; + } + + rc = spdk_app_start(&opts, bdev_svc_start, (void *)(intptr_t)opts.shm_id, NULL); + + spdk_app_fini(); + + return rc; +} diff --git a/src/spdk/test/app/histogram_perf/.gitignore b/src/spdk/test/app/histogram_perf/.gitignore new file mode 100644 index 00000000..c77b0531 --- /dev/null +++ b/src/spdk/test/app/histogram_perf/.gitignore @@ -0,0 +1 @@ +histogram_perf diff --git a/src/spdk/test/app/histogram_perf/Makefile b/src/spdk/test/app/histogram_perf/Makefile new file mode 100644 index 00000000..7c6ecd89 --- /dev/null +++ b/src/spdk/test/app/histogram_perf/Makefile @@ -0,0 +1,55 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +APP = histogram_perf + +C_SRCS = histogram_perf.c + +SPDK_LIB_LIST = thread util log + +LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) + +all: $(APP) + @: + +$(APP): $(OBJS) $(SPDK_LIB_FILES) + $(LINK_C) + +clean: + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/app/histogram_perf/histogram_perf.c b/src/spdk/test/app/histogram_perf/histogram_perf.c new file mode 100644 index 00000000..5d9de527 --- /dev/null +++ b/src/spdk/test/app/histogram_perf/histogram_perf.c @@ -0,0 +1,102 @@ +/*- + * 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/env.h" +#include "spdk/util.h" +#include "spdk/histogram_data.h" + +/* + * This applications is a simple test app used to test the performance of + * tallying datapoints with struct spdk_histogram_data. It can be used + * to measure the effect of changes to the spdk_histogram_data implementation. + * + * There are no command line parameters currently - it just tallies + * datapoints for 10 seconds in a default-sized histogram structure and + * then prints out the number of tallies performed. + */ + +static void +usage(const char *prog) +{ + printf("usage: %s\n", prog); + printf("Options:\n"); +} + +int +main(int argc, char **argv) +{ + struct spdk_histogram_data *h; + struct spdk_env_opts opts; + uint64_t tsc[128], t, end_tsc, count; + uint32_t i; + int ch; + int rc = 0; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage(argv[0]); + return 1; + } + } + + spdk_env_opts_init(&opts); + if (spdk_env_init(&opts)) { + printf("Err: Unable to initialize SPDK env\n"); + return 1; + } + + for (i = 0; i < SPDK_COUNTOF(tsc); i++) { + tsc[i] = spdk_get_ticks(); + } + + end_tsc = spdk_get_ticks() + (10 * spdk_get_ticks_hz()); + count = 0; + h = spdk_histogram_data_alloc(); + + while (true) { + t = spdk_get_ticks(); + spdk_histogram_data_tally(h, t - tsc[count % 128]); + count++; + if (t > end_tsc) { + break; + } + } + + printf("count = %ju\n", count); + spdk_histogram_data_free(h); + + return rc; +} diff --git a/src/spdk/test/app/jsoncat/.gitignore b/src/spdk/test/app/jsoncat/.gitignore new file mode 100644 index 00000000..3e6db4f0 --- /dev/null +++ b/src/spdk/test/app/jsoncat/.gitignore @@ -0,0 +1 @@ +jsoncat diff --git a/src/spdk/test/app/jsoncat/Makefile b/src/spdk/test/app/jsoncat/Makefile new file mode 100644 index 00000000..2bbba95b --- /dev/null +++ b/src/spdk/test/app/jsoncat/Makefile @@ -0,0 +1,55 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +APP = jsoncat + +C_SRCS = jsoncat.c + +SPDK_LIB_LIST = json thread util log + +LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) + +all: $(APP) + @: + +$(APP): $(OBJS) $(SPDK_LIB_FILES) + $(LINK_C) + +clean: + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/app/jsoncat/jsoncat.c b/src/spdk/test/app/jsoncat/jsoncat.c new file mode 100644 index 00000000..9984e32b --- /dev/null +++ b/src/spdk/test/app/jsoncat/jsoncat.c @@ -0,0 +1,229 @@ +/*- + * 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. + */ + +/* Simple JSON "cat" utility */ + +#include "spdk/stdinc.h" + +#include "spdk/json.h" + +static void +usage(const char *prog) +{ + printf("usage: %s [-c] [-f] file.json\n", prog); + printf("Options:\n"); + printf("-c\tallow comments in input (non-standard)\n"); + printf("-f\tformatted output (default: compact output)\n"); +} + +static void +print_json_error(FILE *pf, int rc, const char *filename) +{ + switch (rc) { + case SPDK_JSON_PARSE_INVALID: + fprintf(pf, "%s: invalid JSON\n", filename); + break; + case SPDK_JSON_PARSE_INCOMPLETE: + fprintf(pf, "%s: incomplete JSON\n", filename); + break; + case SPDK_JSON_PARSE_MAX_DEPTH_EXCEEDED: + fprintf(pf, "%s: maximum nesting depth exceeded\n", filename); + break; + default: + fprintf(pf, "%s: unknown JSON parse error\n", filename); + break; + } +} + +static int +json_write_cb(void *cb_ctx, const void *data, size_t size) +{ + FILE *f = cb_ctx; + size_t rc; + + rc = fwrite(data, 1, size, f); + return rc == size ? 0 : -1; +} + +static void * +read_file(FILE *f, size_t *psize) +{ + void *buf, *newbuf; + size_t cur_size, buf_size, rc; + + buf = NULL; + cur_size = 0; + buf_size = 128 * 1024; + + while (buf_size <= 1024 * 1024 * 1024) { + newbuf = realloc(buf, buf_size); + if (newbuf == NULL) { + free(buf); + return NULL; + } + buf = newbuf; + + rc = fread(buf + cur_size, 1, buf_size - cur_size, f); + cur_size += rc; + + if (feof(f)) { + *psize = cur_size; + return buf; + } + + if (ferror(f)) { + free(buf); + return NULL; + } + + buf_size *= 2; + } + + free(buf); + return NULL; +} + +static int +process_file(const char *filename, FILE *f, uint32_t parse_flags, uint32_t write_flags) +{ + size_t size; + void *buf, *end; + ssize_t rc; + struct spdk_json_val *values; + size_t num_values; + struct spdk_json_write_ctx *w; + + buf = read_file(f, &size); + if (buf == NULL) { + fprintf(stderr, "%s: file read error\n", filename); + return 1; + } + + rc = spdk_json_parse(buf, size, NULL, 0, NULL, parse_flags); + if (rc <= 0) { + print_json_error(stderr, rc, filename); + free(buf); + return 1; + } + + num_values = (size_t)rc; + values = calloc(num_values, sizeof(*values)); + if (values == NULL) { + perror("values calloc"); + free(buf); + return 1; + } + + rc = spdk_json_parse(buf, size, values, num_values, &end, + parse_flags | SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE); + if (rc <= 0) { + print_json_error(stderr, rc, filename); + free(values); + free(buf); + return 1; + } + + w = spdk_json_write_begin(json_write_cb, stdout, write_flags); + if (w == NULL) { + fprintf(stderr, "json_write_begin failed\n"); + free(values); + free(buf); + return 1; + } + + spdk_json_write_val(w, values); + spdk_json_write_end(w); + printf("\n"); + + if (end != buf + size) { + fprintf(stderr, "%s: garbage at end of file\n", filename); + free(values); + free(buf); + return 1; + } + + free(values); + free(buf); + return 0; +} + +int +main(int argc, char **argv) +{ + FILE *f; + int ch; + int rc; + uint32_t parse_flags = 0, write_flags = 0; + const char *filename; + + while ((ch = getopt(argc, argv, "cf")) != -1) { + switch (ch) { + case 'c': + parse_flags |= SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS; + break; + case 'f': + write_flags |= SPDK_JSON_WRITE_FLAG_FORMATTED; + break; + default: + usage(argv[0]); + return 1; + } + } + + if (optind == argc) { + filename = "-"; + } else if (optind == argc - 1) { + filename = argv[optind]; + } else { + usage(argv[0]); + return 1; + } + + if (strcmp(filename, "-") == 0) { + f = stdin; + } else { + f = fopen(filename, "r"); + if (f == NULL) { + perror("fopen"); + return 1; + } + } + + rc = process_file(filename, f, parse_flags, write_flags); + + if (f != stdin) { + fclose(f); + } + + return rc; +} diff --git a/src/spdk/test/app/match/match b/src/spdk/test/app/match/match new file mode 100755 index 00000000..7c1cdc45 --- /dev/null +++ b/src/spdk/test/app/match/match @@ -0,0 +1,326 @@ +#!/usr/bin/env perl +# +# Copyright 2014-2017, Intel Corporation +# +# 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 the copyright holder 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. +# + +# +# match -- compare an output file with expected results +# +# usage: match [-adoqv] [match-file]... +# +# this script compares the output from a test run, stored in a file, with +# the expected output. comparison is done line-by-line until either all +# lines compare correctly (exit code 0) or a miscompare is found (exit +# code nonzero). +# +# expected output is stored in a ".match" file, which contains a copy of +# the expected output with embedded tokens for things that should not be +# exact matches. the supported tokens are: +# +# $(N) an integer (i.e. one or more decimal digits) +# $(NC) one or more decimal digits with comma separators +# $(FP) a floating point number +# $(S) ascii string +# $(X) hex number +# $(XX) hex number prefixed with 0x +# $(W) whitespace +# $(nW) non-whitespace +# $(*) any string +# $(DD) output of a "dd" run +# $(OPT) line is optional (may be missing, matched if found) +# $(OPX) ends a contiguous list of $(OPT)...$(OPX) lines, at least +# one of which must match +# +# Additionally, if any "X.ignore" file exists, strings or phrases found per +# line in the file will be ignored if found as a substring in the +# corresponding output file (making it easy to skip entire output lines). +# +# arguments are: +# +# -a find all files of the form "X.match" in the current +# directory and match them again the corresponding file "X". +# +# -o custom output filename - only one match file can be given +# +# -d debug -- show lots of debug output +# +# -q don't print log files on mismatch +# +# -v verbose -- show every line as it is being matched +# + +use strict; +use Getopt::Std; +use Encode; +use v5.16; + +select STDERR; +binmode(STDOUT, ":utf8"); +binmode(STDERR, ":utf8"); + +my $Me = $0; +$Me =~ s,.*/,,; + +our ($opt_a, $opt_d, $opt_q, $opt_v, $opt_o); + +$SIG{HUP} = $SIG{INT} = $SIG{TERM} = $SIG{__DIE__} = sub { + die @_ if $^S; + my $errstr = shift; + die "FAIL: $Me: $errstr"; +}; + +sub usage { + my $msg = shift; + + warn "$Me: $msg\n" if $msg; + warn "Usage: $Me [-adqv] [match-file]...\n"; + warn " or: $Me [-dqv] -o output-file match-file...\n"; + exit 1; +} + +getopts('adoqv') or usage; + +my %match2file; + +if ($opt_a) { + usage("-a and filename arguments are mutually exclusive") + if $#ARGV != -1; + opendir(DIR, '.') or die "opendir: .: $!\n"; + my @matchfiles = grep { /(.*)\.match$/ && -f $1 } readdir(DIR); + closedir(DIR); + die "no files found to process\n" unless @matchfiles; + foreach my $mfile (@matchfiles) { + die "$mfile: $!\n" unless open(F, $mfile); + close(F); + my $ofile = $mfile; + $ofile =~ s/\.match$//; + die "$mfile found but cannot open $ofile: $!\n" + unless open(F, $ofile); + close(F); + $match2file{$mfile} = $ofile; + } +} elsif ($opt_o) { + usage("-o argument requires two paths") if $#ARGV != 1; + + $match2file{$ARGV[1]} = $ARGV[0]; +} else { + usage("no match-file arguments found") if $#ARGV == -1; + + # to improve the failure case, check all filename args exist and + # are provided in pairs now, before going through and processing them + foreach my $mfile (@ARGV) { + my $ofile = $mfile; + usage("$mfile: not a .match file") unless + $ofile =~ s/\.match$//; + usage("$mfile: $!") unless open(F, $mfile); + close(F); + usage("$ofile: $!") unless open(F, $ofile); + close(F); + $match2file{$mfile} = $ofile; + } +} + +my $mfile; +my $ofile; +my $ifile; +print "Files to be processed:\n" if $opt_v; +foreach $mfile (sort keys %match2file) { + $ofile = $match2file{$mfile}; + $ifile = $ofile . ".ignore"; + $ifile = undef unless (-f $ifile); + if ($opt_v) { + print " match-file \"$mfile\" output-file \"$ofile\""; + if ($ifile) { + print " ignore-file $ifile\n"; + } else { + print "\n"; + } + } + match($mfile, $ofile, $ifile); +} + +exit 0; + +# +# strip_it - user can optionally ignore lines from files that contain +# any number of substrings listed in a file called "X.ignore" where X +# is the name of the output file. +# +sub strip_it { + my ($ifile, $file, $input) = @_; + # if there is no ignore file just return unaltered input + return $input unless $ifile; + my @lines_in = split /^/, $input; + my $output; + my $line_in; + my @i_file = split /^/, snarf($ifile); + my $i_line; + my $ignore_it = 0; + + foreach $line_in (@lines_in) { + my @i_lines = @i_file; + foreach $i_line (@i_lines) { + chop($i_line); + if (index($line_in, $i_line) != -1) { + $ignore_it = 1; + if ($opt_v) { + print "Ignoring (from $file): $line_in"; + } + } + } + if ($ignore_it == 0) { + $output .= $line_in; + } + $ignore_it = 0; + } + return $output; +} + +# +# match -- process a match-file, output-file pair +# +sub match { + my ($mfile, $ofile, $ifile) = @_; + my $pat; + my $output = snarf($ofile); + $output = strip_it($ifile, $ofile, $output); + my $all_lines = $output; + my $line_pat = 0; + my $line_out = 0; + my $opt = 0; + my $opx = 0; + my $opt_found = 0; + my $fstr = snarf($mfile); + $fstr = strip_it($ifile, $mfile, $fstr); + for (split /^/, $fstr) { + $pat = $_; + $line_pat++; + $line_out++; + s/([*+?|{}.\\^\$\[()])/\\$1/g; + s/\\\$\\\(FP\\\)/[-+]?\\d*\\.?\\d+([eE][-+]?\\d+)?/g; + s/\\\$\\\(N\\\)/[-+]?\\d+/g; + s/\\\$\\\(NC\\\)/[-+]?\\d+(,[0-9]+)*/g; + s/\\\$\\\(\\\*\\\)/\\p{Print}*/g; + s/\\\$\\\(S\\\)/\\P{IsC}+/g; + s/\\\$\\\(X\\\)/\\p{XPosixXDigit}+/g; + s/\\\$\\\(XX\\\)/0x\\p{XPosixXDigit}+/g; + s/\\\$\\\(W\\\)/\\p{Blank}*/g; + s/\\\$\\\(nW\\\)/\\p{Graph}*/g; + s/\\\$\\\(DD\\\)/\\d+\\+\\d+ records in\n\\d+\\+\\d+ records out\n\\d+ bytes \\\(\\d+ .B\\\) copied, [.0-9e-]+[^,]*, [.0-9]+ .B.s/g; + if (s/\\\$\\\(OPT\\\)//) { + $opt = 1; + } elsif (s/\\\$\\\(OPX\\\)//) { + $opx = 1; + } else { + $opt_found = 0; + } + + if ($opt_v) { + my @lines = split /\n/, $output; + my $line; + if (@lines) { + $line = $lines[0]; + } else { + $line = "[EOF]"; + } + + printf("%s:%-3d %s%s:%-3d %s\n", $mfile, $line_pat, $pat, $ofile, $line_out, $line); + } + + print " => /$_/\n" if $opt_d; + print " [$output]\n" if $opt_d; + unless ($output =~ s/^$_//) { + if ($opt || ($opx && $opt_found)) { + printf("%s:%-3d [skipping optional line]\n", $ofile, $line_out) if $opt_v; + $line_out--; + $opt = 0; + } else { + if (!$opt_v) { + if ($opt_q) { + print "[MATCHING FAILED]\n"; + } else { + print "[MATCHING FAILED, COMPLETE FILE ($ofile) BELOW]\n$all_lines\n[EOF]\n"; + } + $opt_v = 1; + match($mfile, $ofile); + } + + die "$mfile:$line_pat did not match pattern\n"; + } + } elsif ($opt) { + $opt_found = 1; + } + $opx = 0; + } + + if ($output ne '') { + if (!$opt_v) { + if ($opt_q) { + print "[MATCHING FAILED]\n"; + } else { + print "[MATCHING FAILED, COMPLETE FILE ($ofile) BELOW]\n$all_lines\n[EOF]\n"; + } + } + + # make it a little more print-friendly... + $output =~ s/\n/\\n/g; + die "line $line_pat: unexpected output: \"$output\"\n"; + } +} + + +# +# snarf -- slurp an entire file into memory +# +sub snarf { + my ($file) = @_; + my $fh; + open($fh, '<', $file) or die "$file $!\n"; + + local $/; + $_ = <$fh>; + close $fh; + + # check known encodings or die + my $decoded; + my @encodings = ("UTF-8", "UTF-16", "UTF-16LE", "UTF-16BE"); + + foreach my $enc (@encodings) { + eval { $decoded = decode( $enc, $_, Encode::FB_CROAK ) }; + + if (!$@) { + $decoded =~ s/\R/\n/g; + return $decoded; + } + } + + die "$Me: ERROR: Unknown file encoding"; +} diff --git a/src/spdk/test/app/stub/.gitignore b/src/spdk/test/app/stub/.gitignore new file mode 100644 index 00000000..39802f64 --- /dev/null +++ b/src/spdk/test/app/stub/.gitignore @@ -0,0 +1 @@ +stub diff --git a/src/spdk/test/app/stub/Makefile b/src/spdk/test/app/stub/Makefile new file mode 100644 index 00000000..9cca6b2c --- /dev/null +++ b/src/spdk/test/app/stub/Makefile @@ -0,0 +1,58 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = stub + +C_SRCS := stub.c + +SPDK_LIB_LIST = event conf nvme log trace rpc jsonrpc json thread util + +LIBS += $(SOCK_MODULES_LINKER_ARGS) +LIBS += $(SPDK_LIB_LINKER_ARGS) +LIBS += $(ENV_LINKER_ARGS) + +all : $(APP) + @: + +$(APP) : $(OBJS) $(SOCK_MODULES_FILES) $(SPDK_LIB_FILES) $(ENV_LIBS) + $(LINK_C) + +clean : + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/app/stub/stub.c b/src/spdk/test/app/stub/stub.c new file mode 100644 index 00000000..c52917dd --- /dev/null +++ b/src/spdk/test/app/stub/stub.c @@ -0,0 +1,147 @@ +/*- + * 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/event.h" +#include "spdk/nvme.h" + +static char g_path[256]; + +static void +usage(char *executable_name) +{ + printf("%s [options]\n", executable_name); + printf("options:\n"); + printf(" -i shared memory ID [required]\n"); + printf(" -m mask core mask for DPDK\n"); + printf(" -n channel number of memory channels used for DPDK\n"); + printf(" -p core master (primary) core for DPDK\n"); + printf(" -s size memory size in MB for DPDK\n"); + printf(" -H show this usage\n"); +} + +static bool +probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr_opts *opts) +{ + /* + * Set the io_queue_size to UINT16_MAX to initialize + * the controller with the possible largest queue size. + */ + opts->io_queue_size = UINT16_MAX; + return true; +} + +static void +attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ +} + +static void +stub_start(void *arg1, void *arg2) +{ + int shm_id = (intptr_t)arg1; + + spdk_unaffinitize_thread(); + + if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) { + fprintf(stderr, "spdk_nvme_probe() failed\n"); + exit(1); + } + + snprintf(g_path, sizeof(g_path), "/var/run/spdk_stub%d", shm_id); + if (mknod(g_path, S_IFREG, 0) != 0) { + fprintf(stderr, "could not create sentinel file %s\n", g_path); + exit(1); + } +} + +static void +stub_shutdown(void) +{ + unlink(g_path); + spdk_app_stop(0); +} + +int +main(int argc, char **argv) +{ + int ch; + struct spdk_app_opts opts = {}; + + /* default value in opts structure */ + spdk_app_opts_init(&opts); + + opts.name = "stub"; + opts.rpc_addr = NULL; + + while ((ch = getopt(argc, argv, "i:m:n:p:s:H")) != -1) { + switch (ch) { + case 'i': + opts.shm_id = atoi(optarg); + break; + case 'm': + opts.reactor_mask = optarg; + break; + case 'n': + opts.mem_channel = atoi(optarg); + break; + case 'p': + opts.master_core = atoi(optarg); + break; + case 's': + opts.mem_size = atoi(optarg); + break; + case 'H': + default: + usage(argv[0]); + exit(EXIT_SUCCESS); + } + } + + if (opts.shm_id < 0) { + fprintf(stderr, "%s: -i shared memory ID must be specified\n", argv[0]); + usage(argv[0]); + exit(1); + } + + opts.shutdown_cb = stub_shutdown; + opts.max_delay_us = 1000 * 1000; + + ch = spdk_app_start(&opts, stub_start, (void *)(intptr_t)opts.shm_id, NULL); + spdk_app_fini(); + + return ch; +} diff --git a/src/spdk/test/bdev/Makefile b/src/spdk/test/bdev/Makefile new file mode 100644 index 00000000..cb15bd49 --- /dev/null +++ b/src/spdk/test/bdev/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = bdevio bdevperf + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/bdev/bdev.conf.in b/src/spdk/test/bdev/bdev.conf.in new file mode 100644 index 00000000..0439ab5e --- /dev/null +++ b/src/spdk/test/bdev/bdev.conf.in @@ -0,0 +1,44 @@ +[Passthru] + # PT <bdev name> <vbdev name> + PT Malloc3 TestPT + +[Malloc] + NumberOfLuns 7 + LunSizeInMB 32 + +[Split] + # Split Malloc1 into two auto-sized halves + Split Malloc1 2 + + # Split Malloc2 into eight 4-megabyte pieces, + # leaving the rest of the device inaccessible + Split Malloc2 8 4 + +[AIO] + AIO /dev/ram0 AIO0 + AIO /tmp/aiofile AIO1 2048 + +[QoS] + # QoS section defines limitation on performance + # metric like IOPS and bandwidth + # + # Format: Limit_IOPS Bdev_Name IOPS_Limit_Value + # + # IOPS limit must be 10000 or greater and be multiple + # of 10000 + # + # Assign 20000 IOPS for the Malloc0 block device + Limit_IOPS Malloc0 20000 + # + # Bandwidth limit must be 10 (MB) or greater and be + # multiple of 10 + # Assign 100 (MB) bandwidth for the Malloc3 block + # device + Limit_BPS Malloc3 100 + +[RAID0] + Name raid0 + StripSize 64 + NumDevices 2 + RaidLevel 0 + Devices Malloc4 Malloc5 diff --git a/src/spdk/test/bdev/bdevio/.gitignore b/src/spdk/test/bdev/bdevio/.gitignore new file mode 100644 index 00000000..1bb55429 --- /dev/null +++ b/src/spdk/test/bdev/bdevio/.gitignore @@ -0,0 +1 @@ +bdevio diff --git a/src/spdk/test/bdev/bdevio/Makefile b/src/spdk/test/bdev/bdevio/Makefile new file mode 100644 index 00000000..d973846f --- /dev/null +++ b/src/spdk/test/bdev/bdevio/Makefile @@ -0,0 +1,61 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = bdevio + +C_SRCS := bdevio.c + +SPDK_LIB_LIST = event_bdev event_copy +SPDK_LIB_LIST += bdev copy event trace log conf thread util rpc jsonrpc json + +LIBS += $(BLOCKDEV_MODULES_LINKER_ARGS) \ + $(COPY_MODULES_LINKER_ARGS) \ + $(SOCK_MODULES_LINKER_ARGS) + +LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) -lcunit + +all : $(APP) + @: + +$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(COPY_MODULES_FILES) $(BLOCKDEV_MODULES_FILES) $(SOCK_MODULES_FILES) $(LINKER_MODULES) $(ENV_LIBS) + $(LINK_C) + +clean : + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/bdev/bdevio/bdevio.c b/src/spdk/test/bdev/bdevio/bdevio.c new file mode 100644 index 00000000..c139b6f2 --- /dev/null +++ b/src/spdk/test/bdev/bdevio/bdevio.c @@ -0,0 +1,973 @@ +/*- + * 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/bdev.h" +#include "spdk/copy_engine.h" +#include "spdk/env.h" +#include "spdk/log.h" +#include "spdk/thread.h" +#include "spdk/event.h" + +#include "CUnit/Basic.h" + +#define BUFFER_IOVS 1024 +#define BUFFER_SIZE 260 * 1024 +#define BDEV_TASK_ARRAY_SIZE 2048 + +pthread_mutex_t g_test_mutex; +pthread_cond_t g_test_cond; + +static uint32_t g_lcore_id_init; +static uint32_t g_lcore_id_ut; +static uint32_t g_lcore_id_io; + +struct io_target { + struct spdk_bdev *bdev; + struct spdk_bdev_desc *bdev_desc; + struct spdk_io_channel *ch; + struct io_target *next; +}; + +struct bdevio_request { + char *buf; + int data_len; + uint64_t offset; + struct iovec iov[BUFFER_IOVS]; + int iovcnt; + struct io_target *target; +}; + +struct io_target *g_io_targets = NULL; + +static void +execute_spdk_function(spdk_event_fn fn, void *arg1, void *arg2) +{ + struct spdk_event *event; + + event = spdk_event_allocate(g_lcore_id_io, fn, arg1, arg2); + pthread_mutex_lock(&g_test_mutex); + spdk_event_call(event); + pthread_cond_wait(&g_test_cond, &g_test_mutex); + pthread_mutex_unlock(&g_test_mutex); +} + +static void +wake_ut_thread(void) +{ + pthread_mutex_lock(&g_test_mutex); + pthread_cond_signal(&g_test_cond); + pthread_mutex_unlock(&g_test_mutex); +} + +static void +__get_io_channel(void *arg1, void *arg2) +{ + struct io_target *target = arg1; + + target->ch = spdk_bdev_get_io_channel(target->bdev_desc); + assert(target->ch); + wake_ut_thread(); +} + +static int +bdevio_construct_targets(void) +{ + struct spdk_bdev *bdev; + struct io_target *target; + int rc; + + printf("I/O targets:\n"); + + bdev = spdk_bdev_first_leaf(); + while (bdev != NULL) { + uint64_t num_blocks = spdk_bdev_get_num_blocks(bdev); + uint32_t block_size = spdk_bdev_get_block_size(bdev); + + target = malloc(sizeof(struct io_target)); + if (target == NULL) { + return -ENOMEM; + } + + rc = spdk_bdev_open(bdev, true, NULL, NULL, &target->bdev_desc); + if (rc != 0) { + free(target); + SPDK_ERRLOG("Could not open leaf bdev %s, error=%d\n", spdk_bdev_get_name(bdev), rc); + bdev = spdk_bdev_next_leaf(bdev); + continue; + } + + printf(" %s: %" PRIu64 " blocks of %" PRIu32 " bytes (%" PRIu64 " MiB)\n", + spdk_bdev_get_name(bdev), + num_blocks, block_size, + (num_blocks * block_size + 1024 * 1024 - 1) / (1024 * 1024)); + + target->bdev = bdev; + target->next = g_io_targets; + execute_spdk_function(__get_io_channel, target, NULL); + g_io_targets = target; + + bdev = spdk_bdev_next_leaf(bdev); + } + + return 0; +} + +static void +__put_io_channel(void *arg1, void *arg2) +{ + struct io_target *target = arg1; + + spdk_put_io_channel(target->ch); + wake_ut_thread(); +} + +static void +bdevio_cleanup_targets(void) +{ + struct io_target *target; + + target = g_io_targets; + while (target != NULL) { + execute_spdk_function(__put_io_channel, target, NULL); + spdk_bdev_close(target->bdev_desc); + g_io_targets = target->next; + free(target); + target = g_io_targets; + } +} + +static bool g_completion_success; + +static void +initialize_buffer(char **buf, int pattern, int size) +{ + *buf = spdk_dma_zmalloc(size, 0x1000, NULL); + memset(*buf, pattern, size); +} + +static void +quick_test_complete(struct spdk_bdev_io *bdev_io, bool success, void *arg) +{ + g_completion_success = success; + spdk_bdev_free_io(bdev_io); + wake_ut_thread(); +} + +static void +__blockdev_write(void *arg1, void *arg2) +{ + struct bdevio_request *req = arg1; + struct io_target *target = req->target; + int rc; + + if (req->iovcnt) { + rc = spdk_bdev_writev(target->bdev_desc, target->ch, req->iov, req->iovcnt, req->offset, + req->data_len, quick_test_complete, NULL); + } else { + rc = spdk_bdev_write(target->bdev_desc, target->ch, req->buf, req->offset, + req->data_len, quick_test_complete, NULL); + } + + if (rc) { + g_completion_success = false; + wake_ut_thread(); + } +} + +static void +__blockdev_write_zeroes(void *arg1, void *arg2) +{ + struct bdevio_request *req = arg1; + struct io_target *target = req->target; + int rc; + + rc = spdk_bdev_write_zeroes(target->bdev_desc, target->ch, req->offset, + req->data_len, quick_test_complete, NULL); + if (rc) { + g_completion_success = false; + wake_ut_thread(); + } +} + +static void +sgl_chop_buffer(struct bdevio_request *req, int iov_len) +{ + int data_len = req->data_len; + char *buf = req->buf; + + req->iovcnt = 0; + if (!iov_len) { + return; + } + + for (; data_len > 0 && req->iovcnt < BUFFER_IOVS; req->iovcnt++) { + if (data_len < iov_len) { + iov_len = data_len; + } + + req->iov[req->iovcnt].iov_base = buf; + req->iov[req->iovcnt].iov_len = iov_len; + + buf += iov_len; + data_len -= iov_len; + } + + CU_ASSERT_EQUAL_FATAL(data_len, 0); +} + +static void +blockdev_write(struct io_target *target, char *tx_buf, + uint64_t offset, int data_len, int iov_len) +{ + struct bdevio_request req; + + req.target = target; + req.buf = tx_buf; + req.data_len = data_len; + req.offset = offset; + sgl_chop_buffer(&req, iov_len); + + g_completion_success = false; + + execute_spdk_function(__blockdev_write, &req, NULL); +} + +static void +blockdev_write_zeroes(struct io_target *target, char *tx_buf, + uint64_t offset, int data_len) +{ + struct bdevio_request req; + + req.target = target; + req.buf = tx_buf; + req.data_len = data_len; + req.offset = offset; + + g_completion_success = false; + + execute_spdk_function(__blockdev_write_zeroes, &req, NULL); +} + +static void +__blockdev_read(void *arg1, void *arg2) +{ + struct bdevio_request *req = arg1; + struct io_target *target = req->target; + int rc; + + if (req->iovcnt) { + rc = spdk_bdev_readv(target->bdev_desc, target->ch, req->iov, req->iovcnt, req->offset, + req->data_len, quick_test_complete, NULL); + } else { + rc = spdk_bdev_read(target->bdev_desc, target->ch, req->buf, req->offset, + req->data_len, quick_test_complete, NULL); + } + + if (rc) { + g_completion_success = false; + wake_ut_thread(); + } +} + +static void +blockdev_read(struct io_target *target, char *rx_buf, + uint64_t offset, int data_len, int iov_len) +{ + struct bdevio_request req; + + req.target = target; + req.buf = rx_buf; + req.data_len = data_len; + req.offset = offset; + req.iovcnt = 0; + sgl_chop_buffer(&req, iov_len); + + g_completion_success = false; + + execute_spdk_function(__blockdev_read, &req, NULL); +} + +static int +blockdev_write_read_data_match(char *rx_buf, char *tx_buf, int data_length) +{ + int rc; + rc = memcmp(rx_buf, tx_buf, data_length); + + spdk_dma_free(rx_buf); + spdk_dma_free(tx_buf); + + return rc; +} + +static void +blockdev_write_read(uint32_t data_length, uint32_t iov_len, int pattern, uint64_t offset, + int expected_rc, bool write_zeroes) +{ + struct io_target *target; + char *tx_buf = NULL; + char *rx_buf = NULL; + int rc; + + target = g_io_targets; + while (target != NULL) { + if (data_length < spdk_bdev_get_block_size(target->bdev) || + data_length / spdk_bdev_get_block_size(target->bdev) > spdk_bdev_get_num_blocks(target->bdev)) { + target = target->next; + continue; + } + + if (!write_zeroes) { + initialize_buffer(&tx_buf, pattern, data_length); + initialize_buffer(&rx_buf, 0, data_length); + + blockdev_write(target, tx_buf, offset, data_length, iov_len); + } else { + initialize_buffer(&tx_buf, 0, data_length); + initialize_buffer(&rx_buf, pattern, data_length); + + blockdev_write_zeroes(target, tx_buf, offset, data_length); + } + + + if (expected_rc == 0) { + CU_ASSERT_EQUAL(g_completion_success, true); + } else { + CU_ASSERT_EQUAL(g_completion_success, false); + } + blockdev_read(target, rx_buf, offset, data_length, iov_len); + + if (expected_rc == 0) { + CU_ASSERT_EQUAL(g_completion_success, true); + } else { + CU_ASSERT_EQUAL(g_completion_success, false); + } + + if (g_completion_success) { + rc = blockdev_write_read_data_match(rx_buf, tx_buf, data_length); + /* Assert the write by comparing it with values read + * from each blockdev */ + CU_ASSERT_EQUAL(rc, 0); + } + + target = target->next; + } +} + +static void +blockdev_write_read_4k(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 4K */ + data_length = 4096; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +blockdev_write_zeroes_read_4k(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 4K */ + data_length = 4096; + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write_zeroes and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 1); +} + +/* + * This i/o will not have to split at the bdev layer. + */ +static void +blockdev_write_zeroes_read_1m(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 1M */ + data_length = 1048576; + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write_zeroes and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 1); +} + +/* + * This i/o will have to split at the bdev layer if + * write-zeroes is not supported by the bdev. + */ +static void +blockdev_write_zeroes_read_3m(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 3M */ + data_length = 3145728; + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write_zeroes and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 1); +} + +/* + * This i/o will have to split at the bdev layer if + * write-zeroes is not supported by the bdev. It also + * tests a write size that is not an even multiple of + * the bdev layer zero buffer size. + */ +static void +blockdev_write_zeroes_read_3m_500k(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 3.5M */ + data_length = 3670016; + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write_zeroes and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 1); +} + +static void +blockdev_writev_readv_4k(void) +{ + uint32_t data_length, iov_len; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 4K */ + data_length = 4096; + iov_len = 4096; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, iov_len, pattern, offset, expected_rc, 0); +} + +static void +blockdev_writev_readv_30x4k(void) +{ + uint32_t data_length, iov_len; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 4K */ + data_length = 4096 * 30; + iov_len = 4096; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, iov_len, pattern, offset, expected_rc, 0); +} + +static void +blockdev_write_read_512Bytes(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 512 */ + data_length = 512; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +blockdev_writev_readv_512Bytes(void) +{ + uint32_t data_length, iov_len; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 512 */ + data_length = 512; + iov_len = 512; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, iov_len, pattern, offset, expected_rc, 0); +} + +static void +blockdev_write_read_size_gt_128k(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 132K */ + data_length = 135168; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +blockdev_writev_readv_size_gt_128k(void) +{ + uint32_t data_length, iov_len; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 132K */ + data_length = 135168; + iov_len = 135168; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, iov_len, pattern, offset, expected_rc, 0); +} + +static void +blockdev_writev_readv_size_gt_128k_two_iov(void) +{ + uint32_t data_length, iov_len; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 132K */ + data_length = 135168; + iov_len = 128 * 1024; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, iov_len, pattern, offset, expected_rc, 0); +} + +static void +blockdev_write_read_invalid_size(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size is not a multiple of the block size */ + data_length = 0x1015; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are invalid, hence the expected return value + * of write and read for all blockdevs is < 0 */ + expected_rc = -1; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +blockdev_write_read_offset_plus_nbytes_equals_bdev_size(void) +{ + struct io_target *target; + struct spdk_bdev *bdev; + char *tx_buf = NULL; + char *rx_buf = NULL; + uint64_t offset; + uint32_t block_size; + int rc; + + target = g_io_targets; + while (target != NULL) { + bdev = target->bdev; + + block_size = spdk_bdev_get_block_size(bdev); + + /* The start offset has been set to a marginal value + * such that offset + nbytes == Total size of + * blockdev. */ + offset = ((spdk_bdev_get_num_blocks(bdev) - 1) * block_size); + + initialize_buffer(&tx_buf, 0xA3, block_size); + initialize_buffer(&rx_buf, 0, block_size); + + blockdev_write(target, tx_buf, offset, block_size, 0); + CU_ASSERT_EQUAL(g_completion_success, true); + + blockdev_read(target, rx_buf, offset, block_size, 0); + CU_ASSERT_EQUAL(g_completion_success, true); + + rc = blockdev_write_read_data_match(rx_buf, tx_buf, block_size); + /* Assert the write by comparing it with values read + * from each blockdev */ + CU_ASSERT_EQUAL(rc, 0); + + target = target->next; + } +} + +static void +blockdev_write_read_offset_plus_nbytes_gt_bdev_size(void) +{ + struct io_target *target; + struct spdk_bdev *bdev; + char *tx_buf = NULL; + char *rx_buf = NULL; + int data_length; + uint64_t offset; + int pattern; + + /* Tests the overflow condition of the blockdevs. */ + data_length = 4096; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + pattern = 0xA3; + + target = g_io_targets; + while (target != NULL) { + bdev = target->bdev; + + /* The start offset has been set to a valid value + * but offset + nbytes is greater than the Total size + * of the blockdev. The test should fail. */ + offset = ((spdk_bdev_get_num_blocks(bdev) * spdk_bdev_get_block_size(bdev)) - 1024); + + initialize_buffer(&tx_buf, pattern, data_length); + initialize_buffer(&rx_buf, 0, data_length); + + blockdev_write(target, tx_buf, offset, data_length, 0); + CU_ASSERT_EQUAL(g_completion_success, false); + + blockdev_read(target, rx_buf, offset, data_length, 0); + CU_ASSERT_EQUAL(g_completion_success, false); + + target = target->next; + } +} + +static void +blockdev_write_read_max_offset(void) +{ + int data_length; + uint64_t offset; + int pattern; + int expected_rc; + + data_length = 4096; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + /* The start offset has been set to UINT64_MAX such that + * adding nbytes wraps around and points to an invalid address. */ + offset = UINT64_MAX; + pattern = 0xA3; + /* Params are invalid, hence the expected return value + * of write and read for all blockdevs is < 0 */ + expected_rc = -1; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +blockdev_overlapped_write_read_8k(void) +{ + int data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 8K */ + data_length = 8192; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + /* Assert the write by comparing it with values read + * from the same offset for each blockdev */ + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); + + /* Overwrite the pattern 0xbb of size 8K on an address offset overlapping + * with the address written above and assert the new value in + * the overlapped address range */ + /* Populate 8k with value 0xBB */ + pattern = 0xBB; + /* Offset = 6144; Overlap offset addresses and write value 0xbb */ + offset = 4096; + /* Assert the write by comparing it with values read + * from the overlapped offset for each blockdev */ + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +__blockdev_reset(void *arg1, void *arg2) +{ + struct bdevio_request *req = arg1; + struct io_target *target = req->target; + int rc; + + rc = spdk_bdev_reset(target->bdev_desc, target->ch, quick_test_complete, NULL); + if (rc < 0) { + g_completion_success = false; + wake_ut_thread(); + } +} + +static void +blockdev_reset(struct io_target *target) +{ + struct bdevio_request req; + + req.target = target; + + g_completion_success = false; + + execute_spdk_function(__blockdev_reset, &req, NULL); +} + +static void +blockdev_test_reset(void) +{ + struct io_target *target; + + target = g_io_targets; + while (target != NULL) { + blockdev_reset(target); + CU_ASSERT_EQUAL(g_completion_success, true); + + target = target->next; + } +} + +static void +__stop_init_thread(void *arg1, void *arg2) +{ + unsigned num_failures = (unsigned)(uintptr_t)arg1; + + bdevio_cleanup_targets(); + spdk_app_stop(num_failures); +} + +static void +stop_init_thread(unsigned num_failures) +{ + struct spdk_event *event; + + event = spdk_event_allocate(g_lcore_id_init, __stop_init_thread, + (void *)(uintptr_t)num_failures, NULL); + spdk_event_call(event); +} + +static void +__run_ut_thread(void *arg1, void *arg2) +{ + CU_pSuite suite = NULL; + unsigned num_failures; + + if (CU_initialize_registry() != CUE_SUCCESS) { + stop_init_thread(CU_get_error()); + return; + } + + suite = CU_add_suite("components_suite", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + stop_init_thread(CU_get_error()); + return; + } + + if ( + CU_add_test(suite, "blockdev write read 4k", blockdev_write_read_4k) == NULL + || CU_add_test(suite, "blockdev write zeroes read 4k", blockdev_write_zeroes_read_4k) == NULL + || CU_add_test(suite, "blockdev write zeroes read 1m", blockdev_write_zeroes_read_1m) == NULL + || CU_add_test(suite, "blockdev write zeroes read 3m", blockdev_write_zeroes_read_3m) == NULL + || CU_add_test(suite, "blockdev write zeroes read 3.5m", blockdev_write_zeroes_read_3m_500k) == NULL + || CU_add_test(suite, "blockdev write read 512 bytes", + blockdev_write_read_512Bytes) == NULL + || CU_add_test(suite, "blockdev write read size > 128k", + blockdev_write_read_size_gt_128k) == NULL + || CU_add_test(suite, "blockdev write read invalid size", + blockdev_write_read_invalid_size) == NULL + || CU_add_test(suite, "blockdev write read offset + nbytes == size of blockdev", + blockdev_write_read_offset_plus_nbytes_equals_bdev_size) == NULL + || CU_add_test(suite, "blockdev write read offset + nbytes > size of blockdev", + blockdev_write_read_offset_plus_nbytes_gt_bdev_size) == NULL + || CU_add_test(suite, "blockdev write read max offset", + blockdev_write_read_max_offset) == NULL + || CU_add_test(suite, "blockdev write read 8k on overlapped address offset", + blockdev_overlapped_write_read_8k) == NULL + || CU_add_test(suite, "blockdev writev readv 4k", blockdev_writev_readv_4k) == NULL + || CU_add_test(suite, "blockdev writev readv 30 x 4k", + blockdev_writev_readv_30x4k) == NULL + || CU_add_test(suite, "blockdev writev readv 512 bytes", + blockdev_writev_readv_512Bytes) == NULL + || CU_add_test(suite, "blockdev writev readv size > 128k", + blockdev_writev_readv_size_gt_128k) == NULL + || CU_add_test(suite, "blockdev writev readv size > 128k in two iovs", + blockdev_writev_readv_size_gt_128k_two_iov) == NULL + || CU_add_test(suite, "blockdev reset", + blockdev_test_reset) == NULL + ) { + CU_cleanup_registry(); + stop_init_thread(CU_get_error()); + return; + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + stop_init_thread(num_failures); +} + +static void +test_main(void *arg1, void *arg2) +{ + struct spdk_event *event; + + pthread_mutex_init(&g_test_mutex, NULL); + pthread_cond_init(&g_test_cond, NULL); + + g_lcore_id_init = spdk_env_get_first_core(); + g_lcore_id_ut = spdk_env_get_next_core(g_lcore_id_init); + g_lcore_id_io = spdk_env_get_next_core(g_lcore_id_ut); + + if (g_lcore_id_init == SPDK_ENV_LCORE_ID_ANY || + g_lcore_id_ut == SPDK_ENV_LCORE_ID_ANY || + g_lcore_id_io == SPDK_ENV_LCORE_ID_ANY) { + SPDK_ERRLOG("Could not reserve 3 separate threads.\n"); + spdk_app_stop(-1); + } + + if (bdevio_construct_targets() < 0) { + spdk_app_stop(-1); + return; + } + + event = spdk_event_allocate(g_lcore_id_ut, __run_ut_thread, NULL, NULL); + spdk_event_call(event); +} + +static void +bdevio_usage(void) +{ +} + +static void +bdevio_parse_arg(int ch, char *arg) +{ +} + +int +main(int argc, char **argv) +{ + int rc; + struct spdk_app_opts opts = {}; + + spdk_app_opts_init(&opts); + opts.name = "bdevtest"; + opts.rpc_addr = NULL; + opts.reactor_mask = "0x7"; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "", NULL, + bdevio_parse_arg, bdevio_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + return rc; + } + + rc = spdk_app_start(&opts, test_main, NULL, NULL); + spdk_app_fini(); + + return rc; +} diff --git a/src/spdk/test/bdev/bdevjson/json_config.sh b/src/spdk/test/bdev/bdevjson/json_config.sh new file mode 100755 index 00000000..3e5d276e --- /dev/null +++ b/src/spdk/test/bdev/bdevjson/json_config.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -ex +BDEV_JSON_DIR=$(readlink -f $(dirname $0)) +. $BDEV_JSON_DIR/../../json_config/common.sh + +function test_subsystems() { + run_spdk_tgt + rootdir=$(readlink -f $BDEV_JSON_DIR/../../..) + + rpc_py="$spdk_rpc_py" + clear_config_py="$spdk_clear_config_py" + load_nvme + create_bdev_subsystem_config + test_json_config + + clear_bdev_subsystem_config + test_global_params "spdk_tgt" + kill_targets +} + +timing_enter json_config +trap 'on_error_exit "${FUNCNAME}" "${LINENO}"' ERR + +test_subsystems + +timing_exit json_config +report_test_completion json_config diff --git a/src/spdk/test/bdev/bdevjson/rbd_json_config.sh b/src/spdk/test/bdev/bdevjson/rbd_json_config.sh new file mode 100755 index 00000000..458ecb74 --- /dev/null +++ b/src/spdk/test/bdev/bdevjson/rbd_json_config.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -ex +VHOST_JSON_DIR=$(readlink -f $(dirname $0)) +. $VHOST_JSON_DIR/../../json_config/common.sh + +function test_subsystems() { + run_spdk_tgt + rootdir=$(readlink -f $VHOST_JSON_DIR/../../..) + + rpc_py="$spdk_rpc_py" + clear_config_py="$spdk_clear_config_py" + $rpc_py start_subsystem_init + + create_rbd_bdev_subsystem_config + test_json_config + clear_rbd_bdev_subsystem_config + + kill_targets +} + +trap 'rbd_cleanup; on_error_exit "${FUNCNAME}" "${LINENO}"' ERR +timing_enter rbd_json_config + +test_subsystems +timing_exit rbd_json_config +report_test_completion rbd_json_config diff --git a/src/spdk/test/bdev/bdevperf/.gitignore b/src/spdk/test/bdev/bdevperf/.gitignore new file mode 100644 index 00000000..e14ddd84 --- /dev/null +++ b/src/spdk/test/bdev/bdevperf/.gitignore @@ -0,0 +1 @@ +bdevperf diff --git a/src/spdk/test/bdev/bdevperf/Makefile b/src/spdk/test/bdev/bdevperf/Makefile new file mode 100644 index 00000000..eb5f76ae --- /dev/null +++ b/src/spdk/test/bdev/bdevperf/Makefile @@ -0,0 +1,61 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = bdevperf + +C_SRCS := bdevperf.c + +SPDK_LIB_LIST = event_bdev event_copy +SPDK_LIB_LIST += bdev copy event trace log conf thread util rpc jsonrpc json + +LIBS += $(BLOCKDEV_MODULES_LINKER_ARGS) \ + $(COPY_MODULES_LINKER_ARGS) \ + $(SOCK_MODULES_LINKER_ARGS) + +LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) + +all : $(APP) + @: + +$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(BLOCKDEV_MODULES_FILES) $(COPY_MODULES_FILES) $(SOCK_MODULES_FILES) $(ENV_LIBS) + $(LINK_C) + +clean : + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/bdev/bdevperf/bdevperf.c b/src/spdk/test/bdev/bdevperf/bdevperf.c new file mode 100644 index 00000000..1416ea27 --- /dev/null +++ b/src/spdk/test/bdev/bdevperf/bdevperf.c @@ -0,0 +1,1035 @@ +/*- + * 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/bdev.h" +#include "spdk/copy_engine.h" +#include "spdk/endian.h" +#include "spdk/env.h" +#include "spdk/event.h" +#include "spdk/log.h" +#include "spdk/util.h" +#include "spdk/thread.h" +#include "spdk/string.h" + +struct bdevperf_task { + struct iovec iov; + struct io_target *target; + void *buf; + uint64_t offset_blocks; + enum spdk_bdev_io_type io_type; + TAILQ_ENTRY(bdevperf_task) link; + struct spdk_bdev_io_wait_entry bdev_io_wait; +}; + +static const char *g_workload_type; +static int g_io_size = 0; +/* initialize to invalid value so we can detect if user overrides it. */ +static int g_rw_percentage = -1; +static int g_is_random; +static bool g_verify = false; +static bool g_reset = false; +static bool g_unmap = false; +static bool g_write_zeroes = false; +static bool g_flush = false; +static int g_queue_depth; +static uint64_t g_time_in_usec; +static int g_show_performance_real_time = 0; +static uint64_t g_show_performance_period_in_usec = 1000000; +static uint64_t g_show_performance_period_num = 0; +static uint64_t g_show_performance_ema_period = 0; +static bool g_run_failed = false; +static bool g_shutdown = false; +static uint64_t g_shutdown_tsc; +static bool g_zcopy = true; +static unsigned g_master_core; +static int g_time_in_sec; +static bool g_mix_specified; + +static struct spdk_poller *g_perf_timer = NULL; + +static void bdevperf_submit_single(struct io_target *target, struct bdevperf_task *task); + +struct io_target { + char *name; + struct spdk_bdev *bdev; + struct spdk_bdev_desc *bdev_desc; + struct spdk_io_channel *ch; + struct io_target *next; + unsigned lcore; + uint64_t io_completed; + uint64_t prev_io_completed; + double ema_io_per_second; + int current_queue_depth; + uint64_t size_in_ios; + uint64_t offset_in_ios; + uint64_t io_size_blocks; + bool is_draining; + struct spdk_poller *run_timer; + struct spdk_poller *reset_timer; + TAILQ_HEAD(, bdevperf_task) task_list; +}; + +struct io_target **g_head; +uint32_t *coremap; +static int g_target_count = 0; + +/* + * Used to determine how the I/O buffers should be aligned. + * This alignment will be bumped up for blockdevs that + * require alignment based on block length - for example, + * AIO blockdevs. + */ +static size_t g_min_alignment = 8; + +static int +blockdev_heads_init(void) +{ + uint32_t i, idx = 0; + uint32_t core_count = spdk_env_get_core_count(); + + g_head = calloc(core_count, sizeof(struct io_target *)); + if (!g_head) { + fprintf(stderr, "Cannot allocate g_head array with size=%u\n", + core_count); + return -1; + } + + coremap = calloc(core_count, sizeof(uint32_t)); + if (!coremap) { + free(g_head); + fprintf(stderr, "Cannot allocate coremap array with size=%u\n", + core_count); + return -1; + } + + SPDK_ENV_FOREACH_CORE(i) { + coremap[idx++] = i; + } + + return 0; +} + +static void +bdevperf_free_target(struct io_target *target) +{ + struct bdevperf_task *task, *tmp; + + TAILQ_FOREACH_SAFE(task, &target->task_list, link, tmp) { + TAILQ_REMOVE(&target->task_list, task, link); + spdk_dma_free(task->buf); + free(task); + } + + free(target->name); + free(target); +} + +static void +blockdev_heads_destroy(void) +{ + uint32_t i, core_count; + struct io_target *target, *next_target; + + if (!g_head) { + return; + } + + core_count = spdk_env_get_core_count(); + for (i = 0; i < core_count; i++) { + target = g_head[i]; + while (target != NULL) { + next_target = target->next; + bdevperf_free_target(target); + target = next_target; + } + } + + free(g_head); + free(coremap); +} + +static void +bdevperf_construct_targets(void) +{ + int index = 0; + struct spdk_bdev *bdev; + struct io_target *target; + size_t align; + int rc; + + bdev = spdk_bdev_first_leaf(); + while (bdev != NULL) { + + if (g_unmap && !spdk_bdev_io_type_supported(bdev, SPDK_BDEV_IO_TYPE_UNMAP)) { + printf("Skipping %s because it does not support unmap\n", spdk_bdev_get_name(bdev)); + bdev = spdk_bdev_next_leaf(bdev); + continue; + } + + target = malloc(sizeof(struct io_target)); + if (!target) { + fprintf(stderr, "Unable to allocate memory for new target.\n"); + /* Return immediately because all mallocs will presumably fail after this */ + return; + } + + target->name = strdup(spdk_bdev_get_name(bdev)); + if (!target->name) { + fprintf(stderr, "Unable to allocate memory for target name.\n"); + free(target); + /* Return immediately because all mallocs will presumably fail after this */ + return; + } + + rc = spdk_bdev_open(bdev, true, NULL, NULL, &target->bdev_desc); + if (rc != 0) { + SPDK_ERRLOG("Could not open leaf bdev %s, error=%d\n", spdk_bdev_get_name(bdev), rc); + free(target->name); + free(target); + bdev = spdk_bdev_next_leaf(bdev); + continue; + } + + target->bdev = bdev; + /* Mapping each target to lcore */ + index = g_target_count % spdk_env_get_core_count(); + target->next = g_head[index]; + target->lcore = coremap[index]; + target->io_completed = 0; + target->current_queue_depth = 0; + target->offset_in_ios = 0; + target->io_size_blocks = g_io_size / spdk_bdev_get_block_size(bdev); + if (target->io_size_blocks == 0 || + (g_io_size % spdk_bdev_get_block_size(bdev)) != 0) { + SPDK_ERRLOG("IO size (%d) is bigger than blocksize of bdev %s (%"PRIu32") or not a blocksize multiple\n", + g_io_size, spdk_bdev_get_name(bdev), spdk_bdev_get_block_size(bdev)); + spdk_bdev_close(target->bdev_desc); + free(target->name); + free(target); + bdev = spdk_bdev_next_leaf(bdev); + continue; + } + + target->size_in_ios = spdk_bdev_get_num_blocks(bdev) / target->io_size_blocks; + align = spdk_bdev_get_buf_align(bdev); + /* + * TODO: This should actually use the LCM of align and g_min_alignment, but + * it is fairly safe to assume all alignments are powers of two for now. + */ + g_min_alignment = spdk_max(g_min_alignment, align); + + target->is_draining = false; + target->run_timer = NULL; + target->reset_timer = NULL; + TAILQ_INIT(&target->task_list); + + g_head[index] = target; + g_target_count++; + + bdev = spdk_bdev_next_leaf(bdev); + } +} + +static void +end_run(void *arg1, void *arg2) +{ + struct io_target *target = arg1; + + spdk_put_io_channel(target->ch); + spdk_bdev_close(target->bdev_desc); + if (--g_target_count == 0) { + if (g_show_performance_real_time) { + spdk_poller_unregister(&g_perf_timer); + } + if (g_run_failed) { + spdk_app_stop(1); + } else { + spdk_app_stop(0); + } + } +} + +static void +bdevperf_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + struct io_target *target; + struct bdevperf_task *task = cb_arg; + struct spdk_event *complete; + struct iovec *iovs; + int iovcnt; + + target = task->target; + + if (!success) { + if (!g_reset) { + target->is_draining = true; + g_run_failed = true; + printf("task offset: %lu on target bdev=%s fails\n", + task->offset_blocks, target->name); + } + } else if (g_verify || g_reset) { + spdk_bdev_io_get_iovec(bdev_io, &iovs, &iovcnt); + assert(iovcnt == 1); + assert(iovs != NULL); + if (memcmp(task->buf, iovs[0].iov_base, g_io_size) != 0) { + printf("Buffer mismatch! Disk Offset: %lu\n", task->offset_blocks); + target->is_draining = true; + g_run_failed = true; + } + } + + target->current_queue_depth--; + + if (success) { + target->io_completed++; + } + + spdk_bdev_free_io(bdev_io); + + /* + * is_draining indicates when time has expired for the test run + * and we are just waiting for the previously submitted I/O + * to complete. In this case, do not submit a new I/O to replace + * the one just completed. + */ + if (!target->is_draining) { + bdevperf_submit_single(target, task); + } else { + TAILQ_INSERT_TAIL(&target->task_list, task, link); + if (target->current_queue_depth == 0) { + complete = spdk_event_allocate(g_master_core, end_run, target, NULL); + spdk_event_call(complete); + } + } +} + +static void +bdevperf_verify_submit_read(void *cb_arg) +{ + struct io_target *target; + struct bdevperf_task *task = cb_arg; + int rc; + + target = task->target; + + /* Read the data back in */ + rc = spdk_bdev_read_blocks(target->bdev_desc, target->ch, NULL, task->offset_blocks, + target->io_size_blocks, bdevperf_complete, task); + if (rc == -ENOMEM) { + task->bdev_io_wait.bdev = target->bdev; + task->bdev_io_wait.cb_fn = bdevperf_verify_submit_read; + task->bdev_io_wait.cb_arg = task; + spdk_bdev_queue_io_wait(target->bdev, target->ch, &task->bdev_io_wait); + } else if (rc != 0) { + printf("Failed to submit read: %d\n", rc); + target->is_draining = true; + g_run_failed = true; + } +} + +static void +bdevperf_verify_write_complete(struct spdk_bdev_io *bdev_io, bool success, + void *cb_arg) +{ + if (success) { + spdk_bdev_free_io(bdev_io); + bdevperf_verify_submit_read(cb_arg); + } else { + bdevperf_complete(bdev_io, success, cb_arg); + } +} + +static __thread unsigned int seed = 0; + +static void +bdevperf_prep_task(struct bdevperf_task *task) +{ + struct io_target *target = task->target; + uint64_t offset_in_ios; + + if (g_is_random) { + offset_in_ios = rand_r(&seed) % target->size_in_ios; + } else { + offset_in_ios = target->offset_in_ios++; + if (target->offset_in_ios == target->size_in_ios) { + target->offset_in_ios = 0; + } + } + + task->offset_blocks = offset_in_ios * target->io_size_blocks; + if (g_verify || g_reset) { + memset(task->buf, rand_r(&seed) % 256, g_io_size); + task->iov.iov_base = task->buf; + task->iov.iov_len = g_io_size; + task->io_type = SPDK_BDEV_IO_TYPE_WRITE; + } else if (g_flush) { + task->io_type = SPDK_BDEV_IO_TYPE_FLUSH; + } else if (g_unmap) { + task->io_type = SPDK_BDEV_IO_TYPE_UNMAP; + } else if (g_write_zeroes) { + task->io_type = SPDK_BDEV_IO_TYPE_WRITE_ZEROES; + } else if ((g_rw_percentage == 100) || + (g_rw_percentage != 0 && ((rand_r(&seed) % 100) < g_rw_percentage))) { + task->io_type = SPDK_BDEV_IO_TYPE_READ; + } else { + task->iov.iov_base = task->buf; + task->iov.iov_len = g_io_size; + task->io_type = SPDK_BDEV_IO_TYPE_WRITE; + } +} + +static void +bdevperf_submit_task(void *arg) +{ + struct bdevperf_task *task = arg; + struct io_target *target = task->target; + struct spdk_bdev_desc *desc; + struct spdk_io_channel *ch; + spdk_bdev_io_completion_cb cb_fn; + void *rbuf; + int rc; + + desc = target->bdev_desc; + ch = target->ch; + + switch (task->io_type) { + case SPDK_BDEV_IO_TYPE_WRITE: + cb_fn = (g_verify || g_reset) ? bdevperf_verify_write_complete : bdevperf_complete; + rc = spdk_bdev_writev_blocks(desc, ch, &task->iov, 1, task->offset_blocks, + target->io_size_blocks, cb_fn, task); + break; + case SPDK_BDEV_IO_TYPE_FLUSH: + rc = spdk_bdev_flush_blocks(desc, ch, task->offset_blocks, + target->io_size_blocks, bdevperf_complete, task); + break; + case SPDK_BDEV_IO_TYPE_UNMAP: + rc = spdk_bdev_unmap_blocks(desc, ch, task->offset_blocks, + target->io_size_blocks, bdevperf_complete, task); + break; + case SPDK_BDEV_IO_TYPE_WRITE_ZEROES: + rc = spdk_bdev_write_zeroes_blocks(desc, ch, task->offset_blocks, + target->io_size_blocks, bdevperf_complete, task); + break; + case SPDK_BDEV_IO_TYPE_READ: + rbuf = g_zcopy ? NULL : task->buf; + rc = spdk_bdev_read_blocks(desc, ch, rbuf, task->offset_blocks, + target->io_size_blocks, bdevperf_complete, task); + break; + default: + assert(false); + rc = -EINVAL; + break; + } + + if (rc == -ENOMEM) { + task->bdev_io_wait.bdev = target->bdev; + task->bdev_io_wait.cb_fn = bdevperf_submit_task; + task->bdev_io_wait.cb_arg = task; + spdk_bdev_queue_io_wait(target->bdev, ch, &task->bdev_io_wait); + return; + } else if (rc != 0) { + printf("Failed to submit bdev_io: %d\n", rc); + target->is_draining = true; + g_run_failed = true; + return; + } + + target->current_queue_depth++; +} + +static void +bdevperf_submit_single(struct io_target *target, struct bdevperf_task *task) +{ + if (!task) { + if (!TAILQ_EMPTY(&target->task_list)) { + task = TAILQ_FIRST(&target->task_list); + TAILQ_REMOVE(&target->task_list, task, link); + } else { + printf("Task allocation failed\n"); + abort(); + } + } + + bdevperf_prep_task(task); + bdevperf_submit_task(task); +} + +static void +bdevperf_submit_io(struct io_target *target, int queue_depth) +{ + while (queue_depth-- > 0) { + bdevperf_submit_single(target, NULL); + } +} + +static int +end_target(void *arg) +{ + struct io_target *target = arg; + + spdk_poller_unregister(&target->run_timer); + if (g_reset) { + spdk_poller_unregister(&target->reset_timer); + } + + target->is_draining = true; + + return -1; +} + +static int reset_target(void *arg); + +static void +reset_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + struct bdevperf_task *task = cb_arg; + struct io_target *target = task->target; + + if (!success) { + printf("Reset blockdev=%s failed\n", spdk_bdev_get_name(target->bdev)); + target->is_draining = true; + g_run_failed = true; + } + + TAILQ_INSERT_TAIL(&target->task_list, task, link); + spdk_bdev_free_io(bdev_io); + + target->reset_timer = spdk_poller_register(reset_target, target, + 10 * 1000000); +} + +static int +reset_target(void *arg) +{ + struct io_target *target = arg; + struct bdevperf_task *task = NULL; + int rc; + + spdk_poller_unregister(&target->reset_timer); + + /* Do reset. */ + task = TAILQ_FIRST(&target->task_list); + if (!task) { + printf("Task allocation failed\n"); + abort(); + } + TAILQ_REMOVE(&target->task_list, task, link); + + rc = spdk_bdev_reset(target->bdev_desc, target->ch, + reset_cb, task); + if (rc) { + printf("Reset failed: %d\n", rc); + target->is_draining = true; + g_run_failed = true; + } + + return -1; +} + +static void +bdevperf_submit_on_core(void *arg1, void *arg2) +{ + struct io_target *target = arg1; + + /* Submit initial I/O for each block device. Each time one + * completes, another will be submitted. */ + while (target != NULL) { + target->ch = spdk_bdev_get_io_channel(target->bdev_desc); + if (!target->ch) { + printf("Skip this device (%s) as IO channel not setup.\n", + spdk_bdev_get_name(target->bdev)); + g_target_count--; + g_run_failed = true; + spdk_bdev_close(target->bdev_desc); + + target = target->next; + continue; + } + + /* Start a timer to stop this I/O chain when the run is over */ + target->run_timer = spdk_poller_register(end_target, target, + g_time_in_usec); + if (g_reset) { + target->reset_timer = spdk_poller_register(reset_target, target, + 10 * 1000000); + } + bdevperf_submit_io(target, g_queue_depth); + target = target->next; + } +} + +static void +bdevperf_usage(void) +{ + printf(" -q <depth> io depth\n"); + printf(" -o <size> io size in bytes\n"); + printf(" -w <type> io pattern type, must be one of (read, write, randread, randwrite, rw, randrw, verify, reset, unmap, flush)\n"); + printf(" -t <time> time in seconds\n"); + printf(" -M <percent> rwmixread (100 for reads, 0 for writes)\n"); + printf(" -P <num> number of moving average period\n"); + printf("\t\t(If set to n, show weighted mean of the previous n IO/s in real time)\n"); + printf("\t\t(Formula: M = 2 / (n + 1), EMA[i+1] = IO/s * M + (1 - M) * EMA[i])\n"); + printf("\t\t(only valid with -S)\n"); + printf(" -S show performance result in real time in seconds\n"); +} + +/* + * Cumulative Moving Average (CMA): average of all data up to current + * Exponential Moving Average (EMA): weighted mean of the previous n data and more weight is given to recent + * Simple Moving Average (SMA): unweighted mean of the previous n data + * + * Bdevperf supports CMA and EMA. + */ +static double +get_cma_io_per_second(struct io_target *target, uint64_t io_time_in_usec) +{ + return (double)target->io_completed * 1000000 / io_time_in_usec; +} + +static double +get_ema_io_per_second(struct io_target *target, uint64_t ema_period) +{ + double io_completed, io_per_second; + + io_completed = target->io_completed; + io_per_second = (double)(io_completed - target->prev_io_completed) * 1000000 + / g_show_performance_period_in_usec; + target->prev_io_completed = io_completed; + + target->ema_io_per_second += (io_per_second - target->ema_io_per_second) * 2 + / (ema_period + 1); + return target->ema_io_per_second; +} + +static void +performance_dump(uint64_t io_time_in_usec, uint64_t ema_period) +{ + uint32_t index; + unsigned lcore_id; + double io_per_second, mb_per_second; + double total_io_per_second, total_mb_per_second; + struct io_target *target; + + total_io_per_second = 0; + total_mb_per_second = 0; + for (index = 0; index < spdk_env_get_core_count(); index++) { + target = g_head[index]; + if (target != NULL) { + lcore_id = target->lcore; + printf("\r Logical core: %u\n", lcore_id); + } + while (target != NULL) { + if (ema_period == 0) { + io_per_second = get_cma_io_per_second(target, io_time_in_usec); + } else { + io_per_second = get_ema_io_per_second(target, ema_period); + } + mb_per_second = io_per_second * g_io_size / (1024 * 1024); + printf("\r %-20s: %10.2f IO/s %10.2f MB/s\n", + target->name, io_per_second, mb_per_second); + total_io_per_second += io_per_second; + total_mb_per_second += mb_per_second; + target = target->next; + } + } + + printf("\r =====================================================\n"); + printf("\r %-20s: %10.2f IO/s %10.2f MB/s\n", + "Total", total_io_per_second, total_mb_per_second); + fflush(stdout); + +} + +static int +performance_statistics_thread(void *arg) +{ + g_show_performance_period_num++; + performance_dump(g_show_performance_period_num * g_show_performance_period_in_usec, + g_show_performance_ema_period); + return -1; +} + +static int +bdevperf_construct_targets_tasks(void) +{ + uint32_t i; + struct io_target *target; + struct bdevperf_task *task; + int j, task_num = g_queue_depth; + + /* + * Create the task pool after we have enumerated the targets, so that we know + * the min buffer alignment. Some backends such as AIO have alignment restrictions + * that must be accounted for. + */ + if (g_reset) { + task_num += 1; + } + + /* Initialize task list for each target */ + for (i = 0; i < spdk_env_get_core_count(); i++) { + target = g_head[i]; + if (!target) { + break; + } + while (target != NULL) { + for (j = 0; j < task_num; j++) { + task = calloc(1, sizeof(struct bdevperf_task)); + if (!task) { + fprintf(stderr, "Failed to allocate task from memory\n"); + goto ret; + } + + task->buf = spdk_dma_zmalloc(g_io_size, g_min_alignment, NULL); + if (!task->buf) { + fprintf(stderr, "Cannot allocate buf for task=%p\n", task); + free(task); + goto ret; + } + + task->target = target; + TAILQ_INSERT_TAIL(&target->task_list, task, link); + } + target = target->next; + } + } + + return 0; + +ret: + fprintf(stderr, "Bdevperf program exits due to memory allocation issue\n"); + fprintf(stderr, "Use -d XXX to allocate more huge pages, e.g., -d 4096\n"); + return -1; +} + +static void +bdevperf_run(void *arg1, void *arg2) +{ + uint32_t i; + struct io_target *target; + struct spdk_event *event; + int rc; + + rc = blockdev_heads_init(); + if (rc) { + spdk_app_stop(1); + return; + } + + bdevperf_construct_targets(); + + if (g_target_count == 0) { + fprintf(stderr, "No valid bdevs found.\n"); + spdk_app_stop(1); + return; + } + + rc = bdevperf_construct_targets_tasks(); + if (rc) { + blockdev_heads_destroy(); + spdk_app_stop(1); + return; + } + + printf("Running I/O for %" PRIu64 " seconds...\n", g_time_in_usec / 1000000); + fflush(stdout); + + /* Start a timer to dump performance numbers */ + g_shutdown_tsc = spdk_get_ticks(); + if (g_show_performance_real_time) { + g_perf_timer = spdk_poller_register(performance_statistics_thread, NULL, + g_show_performance_period_in_usec); + } + + g_master_core = spdk_env_get_current_core(); + /* Send events to start all I/O */ + for (i = 0; i < spdk_env_get_core_count(); i++) { + target = g_head[i]; + if (target == NULL) { + break; + } + event = spdk_event_allocate(target->lcore, bdevperf_submit_on_core, + target, NULL); + spdk_event_call(event); + } +} + +static void +bdevperf_stop_io_on_core(void *arg1, void *arg2) +{ + struct io_target *target = arg1; + + /* Stop I/O for each block device. */ + while (target != NULL) { + end_target(target); + target = target->next; + } +} + +static void +spdk_bdevperf_shutdown_cb(void) +{ + uint32_t i; + struct io_target *target; + struct spdk_event *event; + + g_shutdown = true; + g_shutdown_tsc = spdk_get_ticks() - g_shutdown_tsc; + + /* Send events to stop all I/O on each core */ + for (i = 0; i < spdk_env_get_core_count(); i++) { + if (g_head == NULL) { + break; + } + target = g_head[i]; + if (target == NULL) { + break; + } + event = spdk_event_allocate(target->lcore, bdevperf_stop_io_on_core, + target, NULL); + spdk_event_call(event); + } +} + +static void +bdevperf_parse_arg(int ch, char *arg) +{ + switch (ch) { + case 'q': + g_queue_depth = atoi(optarg); + break; + case 'o': + g_io_size = atoi(optarg); + break; + case 't': + g_time_in_sec = atoi(optarg); + break; + case 'w': + g_workload_type = optarg; + break; + case 'M': + g_rw_percentage = atoi(optarg); + g_mix_specified = true; + break; + case 'P': + g_show_performance_ema_period = atoi(optarg); + break; + case 'S': + g_show_performance_real_time = 1; + g_show_performance_period_in_usec = atoi(optarg) * 1000000; + g_show_performance_period_in_usec = spdk_max(g_show_performance_period_in_usec, + g_show_performance_period_in_usec); + break; + } +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc; + + spdk_app_opts_init(&opts); + opts.name = "bdevperf"; + opts.rpc_addr = NULL; + opts.reactor_mask = NULL; + opts.mem_size = 1024; + opts.shutdown_cb = spdk_bdevperf_shutdown_cb; + + /* default value */ + g_queue_depth = 0; + g_io_size = 0; + g_workload_type = NULL; + g_time_in_sec = 0; + g_mix_specified = false; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "q:o:t:w:M:P:S:", NULL, + bdevperf_parse_arg, bdevperf_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + return rc; + } + + if (g_queue_depth <= 0) { + spdk_app_usage(); + bdevperf_usage(); + exit(1); + } + if (g_io_size <= 0) { + spdk_app_usage(); + bdevperf_usage(); + exit(1); + } + if (!g_workload_type) { + spdk_app_usage(); + bdevperf_usage(); + exit(1); + } + if (g_time_in_sec <= 0) { + spdk_app_usage(); + bdevperf_usage(); + exit(1); + } + g_time_in_usec = g_time_in_sec * 1000000LL; + + if (g_show_performance_ema_period > 0 && + g_show_performance_real_time == 0) { + fprintf(stderr, "-P option must be specified with -S option\n"); + exit(1); + } + + if (strcmp(g_workload_type, "read") && + strcmp(g_workload_type, "write") && + strcmp(g_workload_type, "randread") && + strcmp(g_workload_type, "randwrite") && + strcmp(g_workload_type, "rw") && + strcmp(g_workload_type, "randrw") && + strcmp(g_workload_type, "verify") && + strcmp(g_workload_type, "reset") && + strcmp(g_workload_type, "unmap") && + strcmp(g_workload_type, "write_zeroes") && + strcmp(g_workload_type, "flush")) { + fprintf(stderr, + "io pattern type must be one of\n" + "(read, write, randread, randwrite, rw, randrw, verify, reset, unmap, flush)\n"); + exit(1); + } + + if (!strcmp(g_workload_type, "read") || + !strcmp(g_workload_type, "randread")) { + g_rw_percentage = 100; + } + + if (!strcmp(g_workload_type, "write") || + !strcmp(g_workload_type, "randwrite")) { + g_rw_percentage = 0; + } + + if (!strcmp(g_workload_type, "unmap")) { + g_unmap = true; + } + + if (!strcmp(g_workload_type, "write_zeroes")) { + g_write_zeroes = true; + } + + if (!strcmp(g_workload_type, "flush")) { + g_flush = true; + } + + if (!strcmp(g_workload_type, "verify") || + !strcmp(g_workload_type, "reset")) { + g_rw_percentage = 50; + if (g_io_size > SPDK_BDEV_LARGE_BUF_MAX_SIZE) { + fprintf(stderr, "Unable to exceed max I/O size of %d for verify. (%d provided).\n", + SPDK_BDEV_LARGE_BUF_MAX_SIZE, g_io_size); + exit(1); + } + if (opts.reactor_mask) { + fprintf(stderr, "Ignoring -m option. Verify can only run with a single core.\n"); + opts.reactor_mask = NULL; + } + g_verify = true; + if (!strcmp(g_workload_type, "reset")) { + g_reset = true; + } + } + + if (!strcmp(g_workload_type, "read") || + !strcmp(g_workload_type, "randread") || + !strcmp(g_workload_type, "write") || + !strcmp(g_workload_type, "randwrite") || + !strcmp(g_workload_type, "verify") || + !strcmp(g_workload_type, "reset") || + !strcmp(g_workload_type, "unmap") || + !strcmp(g_workload_type, "write_zeroes") || + !strcmp(g_workload_type, "flush")) { + if (g_mix_specified) { + fprintf(stderr, "Ignoring -M option... Please use -M option" + " only when using rw or randrw.\n"); + } + } + + if (!strcmp(g_workload_type, "rw") || + !strcmp(g_workload_type, "randrw")) { + if (g_rw_percentage < 0 || g_rw_percentage > 100) { + fprintf(stderr, + "-M must be specified to value from 0 to 100 " + "for rw or randrw.\n"); + exit(1); + } + } + + if (!strcmp(g_workload_type, "read") || + !strcmp(g_workload_type, "write") || + !strcmp(g_workload_type, "rw") || + !strcmp(g_workload_type, "verify") || + !strcmp(g_workload_type, "reset") || + !strcmp(g_workload_type, "unmap") || + !strcmp(g_workload_type, "write_zeroes")) { + g_is_random = 0; + } else { + g_is_random = 1; + } + + if (g_io_size > SPDK_BDEV_LARGE_BUF_MAX_SIZE) { + printf("I/O size of %d is greater than zero copy threshold (%d).\n", + g_io_size, SPDK_BDEV_LARGE_BUF_MAX_SIZE); + printf("Zero copy mechanism will not be used.\n"); + g_zcopy = false; + } + + rc = spdk_app_start(&opts, bdevperf_run, NULL, NULL); + if (rc) { + g_run_failed = true; + } + + if (g_shutdown) { + g_time_in_usec = g_shutdown_tsc * 1000000 / spdk_get_ticks_hz(); + printf("Received shutdown signal, test time is about %.6f seconds\n", + (double)g_time_in_usec / 1000000); + } + + if (g_time_in_usec) { + if (!g_run_failed) { + performance_dump(g_time_in_usec, 0); + } + } else { + printf("Test time less than one microsecond, no performance data will be shown\n"); + } + + blockdev_heads_destroy(); + spdk_app_fini(); + return g_run_failed; +} diff --git a/src/spdk/test/bdev/blockdev.sh b/src/spdk/test/bdev/blockdev.sh new file mode 100755 index 00000000..bf3e006b --- /dev/null +++ b/src/spdk/test/bdev/blockdev.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash + +set -e + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) +plugindir=$rootdir/examples/bdev/fio_plugin +rpc_py="$rootdir/scripts/rpc.py" + +function run_fio() +{ + if [ $RUN_NIGHTLY -eq 0 ]; then + LD_PRELOAD=$plugindir/fio_plugin /usr/src/fio/fio --ioengine=spdk_bdev --iodepth=8 --bs=4k --runtime=10 $testdir/bdev.fio "$@" + elif [ $RUN_NIGHTLY_FAILING -eq 1 ]; then + # Use size 192KB which both exceeds typical 128KB max NVMe I/O + # size and will cross 128KB Intel DC P3700 stripe boundaries. + LD_PRELOAD=$plugindir/fio_plugin /usr/src/fio/fio --ioengine=spdk_bdev --iodepth=128 --bs=192k --runtime=100 $testdir/bdev.fio "$@" + fi +} + +source $rootdir/test/common/autotest_common.sh +source $testdir/nbd_common.sh + +function nbd_function_test() { + if [ $(uname -s) = Linux ] && modprobe -n nbd; then + local rpc_server=/var/tmp/spdk-nbd.sock + local conf=$1 + local nbd_num=6 + local nbd_all=(`ls /dev/nbd* | grep -v p`) + local bdev_all=($bdevs_name) + local nbd_list=(${nbd_all[@]:0:$nbd_num}) + local bdev_list=(${bdev_all[@]:0:$nbd_num}) + + if [ ! -e $conf ]; then + return 1 + fi + + modprobe nbd + $rootdir/test/app/bdev_svc/bdev_svc -r $rpc_server -i 0 -c ${conf} & + nbd_pid=$! + echo "Process nbd pid: $nbd_pid" + waitforlisten $nbd_pid $rpc_server + + nbd_rpc_data_verify $rpc_server "${bdev_list[*]}" "${nbd_list[*]}" + + $rpc_py -s $rpc_server delete_passthru_bdev TestPT + + killprocess $nbd_pid + fi + + return 0 +} + +timing_enter bdev + +# Create a file to be used as an AIO backend +dd if=/dev/zero of=/tmp/aiofile bs=2048 count=5000 + +cp $testdir/bdev.conf.in $testdir/bdev.conf +$rootdir/scripts/gen_nvme.sh >> $testdir/bdev.conf + +if [ $SPDK_TEST_RBD -eq 1 ]; then + timing_enter rbd_setup + rbd_setup 127.0.0.1 + timing_exit rbd_setup + + $rootdir/scripts/gen_rbd.sh >> $testdir/bdev.conf +fi + +if [ $SPDK_TEST_CRYPTO -eq 1 ]; then + $rootdir/scripts/gen_crypto.sh Malloc6 >> $testdir/bdev.conf +fi + +if hash pmempool; then + rm -f /tmp/spdk-pmem-pool + pmempool create blk --size=32M 512 /tmp/spdk-pmem-pool + echo "[Pmem]" >> $testdir/bdev.conf + echo " Blk /tmp/spdk-pmem-pool Pmem0" >> $testdir/bdev.conf +fi + +timing_enter hello_bdev +if grep -q Nvme0 $testdir/bdev.conf; then + $rootdir/examples/bdev/hello_world/hello_bdev -c $testdir/bdev.conf -b Nvme0n1 +fi +timing_exit hello_bdev + +timing_enter bounds +$testdir/bdevio/bdevio -c $testdir/bdev.conf +timing_exit bounds + +timing_enter nbd_gpt +if grep -q Nvme0 $testdir/bdev.conf; then + part_dev_by_gpt $testdir/bdev.conf Nvme0n1 $rootdir +fi +timing_exit nbd_gpt + +timing_enter bdev_svc +bdevs=$(discover_bdevs $rootdir $testdir/bdev.conf | jq -r '.[] | select(.claimed == false)') +timing_exit bdev_svc + +timing_enter nbd +bdevs_name=$(echo $bdevs | jq -r '.name') +nbd_function_test $testdir/bdev.conf "$bdevs_name" +timing_exit nbd + +if [ -d /usr/src/fio ] && [ $SPDK_RUN_ASAN -eq 0 ]; then + timing_enter fio + + timing_enter fio_rw_verify + # Generate the fio config file given the list of all unclaimed bdevs + fio_config_gen $testdir/bdev.fio verify + for b in $(echo $bdevs | jq -r '.name'); do + fio_config_add_job $testdir/bdev.fio $b + done + + run_fio --spdk_conf=./test/bdev/bdev.conf + + rm -f *.state + rm -f $testdir/bdev.fio + timing_exit fio_rw_verify + + timing_enter fio_trim + # Generate the fio config file given the list of all unclaimed bdevs that support unmap + fio_config_gen $testdir/bdev.fio trim + for b in $(echo $bdevs | jq -r 'select(.supported_io_types.unmap == true) | .name'); do + fio_config_add_job $testdir/bdev.fio $b + done + + run_fio --spdk_conf=./test/bdev/bdev.conf + + rm -f *.state + rm -f $testdir/bdev.fio + timing_exit fio_trim + report_test_completion "bdev_fio" + timing_exit fio +fi + +# Create conf file for bdevperf with gpt +cat > $testdir/bdev_gpt.conf << EOL +[Gpt] + Disable No +EOL + +# Get Nvme info through filtering gen_nvme.sh's result +$rootdir/scripts/gen_nvme.sh >> $testdir/bdev_gpt.conf + +# Run bdevperf with gpt +$testdir/bdevperf/bdevperf -c $testdir/bdev_gpt.conf -q 128 -o 4096 -w verify -t 5 +$testdir/bdevperf/bdevperf -c $testdir/bdev_gpt.conf -q 128 -o 4096 -w write_zeroes -t 1 +rm -f $testdir/bdev_gpt.conf + +if [ $RUN_NIGHTLY -eq 1 ]; then + # Temporarily disabled - infinite loop + timing_enter reset + #$testdir/bdevperf/bdevperf -c $testdir/bdev.conf -q 16 -w reset -o 4096 -t 60 + timing_exit reset + report_test_completion "nightly_bdev_reset" +fi + + +if grep -q Nvme0 $testdir/bdev.conf; then + part_dev_by_gpt $testdir/bdev.conf Nvme0n1 $rootdir reset +fi + +rm -f /tmp/aiofile +rm -f /tmp/spdk-pmem-pool +rm -f $testdir/bdev.conf +trap - SIGINT SIGTERM EXIT +rbd_cleanup +report_test_completion "bdev" +timing_exit bdev diff --git a/src/spdk/test/bdev/nbd_common.sh b/src/spdk/test/bdev/nbd_common.sh new file mode 100644 index 00000000..df8caac6 --- /dev/null +++ b/src/spdk/test/bdev/nbd_common.sh @@ -0,0 +1,95 @@ +set -e + +function nbd_start_disks() { + local rpc_server=$1 + local bdev_list=($2) + local nbd_list=($3) + + for (( i=0; i<${#nbd_list[@]}; i++ )); do + $rootdir/scripts/rpc.py -s $rpc_server start_nbd_disk \ + ${bdev_list[$i]} ${nbd_list[$i]} + done + # Wait for nbd devices ready + for i in ${nbd_list[@]}; do + waitfornbd ${i:5} + done +} + +function waitfornbd_exit() { + nbd_name=$1 + + for ((i=1; i<=20; i++)); do + if grep -q -w $nbd_name /proc/partitions; then + sleep 0.1 + else + break + fi + done + + return 0 +} + +function nbd_stop_disks() { + local rpc_server=$1 + local nbd_list=($2) + + for i in ${nbd_list[@]}; do + $rootdir/scripts/rpc.py -s $rpc_server stop_nbd_disk $i + done + for i in ${nbd_list[@]}; do + waitfornbd_exit ${i:5} + done +} + +function nbd_get_count() { + # return = count of spdk nbd devices + local rpc_server=$1 + + nbd_disks_json=`$rootdir/scripts/rpc.py -s $rpc_server get_nbd_disks` + nbd_disks_name=`echo "${nbd_disks_json}" | jq -r '.[] | .nbd_device'` + count=`echo "${nbd_disks_name}" | grep -c /dev/nbd || true` + echo $count +} + +function nbd_dd_data_verify() { + local nbd_list=($1) + local operation=$2 + local tmp_file=/tmp/nbdrandtest + + if [ "$operation" = "write" ]; then + # data write + dd if=/dev/urandom of=$tmp_file bs=4096 count=256 + for i in ${nbd_list[@]}; do + dd if=$tmp_file of=$i bs=4096 count=256 oflag=direct + done + elif [ "$operation" = "verify" ]; then + # data read and verify + for i in ${nbd_list[@]}; do + cmp -b -n 1M $tmp_file $i + done + rm $tmp_file + fi +} + +function nbd_rpc_data_verify() { + local rpc_server=$1 + local bdev_list=($2) + local nbd_list=($3) + + nbd_start_disks $rpc_server "${bdev_list[*]}" "${nbd_list[*]}" + count=$(nbd_get_count $rpc_server) + if [ $count -ne ${#nbd_list[@]} ]; then + return -1 + fi + + nbd_dd_data_verify "${nbd_list[*]}" "write" + nbd_dd_data_verify "${nbd_list[*]}" "verify" + + nbd_stop_disks $rpc_server "${nbd_list[*]}" + count=$(nbd_get_count $rpc_server) + if [ $count -ne 0 ]; then + return -1 + fi + + return 0 +} diff --git a/src/spdk/test/bdev/nbdjson/json_config.sh b/src/spdk/test/bdev/nbdjson/json_config.sh new file mode 100755 index 00000000..ccd7006c --- /dev/null +++ b/src/spdk/test/bdev/nbdjson/json_config.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -xe +NBD_JSON_DIR=$(readlink -f $(dirname $0)) +. $NBD_JSON_DIR/../../json_config/common.sh +rpc_py="$spdk_rpc_py" +clear_config_py="$spdk_clear_config_py" +trap 'on_error_exit "${FUNCNAME}" "${LINENO}"' ERR + +timing_enter nbd_json_config +run_spdk_tgt +load_nvme +modprobe nbd + +timing_enter nbd_json_config_create_setup +$rpc_py construct_malloc_bdev 128 512 --name Malloc0 +$rpc_py start_nbd_disk Malloc0 /dev/nbd0 +$rpc_py start_nbd_disk Nvme0n1 /dev/nbd1 +timing_exit nbd_json_config_create_setup + +timing_enter nbd_json_config_test +test_json_config +timing_exit nbd_json_config_test + +$clear_config_py clear_config +kill_targets +rmmod nbd +timing_exit nbd_json_config +report_test_completion nbd_json_config diff --git a/src/spdk/test/blobfs/Makefile b/src/spdk/test/blobfs/Makefile new file mode 100644 index 00000000..1a9dfefa --- /dev/null +++ b/src/spdk/test/blobfs/Makefile @@ -0,0 +1,49 @@ +# +# 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)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = mkfs + +# TODO: do not check a hardcoded path here +ifneq (,$(wildcard /usr/local/include/fuse3)) +DIRS-y += fuse +endif + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/blobfs/fuse/.gitignore b/src/spdk/test/blobfs/fuse/.gitignore new file mode 100644 index 00000000..a517c488 --- /dev/null +++ b/src/spdk/test/blobfs/fuse/.gitignore @@ -0,0 +1 @@ +fuse diff --git a/src/spdk/test/blobfs/fuse/Makefile b/src/spdk/test/blobfs/fuse/Makefile new file mode 100644 index 00000000..847da50e --- /dev/null +++ b/src/spdk/test/blobfs/fuse/Makefile @@ -0,0 +1,60 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = fuse + +C_SRCS := fuse.c + +SPDK_LIB_LIST = event_bdev event_copy +SPDK_LIB_LIST += blobfs blob bdev blob_bdev copy event thread util conf trace \ + log jsonrpc json rpc + +LIBS += $(COPY_MODULES_LINKER_ARGS) $(BLOCKDEV_MODULES_LINKER_ARGS) +LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) +LIBS+= -L/usr/local/lib -lfuse3 + +all : $(APP) + @: + +$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(COPY_MODULES_FILES) $(BLOCKDEV_MODULES_FILES) $(ENV_LIBS) + $(LINK_C) + +clean : + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/blobfs/fuse/fuse.c b/src/spdk/test/blobfs/fuse/fuse.c new file mode 100644 index 00000000..9d11dfa0 --- /dev/null +++ b/src/spdk/test/blobfs/fuse/fuse.c @@ -0,0 +1,348 @@ +/*- + * 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" + +#define FUSE_USE_VERSION 30 +#include "fuse3/fuse.h" +#include "fuse3/fuse_lowlevel.h" + +#include "spdk/blobfs.h" +#include "spdk/bdev.h" +#include "spdk/event.h" +#include "spdk/thread.h" +#include "spdk/blob_bdev.h" +#include "spdk/log.h" + +struct fuse *g_fuse; +char *g_bdev_name; +char *g_mountpoint; +pthread_t g_fuse_thread; + +struct spdk_bs_dev *g_bs_dev; +struct spdk_filesystem *g_fs; +struct spdk_io_channel *g_channel; +struct spdk_file *g_file; +int g_fserrno; +int g_fuse_argc = 0; +char **g_fuse_argv = NULL; + +static void +__call_fn(void *arg1, void *arg2) +{ + fs_request_fn fn; + + fn = (fs_request_fn)arg1; + fn(arg2); +} + +static void +__send_request(fs_request_fn fn, void *arg) +{ + struct spdk_event *event; + + event = spdk_event_allocate(0, __call_fn, (void *)fn, arg); + spdk_event_call(event); +} + +static int +spdk_fuse_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) +{ + struct spdk_file_stat stat; + int rc; + + if (!strcmp(path, "/")) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + return 0; + } + + rc = spdk_fs_file_stat(g_fs, g_channel, path, &stat); + if (rc == 0) { + stbuf->st_mode = S_IFREG | 0644; + stbuf->st_nlink = 1; + stbuf->st_size = stat.size; + } + + return rc; +} + +static int +spdk_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + struct spdk_file *file; + const char *filename; + spdk_fs_iter iter; + + filler(buf, ".", NULL, 0, 0); + filler(buf, "..", NULL, 0, 0); + + iter = spdk_fs_iter_first(g_fs); + while (iter != NULL) { + file = spdk_fs_iter_get_file(iter); + iter = spdk_fs_iter_next(iter); + filename = spdk_file_get_name(file); + filler(buf, &filename[1], NULL, 0, 0); + } + + return 0; +} + +static int +spdk_fuse_mknod(const char *path, mode_t mode, dev_t rdev) +{ + return spdk_fs_create_file(g_fs, g_channel, path); +} + +static int +spdk_fuse_unlink(const char *path) +{ + return spdk_fs_delete_file(g_fs, g_channel, path); +} + +static int +spdk_fuse_truncate(const char *path, off_t size, struct fuse_file_info *fi) +{ + struct spdk_file *file; + int rc; + + rc = spdk_fs_open_file(g_fs, g_channel, path, 0, &file); + if (rc != 0) { + return -rc; + } + + rc = spdk_file_truncate(file, g_channel, size); + if (rc != 0) { + return -rc; + } + + spdk_file_close(file, g_channel); + + return 0; +} + +static int +spdk_fuse_utimens(const char *path, const struct timespec tv[2], struct fuse_file_info *fi) +{ + return 0; +} + +static int +spdk_fuse_open(const char *path, struct fuse_file_info *info) +{ + struct spdk_file *file; + int rc; + + rc = spdk_fs_open_file(g_fs, g_channel, path, 0, &file); + if (rc != 0) { + return -rc; + } + + info->fh = (uintptr_t)file; + return 0; +} + +static int +spdk_fuse_release(const char *path, struct fuse_file_info *info) +{ + struct spdk_file *file = (struct spdk_file *)info->fh; + + return spdk_file_close(file, g_channel); +} + +static int +spdk_fuse_read(const char *path, char *buf, size_t len, off_t offset, struct fuse_file_info *info) +{ + struct spdk_file *file = (struct spdk_file *)info->fh; + + return spdk_file_read(file, g_channel, buf, offset, len); +} + +static int +spdk_fuse_write(const char *path, const char *buf, size_t len, off_t offset, + struct fuse_file_info *info) +{ + struct spdk_file *file = (struct spdk_file *)info->fh; + int rc; + + rc = spdk_file_write(file, g_channel, (void *)buf, offset, len); + if (rc == 0) { + return len; + } else { + return rc; + } +} + +static int +spdk_fuse_flush(const char *path, struct fuse_file_info *info) +{ + return 0; +} + +static int +spdk_fuse_fsync(const char *path, int datasync, struct fuse_file_info *info) +{ + return 0; +} + +static int +spdk_fuse_rename(const char *old_path, const char *new_path, unsigned int flags) +{ + return spdk_fs_rename_file(g_fs, g_channel, old_path, new_path); +} + +static struct fuse_operations spdk_fuse_oper = { + .getattr = spdk_fuse_getattr, + .readdir = spdk_fuse_readdir, + .mknod = spdk_fuse_mknod, + .unlink = spdk_fuse_unlink, + .truncate = spdk_fuse_truncate, + .utimens = spdk_fuse_utimens, + .open = spdk_fuse_open, + .release = spdk_fuse_release, + .read = spdk_fuse_read, + .write = spdk_fuse_write, + .flush = spdk_fuse_flush, + .fsync = spdk_fuse_fsync, + .rename = spdk_fuse_rename, +}; + +static void +construct_targets(void) +{ + struct spdk_bdev *bdev; + + bdev = spdk_bdev_get_by_name(g_bdev_name); + if (bdev == NULL) { + SPDK_ERRLOG("bdev %s not found\n", g_bdev_name); + exit(1); + } + + g_bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL); + + printf("Mounting BlobFS on bdev %s\n", spdk_bdev_get_name(bdev)); +} + +static void +start_fuse_fn(void *arg1, void *arg2) +{ + struct fuse_args args = FUSE_ARGS_INIT(g_fuse_argc, g_fuse_argv); + int rc; + struct fuse_cmdline_opts opts = {}; + + g_fuse_thread = pthread_self(); + rc = fuse_parse_cmdline(&args, &opts); + if (rc != 0) { + spdk_app_stop(-1); + fuse_opt_free_args(&args); + return; + } + g_fuse = fuse_new(&args, &spdk_fuse_oper, sizeof(spdk_fuse_oper), NULL); + fuse_opt_free_args(&args); + + rc = fuse_mount(g_fuse, g_mountpoint); + if (rc != 0) { + spdk_app_stop(-1); + return; + } + + fuse_daemonize(true /* true = run in foreground */); + + fuse_loop(g_fuse); + + fuse_unmount(g_fuse); + fuse_destroy(g_fuse); +} + +static void +init_cb(void *ctx, struct spdk_filesystem *fs, int fserrno) +{ + struct spdk_event *event; + + g_fs = fs; + g_channel = spdk_fs_alloc_io_channel_sync(g_fs); + event = spdk_event_allocate(1, start_fuse_fn, NULL, NULL); + spdk_event_call(event); +} + +static void +spdk_fuse_run(void *arg1, void *arg2) +{ + construct_targets(); + spdk_fs_load(g_bs_dev, __send_request, init_cb, NULL); +} + +static void +shutdown_cb(void *ctx, int fserrno) +{ + fuse_session_exit(fuse_get_session(g_fuse)); + pthread_kill(g_fuse_thread, SIGINT); + spdk_fs_free_io_channel(g_channel); + spdk_app_stop(0); +} + +static void +spdk_fuse_shutdown(void) +{ + spdk_fs_unload(g_fs, shutdown_cb, NULL); +} + +int main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc = 0; + + if (argc < 4) { + fprintf(stderr, "usage: %s <conffile> <bdev name> <mountpoint>\n", argv[0]); + exit(1); + } + + spdk_app_opts_init(&opts); + opts.name = "spdk_fuse"; + opts.config_file = argv[1]; + opts.reactor_mask = "0x3"; + opts.mem_size = 6144; + opts.shutdown_cb = spdk_fuse_shutdown; + + g_bdev_name = argv[2]; + g_mountpoint = argv[3]; + g_fuse_argc = argc - 2; + g_fuse_argv = &argv[2]; + + rc = spdk_app_start(&opts, spdk_fuse_run, NULL, NULL); + spdk_app_fini(); + + return rc; +} diff --git a/src/spdk/test/blobfs/mkfs/.gitignore b/src/spdk/test/blobfs/mkfs/.gitignore new file mode 100644 index 00000000..54e292c6 --- /dev/null +++ b/src/spdk/test/blobfs/mkfs/.gitignore @@ -0,0 +1 @@ +mkfs diff --git a/src/spdk/test/blobfs/mkfs/Makefile b/src/spdk/test/blobfs/mkfs/Makefile new file mode 100644 index 00000000..8367571e --- /dev/null +++ b/src/spdk/test/blobfs/mkfs/Makefile @@ -0,0 +1,59 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = mkfs + +C_SRCS := mkfs.c + +SPDK_LIB_LIST = event_bdev event_copy +SPDK_LIB_LIST += blobfs blob bdev blob_bdev copy event thread util conf trace \ + log jsonrpc json rpc + +LIBS += $(COPY_MODULES_LINKER_ARGS) $(BLOCKDEV_MODULES_LINKER_ARGS) $(SOCK_MODULES_LINKER_ARGS) +LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) + +all : $(APP) + @: + +$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(COPY_MODULES_FILES) $(BLOCKDEV_MODULES_FILES) $(SOCK_MODULES_FILES) $(ENV_LIBS) + $(LINK_C) + +clean : + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/blobfs/mkfs/mkfs.c b/src/spdk/test/blobfs/mkfs/mkfs.c new file mode 100644 index 00000000..0bedd84a --- /dev/null +++ b/src/spdk/test/blobfs/mkfs/mkfs.c @@ -0,0 +1,150 @@ +/*- + * 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/blobfs.h" +#include "spdk/bdev.h" +#include "spdk/event.h" +#include "spdk/blob_bdev.h" +#include "spdk/log.h" +#include "spdk/string.h" + +struct spdk_bs_dev *g_bs_dev; +const char *g_bdev_name; +static uint64_t g_cluster_size; + +static void +stop_cb(void *ctx, int fserrno) +{ + spdk_app_stop(0); +} + +static void +shutdown_cb(void *arg1, void *arg2) +{ + struct spdk_filesystem *fs = arg1; + + printf("done.\n"); + spdk_fs_unload(fs, stop_cb, NULL); +} + +static void +init_cb(void *ctx, struct spdk_filesystem *fs, int fserrno) +{ + struct spdk_event *event; + + event = spdk_event_allocate(0, shutdown_cb, fs, NULL); + spdk_event_call(event); +} + +static void +spdk_mkfs_run(void *arg1, void *arg2) +{ + struct spdk_bdev *bdev; + struct spdk_blobfs_opts blobfs_opt; + + bdev = spdk_bdev_get_by_name(g_bdev_name); + + if (bdev == NULL) { + SPDK_ERRLOG("bdev %s not found\n", g_bdev_name); + spdk_app_stop(-1); + return; + } + + printf("Initializing filesystem on bdev %s...", g_bdev_name); + fflush(stdout); + + spdk_fs_opts_init(&blobfs_opt); + if (g_cluster_size) { + blobfs_opt.cluster_sz = g_cluster_size; + } + g_bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL); + if (blobfs_opt.cluster_sz) { + spdk_fs_init(g_bs_dev, &blobfs_opt, NULL, init_cb, NULL); + } else { + spdk_fs_init(g_bs_dev, NULL, NULL, init_cb, NULL); + } +} + +static void +mkfs_usage(void) +{ + printf(" -C <size> cluster size\n"); +} + +static void +mkfs_parse_arg(int ch, char *arg) +{ + bool has_prefix; + + switch (ch) { + case 'C': + spdk_parse_capacity(arg, &g_cluster_size, &has_prefix); + break; + default: + break; + } + +} + +int main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc = 0; + + if (argc < 3) { + SPDK_ERRLOG("usage: %s <conffile> <bdevname>\n", argv[0]); + exit(1); + } + + spdk_app_opts_init(&opts); + opts.name = "spdk_mkfs"; + opts.config_file = argv[1]; + opts.reactor_mask = "0x3"; + opts.mem_size = 1024; + opts.shutdown_cb = NULL; + + spdk_fs_set_cache_size(512); + g_bdev_name = argv[2]; + if ((rc = spdk_app_parse_args(argc, argv, &opts, "C:", NULL, + mkfs_parse_arg, mkfs_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + exit(rc); + } + + rc = spdk_app_start(&opts, spdk_mkfs_run, NULL, NULL); + spdk_app_fini(); + + return rc; +} diff --git a/src/spdk/test/blobfs/rocksdb/.gitignore b/src/spdk/test/blobfs/rocksdb/.gitignore new file mode 100644 index 00000000..1a06816d --- /dev/null +++ b/src/spdk/test/blobfs/rocksdb/.gitignore @@ -0,0 +1 @@ +results diff --git a/src/spdk/test/blobfs/rocksdb/common_flags.txt b/src/spdk/test/blobfs/rocksdb/common_flags.txt new file mode 100644 index 00000000..6390c7a4 --- /dev/null +++ b/src/spdk/test/blobfs/rocksdb/common_flags.txt @@ -0,0 +1,27 @@ +--disable_seek_compaction=1 +--mmap_read=0 +--statistics=1 +--histogram=1 +--key_size=16 +--value_size=1000 +--block_size=4096 +--cache_size=0 +--bloom_bits=10 +--cache_numshardbits=4 +--open_files=500000 +--verify_checksum=1 +--db=/mnt/rocksdb +--sync=0 +--compression_type=none +--stats_interval=1000000 +--compression_ratio=1 +--disable_data_sync=0 +--target_file_size_base=67108864 +--max_write_buffer_number=3 +--max_bytes_for_level_multiplier=10 +--max_background_compactions=10 +--num_levels=10 +--delete_obsolete_files_period_micros=3000000 +--max_grandparent_overlap_factor=10 +--stats_per_interval=1 +--max_bytes_for_level_base=10485760 diff --git a/src/spdk/test/blobfs/rocksdb/postprocess.py b/src/spdk/test/blobfs/rocksdb/postprocess.py new file mode 100755 index 00000000..1ba8a730 --- /dev/null +++ b/src/spdk/test/blobfs/rocksdb/postprocess.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +from collections import namedtuple +from itertools import islice +import operator +import sys + +total_samples = 0 +thread_module_samples = {} +function_module_samples = {} +module_samples = {} +threads = set() + +ThreadModule = namedtuple('ThreadModule', ['thread', 'module']) +FunctionModule = namedtuple('FunctionModule', ['function', 'module']) + +with open(sys.argv[1] + "/" + sys.argv[2] + ".perf.txt") as f: + for line in f: + fields = line.split() + total_samples += int(fields[1]) + key = ThreadModule(fields[2], fields[3]) + thread_module_samples.setdefault(key, 0) + thread_module_samples[key] += int(fields[1]) + key = FunctionModule(fields[5], fields[3]) + function_module_samples.setdefault(key, 0) + function_module_samples[key] += int(fields[1]) + threads.add(fields[2]) + + key = fields[3] + module_samples.setdefault(key, 0) + module_samples[key] += int(fields[1]) + +for thread in sorted(threads): + thread_pct = 0 + print("") + print("Thread: {:s}".format(thread)) + print(" Percent Module") + print("============================") + for key, value in sorted(list(thread_module_samples.items()), key=operator.itemgetter(1), reverse=True): + if key.thread == thread: + print("{:8.4f} {:20s}".format(float(value) * 100 / total_samples, key.module)) + thread_pct += float(value) * 100 / total_samples + print("============================") + print("{:8.4f} Total".format(thread_pct)) + +print("") +print(" Percent Module Function") +print("=================================================================") +for key, value in islice(sorted(list(function_module_samples.items()), key=operator.itemgetter(1), reverse=True), 100): + print(("{:8.4f} {:20s} {:s}".format(float(value) * 100 / total_samples, key.module, key.function))) + +print("") +print("") +print(" Percent Module") +print("=================================") +for key, value in sorted(list(module_samples.items()), key=operator.itemgetter(1), reverse=True): + print("{:8.4f} {:s}".format(float(value) * 100 / total_samples, key)) + +print("") +with open(sys.argv[1] + "/" + sys.argv[2] + "_db_bench.txt") as f: + for line in f: + if "maxresident" in line: + fields = line.split() + print("Wall time elapsed: {:s}".format(fields[2].split("e")[0])) + print("CPU utilization: {:s}".format(fields[3].split('C')[0])) + user = float(fields[0].split('u')[0]) + system = float(fields[1].split('s')[0]) + print("User: {:8.2f} ({:5.2f}%)".format(user, user * 100 / (user + system))) + print("System: {:8.2f} ({:5.2f}%)".format(system, system * 100 / (user + system))) + +print("") diff --git a/src/spdk/test/blobfs/rocksdb/rocksdb.sh b/src/spdk/test/blobfs/rocksdb/rocksdb.sh new file mode 100755 index 00000000..1a73ee55 --- /dev/null +++ b/src/spdk/test/blobfs/rocksdb/rocksdb.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash + +set -ex + +run_step() { + if [ -z "$1" ]; then + echo run_step called with no parameter + exit 1 + fi + + echo "--spdk=$ROCKSDB_CONF" >> "$1"_flags.txt + echo "--spdk_bdev=Nvme0n1" >> "$1"_flags.txt + echo "--spdk_cache_size=$CACHE_SIZE" >> "$1"_flags.txt + + echo -n Start $1 test phase... + /usr/bin/time taskset 0xFF $DB_BENCH --flagfile="$1"_flags.txt &> "$1"_db_bench.txt + echo done. +} + +run_bsdump() { + $rootdir/examples/blob/cli/blobcli -c $ROCKSDB_CONF -b Nvme0n1 -D &> bsdump.txt +} + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh + +DB_BENCH_DIR=/usr/src/rocksdb +DB_BENCH=$DB_BENCH_DIR/db_bench +ROCKSDB_CONF=$testdir/rocksdb.conf + +if [ ! -e $DB_BENCH_DIR ]; then + echo $DB_BENCH_DIR does not exist, skipping rocksdb tests + exit 0 +fi + +timing_enter rocksdb + +timing_enter db_bench_build + +pushd $DB_BENCH_DIR +git clean -x -f -d +$MAKE db_bench $MAKEFLAGS $MAKECONFIG DEBUG_LEVEL=0 SPDK_DIR=$rootdir +popd + +timing_exit db_bench_build + +$rootdir/scripts/gen_nvme.sh > $ROCKSDB_CONF + +trap 'run_bsdump; rm -f $ROCKSDB_CONF; exit 1' SIGINT SIGTERM EXIT + +timing_enter mkfs +$rootdir/test/blobfs/mkfs/mkfs $ROCKSDB_CONF Nvme0n1 +timing_exit mkfs + +mkdir $output_dir/rocksdb +RESULTS_DIR=$output_dir/rocksdb +CACHE_SIZE=4096 +if [ $RUN_NIGHTLY_FAILING -eq 1 ]; then + DURATION=60 + NUM_KEYS=100000000 +else + DURATION=20 + NUM_KEYS=20000000 +fi + +cd $RESULTS_DIR +cp $testdir/common_flags.txt insert_flags.txt +echo "--benchmarks=fillseq" >> insert_flags.txt +echo "--threads=1" >> insert_flags.txt +echo "--disable_wal=1" >> insert_flags.txt +echo "--use_existing_db=0" >> insert_flags.txt +echo "--num=$NUM_KEYS" >> insert_flags.txt + +cp $testdir/common_flags.txt randread_flags.txt +echo "--benchmarks=readrandom" >> randread_flags.txt +echo "--threads=16" >> randread_flags.txt +echo "--duration=$DURATION" >> randread_flags.txt +echo "--disable_wal=1" >> randread_flags.txt +echo "--use_existing_db=1" >> randread_flags.txt +echo "--num=$NUM_KEYS" >> randread_flags.txt + +cp $testdir/common_flags.txt overwrite_flags.txt +echo "--benchmarks=overwrite" >> overwrite_flags.txt +echo "--threads=1" >> overwrite_flags.txt +echo "--duration=$DURATION" >> overwrite_flags.txt +echo "--disable_wal=1" >> overwrite_flags.txt +echo "--use_existing_db=1" >> overwrite_flags.txt +echo "--num=$NUM_KEYS" >> overwrite_flags.txt + +cp $testdir/common_flags.txt readwrite_flags.txt +echo "--benchmarks=readwhilewriting" >> readwrite_flags.txt +echo "--threads=4" >> readwrite_flags.txt +echo "--duration=$DURATION" >> readwrite_flags.txt +echo "--disable_wal=1" >> readwrite_flags.txt +echo "--use_existing_db=1" >> readwrite_flags.txt +echo "--num=$NUM_KEYS" >> readwrite_flags.txt + +cp $testdir/common_flags.txt writesync_flags.txt +echo "--benchmarks=overwrite" >> writesync_flags.txt +echo "--threads=1" >> writesync_flags.txt +echo "--duration=$DURATION" >> writesync_flags.txt +echo "--disable_wal=0" >> writesync_flags.txt +echo "--use_existing_db=1" >> writesync_flags.txt +echo "--sync=1" >> writesync_flags.txt +echo "--num=$NUM_KEYS" >> writesync_flags.txt + +timing_enter rocksdb_insert +run_step insert +timing_exit rocksdb_insert + +timing_enter rocksdb_overwrite +run_step overwrite +timing_exit rocksdb_overwrite + +timing_enter rocksdb_readwrite +run_step readwrite +timing_exit rocksdb_readwrite + +timing_enter rocksdb_writesync +run_step writesync +timing_exit rocksdb_writesync + +timing_enter rocksdb_randread +run_step randread +timing_exit rocksdb_randread + +trap - SIGINT SIGTERM EXIT + +run_bsdump +rm -f $ROCKSDB_CONF + +report_test_completion "blobfs" +timing_exit rocksdb diff --git a/src/spdk/test/blobfs/rocksdb/run_tests.sh b/src/spdk/test/blobfs/rocksdb/run_tests.sh new file mode 100755 index 00000000..d65d3b10 --- /dev/null +++ b/src/spdk/test/blobfs/rocksdb/run_tests.sh @@ -0,0 +1,197 @@ +#!/bin/bash +set -e + +if [ $# -eq 0 ] +then + echo "usage: $0 <location of db_bench>" + exit 1 +fi + +DB_BENCH=$(readlink -f $1) +[ -e $DB_BENCH ] || (echo "$DB_BENCH does not exist - needs to be built" && exit 1) + +hash mkfs.xfs +: ${USE_PERF:=1} +if ! hash perf; then + USE_PERF=0 +fi +hash python +[ -e /usr/include/gflags/gflags.h ] || (echo "gflags not installed." && exit 1) + +# Increase max number of file descriptors. This will be inherited +# by processes spawned from this script. +ulimit -n 16384 + +TESTDIR=$(readlink -f $(dirname $0)) + +if ls $TESTDIR/results/testrun_* &> /dev/null; then + mkdir -p $TESTDIR/results/old + mv $TESTDIR/results/testrun_* $TESTDIR/results/old +fi + +if [ -z "$RESULTS_DIR" ]; then + RESULTS_DIR=$TESTDIR/results/testrun_`date +%Y%m%d_%H%M%S` + mkdir -p $RESULTS_DIR + rm -f $TESTDIR/results/last + ln -s $RESULTS_DIR $TESTDIR/results/last +fi + +: ${CACHE_SIZE:=4096} +: ${DURATION:=120} +: ${NUM_KEYS:=500000000} +: ${ROCKSDB_CONF:=/usr/local/etc/spdk/rocksdb.conf} + +if [ "$NO_SPDK" = "1" ] +then + [ -e /dev/nvme0n1 ] || (echo "No /dev/nvme0n1 device node found." && exit 1) +else + [ -e /dev/nvme0n1 ] && (echo "/dev/nvme0n1 device found - need to run SPDK setup.sh script to bind to UIO." && exit 1) +fi + +cd $RESULTS_DIR + +SYSINFO_FILE=sysinfo.txt +COMMAND="hostname" +echo ">> $COMMAND : " >> $SYSINFO_FILE +$COMMAND >> $SYSINFO_FILE +echo >> $SYSINFO_FILE + +COMMAND="cat /proc/cpuinfo" +echo ">> $COMMAND : " >> $SYSINFO_FILE +$COMMAND >> $SYSINFO_FILE +echo >> $SYSINFO_FILE + +COMMAND="cat /proc/meminfo" +echo ">> $COMMAND : " >> $SYSINFO_FILE +$COMMAND >> $SYSINFO_FILE +echo >> $SYSINFO_FILE + +if [ "$NO_SPDK" = "1" ] +then + echo -n Creating and mounting XFS filesystem... + sudo mkdir -p /mnt/rocksdb + sudo umount /mnt/rocksdb || true &> /dev/null + sudo mkfs.xfs -d agcount=32 -l su=4096 -f /dev/nvme0n1 &> mkfs_xfs.txt + sudo mount -o discard /dev/nvme0n1 /mnt/rocksdb + sudo chown $USER /mnt/rocksdb + echo done. +fi + +cp $TESTDIR/common_flags.txt insert_flags.txt +echo "--benchmarks=fillseq" >> insert_flags.txt +echo "--threads=1" >> insert_flags.txt +echo "--disable_wal=1" >> insert_flags.txt +echo "--use_existing_db=0" >> insert_flags.txt +echo "--num=$NUM_KEYS" >> insert_flags.txt + +cp $TESTDIR/common_flags.txt randread_flags.txt +echo "--benchmarks=readrandom" >> randread_flags.txt +echo "--threads=16" >> randread_flags.txt +echo "--duration=$DURATION" >> randread_flags.txt +echo "--disable_wal=1" >> randread_flags.txt +echo "--use_existing_db=1" >> randread_flags.txt +echo "--num=$NUM_KEYS" >> randread_flags.txt + +cp $TESTDIR/common_flags.txt overwrite_flags.txt +echo "--benchmarks=overwrite" >> overwrite_flags.txt +echo "--threads=1" >> overwrite_flags.txt +echo "--duration=$DURATION" >> overwrite_flags.txt +echo "--disable_wal=1" >> overwrite_flags.txt +echo "--use_existing_db=1" >> overwrite_flags.txt +echo "--num=$NUM_KEYS" >> overwrite_flags.txt + +cp $TESTDIR/common_flags.txt readwrite_flags.txt +echo "--benchmarks=readwhilewriting" >> readwrite_flags.txt +echo "--threads=4" >> readwrite_flags.txt +echo "--duration=$DURATION" >> readwrite_flags.txt +echo "--disable_wal=1" >> readwrite_flags.txt +echo "--use_existing_db=1" >> readwrite_flags.txt +echo "--num=$NUM_KEYS" >> readwrite_flags.txt + +cp $TESTDIR/common_flags.txt writesync_flags.txt +echo "--benchmarks=overwrite" >> writesync_flags.txt +echo "--threads=1" >> writesync_flags.txt +echo "--duration=$DURATION" >> writesync_flags.txt +echo "--disable_wal=0" >> writesync_flags.txt +echo "--use_existing_db=1" >> writesync_flags.txt +echo "--sync=1" >> writesync_flags.txt +echo "--num=$NUM_KEYS" >> writesync_flags.txt + +run_step() { + if [ -z "$1" ] + then + echo run_step called with no parameter + exit 1 + fi + + if [ -z "$NO_SPDK" ] + then + echo "--spdk=$ROCKSDB_CONF" >> "$1"_flags.txt + echo "--spdk_bdev=Nvme0n1" >> "$1"_flags.txt + echo "--spdk_cache_size=$CACHE_SIZE" >> "$1"_flags.txt + fi + + if [ "$NO_SPDK" = "1" ] + then + echo "--bytes_per_sync=262144" >> "$1"_flags.txt + cat /sys/block/nvme0n1/stat > "$1"_blockdev_stats.txt + fi + + echo -n Start $1 test phase... + if [ "$USE_PERF" = "1" ] + then + sudo /usr/bin/time taskset 0xFF perf record $DB_BENCH --flagfile="$1"_flags.txt &> "$1"_db_bench.txt + else + sudo /usr/bin/time taskset 0xFF $DB_BENCH --flagfile="$1"_flags.txt &> "$1"_db_bench.txt + fi + echo done. + + if [ "$NO_SPDK" = "1" ] + then + drop_caches + cat /sys/block/nvme0n1/stat >> "$1"_blockdev_stats.txt + fi + + if [ "$USE_PERF" = "1" ] + then + echo -n Generating perf report for $1 test phase... + sudo perf report -f -n | sed '/#/d' | sed '/%/!d' | sort -r > $1.perf.txt + sudo rm perf.data + $TESTDIR/postprocess.py `pwd` $1 > $1_summary.txt + echo done. + fi +} + +drop_caches() { + echo -n Cleaning Page Cache... + echo 3 > /proc/sys/vm/drop_caches + echo done. +} + +if [ -z "$SKIP_INSERT" ] +then + run_step insert +fi +if [ -z "$SKIP_OVERWRITE" ] +then + run_step overwrite +fi +if [ -z "$SKIP_READWRITE" ] +then + run_step readwrite +fi +if [ -z "$SKIP_WRITESYNC" ] +then + run_step writesync +fi +if [ -z "$SKIP_RANDREAD" ] +then + run_step randread +fi + +if [ "$NO_SPDK" = "1" ] +then + echo -n Unmounting XFS filesystem... + sudo umount /mnt/rocksdb || true &> /dev/null + echo done. +fi diff --git a/src/spdk/test/blobfs/test_plan.md b/src/spdk/test/blobfs/test_plan.md new file mode 100644 index 00000000..080427b2 --- /dev/null +++ b/src/spdk/test/blobfs/test_plan.md @@ -0,0 +1,67 @@ +# SPDK BlobFS Test Plan + +## Current Tests + +# Unit tests (asynchronous API) + +- Tests BlobFS w/ Blobstore with no dependencies on SPDK bdev layer or event framework. + Uses simple DRAM buffer to simulate a block device - all block operations are immediately + completed so no special event handling is required. +- Current tests include: + - basic fs initialization and unload + - open non-existent file fails if SPDK_BLOBFS_OPEN_CREATE not specified + - open non-existent file creates the file if SPDK_BLOBFS_OPEN_CREATE is specified + - close a file fails if there are still open references + - closing a file with no open references fails + - files can be truncated up and down in length + - three-way rename + - operations for inserting and traversing buffers in a cache tree + - allocating and freeing I/O channels + +# Unit tests (synchronous API) + +- Tests BlobFS w/ Blobstore with no dependencies on SPDK bdev layer or event framework. + The synchronous API requires a separate thread to handle any asynchronous handoffs such as + I/O to disk. + - basic read/write I/O operations + - appending to a file whose cache has been flushed and evicted + +# RocksDB + +- Tests BlobFS as the backing store for a RocksDB database. BlobFS uses the SPDK NVMe driver + through the SPDK bdev layer as its block device. Uses RocksDB db_bench utility to drive + the workloads. Each workload (after the initial sequential insert) reloads the database + which validates metadata operations completed correctly in the previous run via the + RocksDB MANIFEST file. RocksDB also runs checksums on key/value blocks read from disk, + verifying data integrity. + - initialize BlobFS filesystem on NVMe SSD + - bulk sequential insert of up to 500M keys (16B key, 1000B value) + - overwrite test - randomly overwrite one of the keys in the database (driving both + flush and compaction traffic) + - readwrite test - one thread randomly overwrites a key in the database, up to 16 + threads randomly read a key in the database. + - writesync - same as overwrite, but enables a WAL (write-ahead log) + - randread - up to 16 threads randomly read a key in the database + +## Future tests to add + +# Unit tests + +- Corrupt data in DRAM buffer, and confirm subsequent operations such as BlobFS load or + opening a blob fail as expected (no panics, etc.) +- Test synchronous API with multiple synchronous threads. May be implemented separately + from existing synchronous unit tests to allow for more sophisticated thread + synchronization. +- Add tests for out of capacity (no more space on disk for additional blobs/files) +- Pending addition of BlobFS superblob, verify that BlobFS load fails with missing or + corrupt superblob +- Additional tests to reach 100% unit test coverage + +# System/integration tests + +- Use fio with BlobFS fuse module for more focused data integrity testing on individual + files. +- Pending directory support (via an SPDK btree module), use BlobFS fuse module to do + things like a Linux kernel compilation. Performance may be poor but this will heavily + stress the mechanics of BlobFS. +- Run RocksDB tests with varying amounts of BlobFS cache diff --git a/src/spdk/test/blobstore/blob_io_wait/blob_io_wait.sh b/src/spdk/test/blobstore/blob_io_wait/blob_io_wait.sh new file mode 100755 index 00000000..b44a3525 --- /dev/null +++ b/src/spdk/test/blobstore/blob_io_wait/blob_io_wait.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +SYSTEM=`uname -s` +if [ $SYSTEM = "FreeBSD" ] ; then + echo "blob_io_wait.sh cannot run on FreeBSD currently." + exit 0 +fi + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh +rpc_py="python $rootdir/scripts/rpc.py" +set -e + +timing_enter blob_bdev_io_wait + +truncate -s 64M $testdir/aio.bdev + +$rootdir/test/app/bdev_svc/bdev_svc & +bdev_svc_pid=$! + +trap "killprocess $bdev_svc_pid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $bdev_svc_pid +$rpc_py construct_aio_bdev $testdir/aio.bdev aio0 4096 +$rpc_py construct_lvol_store aio0 lvs0 +$rpc_py construct_lvol_bdev -l lvs0 lvol0 32 + +killprocess $bdev_svc_pid + +# Minimal number of bdev io pool (128) and cache (1) +echo "[Bdev]" > $testdir/bdevperf.conf +echo "BdevIoPoolSize 128" >> $testdir/bdevperf.conf +echo "BdevIoCacheSize 1" >> $testdir/bdevperf.conf +echo "[AIO]" >> $testdir/bdevperf.conf +echo "AIO $testdir/aio.bdev aio0 4096" >> $testdir/bdevperf.conf + +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w write -t 1 +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w read -t 1 +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w unmap -t 1 + +sync +rm -rf $testdir/bdevperf.conf +rm -rf $testdir/aio.bdev +trap - SIGINT SIGTERM EXIT + +report_test_completion "blob_io_wait" +timing_exit bdev_io_wait diff --git a/src/spdk/test/blobstore/blobstore.sh b/src/spdk/test/blobstore/blobstore.sh new file mode 100755 index 00000000..4ae413a1 --- /dev/null +++ b/src/spdk/test/blobstore/blobstore.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +SYSTEM=`uname -s` +if [ $SYSTEM = "FreeBSD" ] ; then + echo "blobstore.sh cannot run on FreeBSD currently." + exit 0 +fi + +set -xe + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) +source $rootdir/test/common/autotest_common.sh +timing_enter blobstore + +set -e + +# Nvme0 target configuration +$rootdir/scripts/gen_nvme.sh > $testdir/blobcli.conf + +# generate random data file for import/export diff +dd if=/dev/urandom of=$testdir/test.pattern bs=1M count=1 + +cd $testdir +$rootdir/examples/blob/cli/blobcli -c $testdir/blobcli.conf -b Nvme0n1 -T $testdir/test.bs > $testdir/btest.out +cd - + +# the tool leaves some trailing whitespaces that we need to strip out +sed -i 's/[[:space:]]*$//' $testdir/btest.out + +# the test script will import the test pattern generated by dd and then export +# it to a file so we can compare and confirm basic read and write +$rootdir/test/app/match/match -v $testdir/btest.out.match +diff $testdir/test.pattern $testdir/test.pattern.blob + +rm -rf $testdir/btest.out +rm -rf $testdir/blobcli.conf +rm -rf $testdir/*.blob +rm -rf $testdir/test.pattern + +timing_exit blobstore diff --git a/src/spdk/test/blobstore/btest.out.ignore b/src/spdk/test/blobstore/btest.out.ignore new file mode 100644 index 00000000..d7aa1120 --- /dev/null +++ b/src/spdk/test/blobstore/btest.out.ignore @@ -0,0 +1,5 @@ +DPDK +EAL +CRYPTODEV + +.... diff --git a/src/spdk/test/blobstore/btest.out.match b/src/spdk/test/blobstore/btest.out.match new file mode 100644 index 00000000..62e49287 --- /dev/null +++ b/src/spdk/test/blobstore/btest.out.match @@ -0,0 +1,130 @@ +Starting DPDK 17.11.0 initialization... +[ DPDK EAL parameters: blobcli -c 0x1 --file-prefix=spdk_pid5072 ] +EAL: Probing VFIO support... +EAL: PCI device 0000:00:04.0 on NUMA socket 0 +EAL: probe driver: 8086:2f20 spdk_ioat +EAL: PCI device 0000:00:04.1 on NUMA socket 0 +EAL: probe driver: 8086:2f21 spdk_ioat +EAL: PCI device 0000:00:04.2 on NUMA socket 0 +EAL: probe driver: 8086:2f22 spdk_ioat +EAL: PCI device 0000:00:04.3 on NUMA socket 0 +EAL: probe driver: 8086:2f23 spdk_ioat +EAL: PCI device 0000:00:04.4 on NUMA socket 0 +EAL: probe driver: 8086:2f24 spdk_ioat +EAL: PCI device 0000:00:04.5 on NUMA socket 0 +EAL: probe driver: 8086:2f25 spdk_ioat +EAL: PCI device 0000:00:04.6 on NUMA socket 0 +EAL: probe driver: 8086:2f26 spdk_ioat +EAL: PCI device 0000:00:04.7 on NUMA socket 0 +CRYPTODEV: [crypto_aesni_mb] - Creating cryptodev crypto_aesni_mb + +CRYPTODEV: [crypto_aesni_mb] - Initialisation parameters - name: crypto_aesni_mb,socket id: 0, max queue pairs: 8 +$(OPT)cryptodev_aesni_mb_create() line 976: IPSec Multi-buffer library version used: 0.49.0 + +EAL: probe driver: 8086:2f27 spdk_ioat +EAL: PCI device 0000:80:04.0 on NUMA socket 1 +EAL: probe driver: 8086:2f20 spdk_ioat +EAL: PCI device 0000:80:04.1 on NUMA socket 1 +EAL: probe driver: 8086:2f21 spdk_ioat +EAL: PCI device 0000:80:04.2 on NUMA socket 1 +EAL: probe driver: 8086:2f22 spdk_ioat +EAL: PCI device 0000:80:04.3 on NUMA socket 1 +EAL: probe driver: 8086:2f23 spdk_ioat +EAL: PCI device 0000:80:04.4 on NUMA socket 1 +EAL: probe driver: 8086:2f24 spdk_ioat +EAL: PCI device 0000:80:04.5 on NUMA socket 1 +EAL: probe driver: 8086:2f25 spdk_ioat +EAL: PCI device 0000:80:04.6 on NUMA socket 1 +EAL: probe driver: 8086:2f26 spdk_ioat +EAL: PCI device 0000:80:04.7 on NUMA socket 1 +EAL: probe driver: 8086:2f27 spdk_ioat +EAL: PCI device 0000:06:00.0 on NUMA socket 0 +EAL: probe driver: 8086:953 spdk_nvme + +SCRIPT NOW PROCESSING: -i +Init blobstore using bdev Product Name: NVMe disk +blobstore init'd: ($(XX)) + +SCRIPT NOW PROCESSING: -l bdevs + +List bdevs: + bdev Name: Nvme0n1 + bdev Product Name: NVMe disk + + +SCRIPT NOW PROCESSING: -n 1 +New blob id 4294967296 +blob now has USED clusters of 1 + +SCRIPT NOW PROCESSING: -p $B0 +Super Blob ID has been set. + +SCRIPT NOW PROCESSING: -n 1 +New blob id 4294967297 +blob now has USED clusters of 1 + +SCRIPT NOW PROCESSING: -m $B1 test.pattern +Working............................................................................................................................................................................................................................................................... +Blob import complete (from test.pattern). + +SCRIPT NOW PROCESSING: -d $B1 test.pattern.blob +Working................................................................................................................................................................................................................................................................ +File write complete (to test.pattern.blob). + +SCRIPT NOW PROCESSING: -x $B1 key val +Xattr has been set. + +SCRIPT NOW PROCESSING: -s bs +Blobstore Public Info: + Using bdev Product Name: NVMe disk + API Version: $(N) + super blob ID: 4294967296 + page size: $(N) + io unit size: $(N) + cluster size: 1048576 + # free clusters: $(N) + blobstore type: +00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + +Blobstore Private Info: + Metadata start (pages): $(N) + Metadata length (pages): $(N) + +SCRIPT NOW PROCESSING: -s $B1 +Blob Public Info: +blob ID: 4294967297 +# of clusters: 1 +# of bytes: 1048576 +# of pages: 256 +# of xattrs: 1 +xattrs: + +(0) Name:key +(0) Value: + +00000000 76 61 6c 00 val. + +Blob Private Info: +state: CLEAN +open ref count: 1 + +SCRIPT NOW PROCESSING: -r $B1 key +Xattr has been removed. + +SCRIPT NOW PROCESSING: -s bs +Blobstore Public Info: + Using bdev Product Name: NVMe disk + API Version: 3 + super blob ID: 4294967296 + page size: $(N) + io unit size: $(N) + cluster size: 1048576 + # free clusters: $(N) + blobstore type: +00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + +Blobstore Private Info: + Metadata start (pages): $(N) + Metadata length (pages): $(N) + +SCRIPT NOW PROCESSING: -X diff --git a/src/spdk/test/blobstore/test.bs b/src/spdk/test/blobstore/test.bs new file mode 100644 index 00000000..dcc64861 --- /dev/null +++ b/src/spdk/test/blobstore/test.bs @@ -0,0 +1,12 @@ +-i +-l bdevs +-n 1 +-p $B0 +-n 1 +-m $B1 test.pattern +-d $B1 test.pattern.blob +-x $B1 key val +-s bs +-s $B1 +-r $B1 key +-s bs diff --git a/src/spdk/test/common/autotest_common.sh b/src/spdk/test/common/autotest_common.sh new file mode 100644 index 00000000..86511ccf --- /dev/null +++ b/src/spdk/test/common/autotest_common.sh @@ -0,0 +1,706 @@ +: ${SPDK_AUTOTEST_X=true}; export SPDK_AUTOTEST_X + +if $SPDK_AUTOTEST_X; then + set -x +fi + +set -e + +# Export flag to skip the known bug that exists in librados +# Bug is reported on ceph bug tracker with number 24078 +export ASAN_OPTIONS=new_delete_type_mismatch=0 + +PS4=' \t \$ ' +ulimit -c unlimited + +: ${RUN_NIGHTLY:=0} +export RUN_NIGHTLY + +: ${RUN_NIGHTLY_FAILING:=0} +export RUN_NIGHTLY_FAILING + +if [[ ! -z $1 ]]; then + if [ -f $1 ]; then + source $1 + fi +fi + +# If certain utilities are not installed, preemptively disable the tests +if ! hash ceph; then + SPDK_TEST_RBD=0 +fi + +if ! hash pmempool; then + SPDK_TEST_PMDK=0 +fi + +# Set defaults for missing test config options +: ${SPDK_BUILD_DOC=1}; export SPDK_BUILD_DOC +: ${SPDK_BUILD_SHARED_OBJECT=1}; export SPDK_BUILD_SHARED_OBJECT +: ${SPDK_RUN_CHECK_FORMAT=1}; export SPDK_RUN_CHECK_FORMAT +: ${SPDK_RUN_SCANBUILD=1}; export SPDK_RUN_SCANBUILD +: ${SPDK_RUN_VALGRIND=1}; export SPDK_RUN_VALGRIND +: ${SPDK_TEST_UNITTEST=1}; export SPDK_TEST_UNITTEST +: ${SPDK_TEST_ISCSI=1}; export SPDK_TEST_ISCSI +: ${SPDK_TEST_ISCSI_INITIATOR=1}; export SPDK_TEST_ISCSI_INITIATOR +: ${SPDK_TEST_NVME=1}; export SPDK_TEST_NVME +: ${SPDK_TEST_NVME_CLI=1}; export SPDK_TEST_NVME_CLI +: ${SPDK_TEST_NVMF=1}; export SPDK_TEST_NVMF +: ${SPDK_TEST_RBD=1}; export SPDK_TEST_RBD +: ${SPDK_TEST_VHOST=1}; export SPDK_TEST_VHOST +: ${SPDK_TEST_BLOCKDEV=1}; export SPDK_TEST_BLOCKDEV +: ${SPDK_TEST_IOAT=1}; export SPDK_TEST_IOAT +: ${SPDK_TEST_EVENT=1}; export SPDK_TEST_EVENT +: ${SPDK_TEST_BLOBFS=1}; export SPDK_TEST_BLOBFS +: ${SPDK_TEST_VHOST_INIT=1}; export SPDK_TEST_VHOST_INIT +: ${SPDK_TEST_PMDK=1}; export SPDK_TEST_PMDK +: ${SPDK_TEST_LVOL=1}; export SPDK_TEST_LVOL +: ${SPDK_TEST_JSON=1}; export SPDK_TEST_JSON +: ${SPDK_RUN_ASAN=1}; export SPDK_RUN_ASAN +: ${SPDK_RUN_UBSAN=1}; export SPDK_RUN_UBSAN +: ${SPDK_RUN_INSTALLED_DPDK=1}; export SPDK_RUN_INSTALLED_DPDK +: ${SPDK_TEST_CRYPTO=1}; export SPDK_TEST_CRYPTO + +if [ -z "$DEPENDENCY_DIR" ]; then + export DEPENDENCY_DIR=/home/sys_sgsw +else + export DEPENDENCY_DIR +fi + +if [ ! -z "$HUGEMEM" ]; then + export HUGEMEM +fi + +# pass our valgrind desire on to unittest.sh +if [ $SPDK_RUN_VALGRIND -eq 0 ]; then + export valgrind='' +fi + +config_params='--enable-debug --enable-werror' + +if echo -e "#include <libunwind.h>\nint main(int argc, char *argv[]) {return 0;}\n" | \ + gcc -o /dev/null -lunwind -x c - 2>/dev/null; then + config_params+=' --enable-log-bt' +fi + +if [ $SPDK_TEST_CRYPTO -eq 1 ]; then + config_params+=' --with-crypto' +fi + +export UBSAN_OPTIONS='halt_on_error=1:print_stacktrace=1:abort_on_error=1' + +# On Linux systems, override the default HUGEMEM in scripts/setup.sh to +# allocate 8GB in hugepages. +# FreeBSD runs a much more limited set of tests, so keep the default 2GB. +if [ `uname -s` = "Linux" ]; then + export HUGEMEM=8192 +fi + +DEFAULT_RPC_ADDR=/var/tmp/spdk.sock + +case `uname` in + FreeBSD) + DPDK_FREEBSD_DIR=/usr/local/share/dpdk/x86_64-native-bsdapp-clang + if [ -d $DPDK_FREEBSD_DIR ] && [ $SPDK_RUN_INSTALLED_DPDK -eq 1 ]; then + WITH_DPDK_DIR=$DPDK_FREEBSD_DIR + fi + MAKE=gmake + MAKEFLAGS=${MAKEFLAGS:--j$(sysctl -a | egrep -i 'hw.ncpu' | awk '{print $2}')} + SPDK_RUN_ASAN=0 + SPDK_RUN_UBSAN=0 + ;; + Linux) + DPDK_LINUX_DIR=/usr/share/dpdk/x86_64-default-linuxapp-gcc + if [ -d $DPDK_LINUX_DIR ] && [ $SPDK_RUN_INSTALLED_DPDK -eq 1 ]; then + WITH_DPDK_DIR=$DPDK_LINUX_DIR + fi + MAKE=make + MAKEFLAGS=${MAKEFLAGS:--j$(nproc)} + config_params+=' --enable-coverage' + if [ $SPDK_RUN_UBSAN -eq 1 ]; then + config_params+=' --enable-ubsan' + fi + if [ $SPDK_RUN_ASAN -eq 1 ]; then + if ldconfig -p | grep -q asan; then + config_params+=' --enable-asan' + else + SPDK_RUN_ASAN=0 + fi + fi + ;; + *) + echo "Unknown OS in $0" + exit 1 + ;; +esac + +# By default, --with-dpdk is not set meaning the SPDK build will use the DPDK submodule. +# If a DPDK installation is found in a well-known location though, WITH_DPDK_DIR will be +# set which will override the default and use that DPDK installation instead. +if [ ! -z "$WITH_DPDK_DIR" ]; then + config_params+=" --with-dpdk=$WITH_DPDK_DIR" +fi + +if [ -f /usr/include/infiniband/verbs.h ]; then + config_params+=' --with-rdma' +fi + +if [ -f /usr/include/libpmemblk.h ]; then + config_params+=' --with-pmdk' +else + # PMDK not installed so disable PMDK tests explicitly here + SPDK_TEST_PMDK=0; export SPDK_TEST_PMDK +fi + +if [ -d /usr/src/fio ]; then + config_params+=' --with-fio=/usr/src/fio' +fi + +if [ -d ${DEPENDENCY_DIR}/vtune_codes ]; then + config_params+=' --with-vtune='${DEPENDENCY_DIR}'/vtune_codes' +fi + +if [ -d /usr/include/rbd ] && [ -d /usr/include/rados ]; then + config_params+=' --with-rbd' +fi + +if [ -d /usr/include/iscsi ]; then + libiscsi_version=`grep LIBISCSI_API_VERSION /usr/include/iscsi/iscsi.h | head -1 | awk '{print $3}' | awk -F '(' '{print $2}' | awk -F ')' '{print $1}'` + if [ $libiscsi_version -ge 20150621 ]; then + config_params+=' --with-iscsi-initiator' + else + export SPDK_TEST_ISCSI_INITIATOR=0 + fi +else + export SPDK_TEST_ISCSI_INITIATOR=0 +fi + +if [ ! -d "${DEPENDENCY_DIR}/nvme-cli" ]; then + export SPDK_TEST_NVME_CLI=0 +fi + +export config_params + +if [ -z "$output_dir" ]; then + if [ -z "$rootdir" ] || [ ! -d "$rootdir/../output" ]; then + output_dir=. + else + output_dir=$rootdir/../output + fi + export output_dir +fi + +function timing() { + direction="$1" + testname="$2" + + now=$(date +%s) + + if [ "$direction" = "enter" ]; then + export timing_stack="${timing_stack};${now}" + export test_stack="${test_stack};${testname}" + else + child_time=$(grep "^${test_stack:1};" $output_dir/timing.txt | awk '{s+=$2} END {print s}') + + start_time=$(echo "$timing_stack" | sed -e 's@^.*;@@') + timing_stack=$(echo "$timing_stack" | sed -e 's@;[^;]*$@@') + + elapsed=$((now - start_time - child_time)) + echo "${test_stack:1} $elapsed" >> $output_dir/timing.txt + + test_stack=$(echo "$test_stack" | sed -e 's@;[^;]*$@@') + fi +} + +function timing_enter() { + set +x + timing "enter" "$1" + set -x +} + +function timing_exit() { + set +x + timing "exit" "$1" + set -x +} + +function timing_finish() { + flamegraph='/usr/local/FlameGraph/flamegraph.pl' + if [ -x "$flamegraph" ]; then + "$flamegraph" \ + --title 'Build Timing' \ + --nametype 'Step:' \ + --countname seconds \ + $output_dir/timing.txt \ + >$output_dir/timing.svg + fi +} + +function create_test_list() { + grep -rsh --exclude="autotest_common.sh" --exclude="$rootdir/test/common/autotest_common.sh" -e "report_test_completion" $rootdir | sed 's/report_test_completion//g; s/[[:blank:]]//g; s/"//g;' > $output_dir/all_tests.txt || true +} + +function report_test_completion() { + echo "$1" >> $output_dir/test_completions.txt +} + +function process_core() { + ret=0 + for core in $(find . -type f \( -name 'core*' -o -name '*.core' \)); do + exe=$(eu-readelf -n "$core" | grep psargs | sed "s/.*psargs: \([^ \'\" ]*\).*/\1/") + if [[ ! -f "$exe" ]]; then + exe=$(eu-readelf -n "$core" | grep -oP -m1 "$exe.+") + fi + echo "exe for $core is $exe" + if [[ ! -z "$exe" ]]; then + if hash gdb; then + gdb -batch -ex "thread apply all bt full" $exe $core + fi + cp $exe $output_dir + fi + mv $core $output_dir + chmod a+r $output_dir/$core + ret=1 + done + return $ret +} + +function process_shm() { + type=$1 + id=$2 + if [ "$type" = "--pid" ]; then + id="pid${id}" + elif [ "$type" = "--id" ]; then + id="${id}" + else + echo "Please specify to search for pid or shared memory id." + return 1 + fi + + shm_files=$(find /dev/shm -name "*.${id}" -printf "%f\n") + + if [[ -z $shm_files ]]; then + echo "SHM File for specified PID or shared memory id: ${id} not found!" + return 1 + fi + for n in $shm_files; do + tar -C /dev/shm/ -cvzf $output_dir/${n}_shm.tar.gz ${n} + done + return 0 +} + +function waitforlisten() { + # $1 = process pid + if [ -z "$1" ]; then + exit 1 + fi + + rpc_addr="${2:-$DEFAULT_RPC_ADDR}" + + echo "Waiting for process to start up and listen on UNIX domain socket $rpc_addr..." + # turn off trace for this loop + set +x + ret=1 + while [ $ret -ne 0 ]; do + # if the process is no longer running, then exit the script + # since it means the application crashed + if ! kill -s 0 $1; then + exit 1 + fi + + namespace=$(ip netns identify $1) + if [ -n "$namespace" ]; then + ns_cmd="ip netns exec $namespace" + fi + + if hash ss; then + if $ns_cmd ss -lx | grep -q $rpc_addr; then + ret=0 + fi + else + # if system doesn't have ss, just assume it has netstat + if $ns_cmd netstat -an -x | grep -iw LISTENING | grep -q $rpc_addr; then + ret=0 + fi + fi + done + set -x +} + +function waitfornbd() { + nbd_name=$1 + + for ((i=1; i<=20; i++)); do + if grep -q -w $nbd_name /proc/partitions; then + break + else + sleep 0.1 + fi + done + + # The nbd device is now recognized as a block device, but there can be + # a small delay before we can start I/O to that block device. So loop + # here trying to read the first block of the nbd block device to a temp + # file. Note that dd returns success when reading an empty file, so we + # need to check the size of the output file instead. + for ((i=1; i<=20; i++)); do + dd if=/dev/$nbd_name of=/tmp/nbdtest bs=4096 count=1 iflag=direct + size=`stat -c %s /tmp/nbdtest` + rm -f /tmp/nbdtest + if [ "$size" != "0" ]; then + return 0 + else + sleep 0.1 + fi + done + + return 1 +} + +function killprocess() { + # $1 = process pid + if [ -z "$1" ]; then + exit 1 + fi + + if kill -0 $1; then + echo "killing process with pid $1" + kill $1 + wait $1 + fi +} + +function iscsicleanup() { + echo "Cleaning up iSCSI connection" + iscsiadm -m node --logout || true + iscsiadm -m node -o delete || true +} + +function stop_iscsi_service() { + if cat /etc/*-release | grep Ubuntu; then + service open-iscsi stop + else + service iscsid stop + fi +} + +function start_iscsi_service() { + if cat /etc/*-release | grep Ubuntu; then + service open-iscsi start + else + service iscsid start + fi +} + +function rbd_setup() { + # $1 = monitor ip address + # $2 = name of the namespace + if [ -z "$1" ]; then + echo "No monitor IP address provided for ceph" + exit 1 + fi + if [ -n "$2" ]; then + if ip netns list | grep "$2"; then + NS_CMD="ip netns exec $2" + else + echo "No namespace $2 exists" + exit 1 + fi + fi + + if hash ceph; then + export PG_NUM=128 + export RBD_POOL=rbd + export RBD_NAME=foo + $NS_CMD $rootdir/scripts/ceph/start.sh $1 + + $NS_CMD ceph osd pool create $RBD_POOL $PG_NUM || true + $NS_CMD rbd create $RBD_NAME --size 1000 + fi +} + +function rbd_cleanup() { + if hash ceph; then + $rootdir/scripts/ceph/stop.sh || true + fi +} + +function start_stub() { + # Disable ASLR for multi-process testing. SPDK does support using DPDK multi-process, + # but ASLR can still be unreliable in some cases. + # We will reenable it again after multi-process testing is complete in kill_stub() + echo 0 > /proc/sys/kernel/randomize_va_space + $rootdir/test/app/stub/stub $1 & + stubpid=$! + echo Waiting for stub to ready for secondary processes... + while ! [ -e /var/run/spdk_stub0 ]; do + sleep 1s + done + # dump process memory map contents to help debug random ASLR failures + pmap -pX $stubpid || pmap -x $stubpid || true + echo done. +} + +function kill_stub() { + kill $stubpid + wait $stubpid + rm -f /var/run/spdk_stub0 + # Re-enable ASLR now that we are done with multi-process testing + # Note: "1" enables ASLR w/o randomizing data segments, "2" adds data segment + # randomizing and is the default on all recent Linux kernels + echo 2 > /proc/sys/kernel/randomize_va_space +} + +function run_test() { + set +x + local test_type="$(echo $1 | tr 'a-z' 'A-Z')" + shift + echo "************************************" + echo "START TEST $test_type $@" + echo "************************************" + set -x + time "$@" + set +x + echo "************************************" + echo "END TEST $test_type $@" + echo "************************************" + set -x +} + +function print_backtrace() { + # if errexit is not enabled, don't print a backtrace + [[ "$-" =~ e ]] || return 0 + + local shell_options="$-" + set +x + echo "========== Backtrace start: ==========" + echo "" + for i in $(seq 1 $((${#FUNCNAME[@]} - 1))); do + local func="${FUNCNAME[$i]}" + local line_nr="${BASH_LINENO[$((i - 1))]}" + local src="${BASH_SOURCE[$i]}" + echo "in $src:$line_nr -> $func()" + echo " ..." + nl -w 4 -ba -nln $src | grep -B 5 -A 5 "^$line_nr[^0-9]" | \ + sed "s/^/ /g" | sed "s/^ $line_nr /=> $line_nr /g" + echo " ..." + done + echo "" + echo "========== Backtrace end ==========" + [[ "$shell_options" =~ x ]] && set -x + return 0 +} + +function part_dev_by_gpt () { + if [ $(uname -s) = Linux ] && hash sgdisk && modprobe nbd; then + conf=$1 + devname=$2 + rootdir=$3 + operation=$4 + local nbd_path=/dev/nbd0 + local rpc_server=/var/tmp/spdk-gpt-bdevs.sock + + if [ ! -e $conf ]; then + return 1 + fi + + if [ -z "$operation" ]; then + operation="create" + fi + + cp $conf ${conf}.gpt + echo "[Gpt]" >> ${conf}.gpt + echo " Disable Yes" >> ${conf}.gpt + + $rootdir/test/app/bdev_svc/bdev_svc -r $rpc_server -i 0 -c ${conf}.gpt & + nbd_pid=$! + echo "Process nbd pid: $nbd_pid" + waitforlisten $nbd_pid $rpc_server + + # Start bdev as a nbd device + $rootdir/scripts/rpc.py -s "$rpc_server" start_nbd_disk $devname $nbd_path + + waitfornbd ${nbd_path:5} + + if [ "$operation" = create ]; then + parted -s $nbd_path mklabel gpt mkpart first '0%' '50%' mkpart second '50%' '100%' + + # change the GUID to SPDK GUID value + SPDK_GPT_GUID=`grep SPDK_GPT_PART_TYPE_GUID $rootdir/lib/bdev/gpt/gpt.h \ + | awk -F "(" '{ print $2}' | sed 's/)//g' \ + | awk -F ", " '{ print $1 "-" $2 "-" $3 "-" $4 "-" $5}' | sed 's/0x//g'` + sgdisk -t 1:$SPDK_GPT_GUID $nbd_path + sgdisk -t 2:$SPDK_GPT_GUID $nbd_path + elif [ "$operation" = reset ]; then + # clear the partition table + dd if=/dev/zero of=$nbd_path bs=4096 count=8 oflag=direct + fi + + $rootdir/scripts/rpc.py -s "$rpc_server" stop_nbd_disk $nbd_path + + killprocess $nbd_pid + rm -f ${conf}.gpt + fi + + return 0 +} + +function discover_bdevs() +{ + local rootdir=$1 + local config_file=$2 + local rpc_server=/var/tmp/spdk-discover-bdevs.sock + + if [ ! -e $config_file ]; then + echo "Invalid Configuration File: $config_file" + return -1 + fi + + # Start the bdev service to query for the list of available + # bdevs. + $rootdir/test/app/bdev_svc/bdev_svc -r $rpc_server -i 0 \ + -c $config_file &>/dev/null & + stubpid=$! + while ! [ -e /var/run/spdk_bdev0 ]; do + sleep 1 + done + + # Get all of the bdevs + if [ -z "$rpc_server" ]; then + $rootdir/scripts/rpc.py get_bdevs + else + $rootdir/scripts/rpc.py -s "$rpc_server" get_bdevs + fi + + # Shut down the bdev service + kill $stubpid + wait $stubpid + rm -f /var/run/spdk_bdev0 +} + +function waitforblk() +{ + local i=0 + while ! lsblk -l -o NAME | grep -q -w $1; do + [ $i -lt 15 ] || break + i=$[$i+1] + sleep 1 + done + + if ! lsblk -l -o NAME | grep -q -w $1; then + return 1 + fi + + return 0 +} + +function fio_config_gen() +{ + local config_file=$1 + local workload=$2 + + if [ -e "$config_file" ]; then + echo "Configuration File Already Exists!: $config_file" + return -1 + fi + + if [ -z "$workload" ]; then + workload=randrw + fi + + touch $1 + + cat > $1 << EOL +[global] +thread=1 +group_reporting=1 +direct=1 +norandommap=1 +percentile_list=50:99:99.9:99.99:99.999 +time_based=1 +ramp_time=0 +EOL + + if [ "$workload" == "verify" ]; then + echo "verify=sha1" >> $config_file + echo "rw=randwrite" >> $config_file + elif [ "$workload" == "trim" ]; then + echo "rw=trimwrite" >> $config_file + else + echo "rw=$workload" >> $config_file + fi +} + +function fio_config_add_job() +{ + config_file=$1 + filename=$2 + + if [ ! -e "$config_file" ]; then + echo "Configuration File Doesn't Exist: $config_file" + return -1 + fi + + if [ -z "$filename" ]; then + echo "No filename provided" + return -1 + fi + + echo "[job_$filename]" >> $config_file + echo "filename=$filename" >> $config_file +} + +function get_lvs_free_mb() +{ + local lvs_uuid=$1 + local lvs_info=$($rpc_py get_lvol_stores) + local fc=$(jq ".[] | select(.uuid==\"$lvs_uuid\") .free_clusters" <<< "$lvs_info") + local cs=$(jq ".[] | select(.uuid==\"$lvs_uuid\") .cluster_size" <<< "$lvs_info") + + # Change to MB's + free_mb=$((fc*cs/1024/1024)) + echo "$free_mb" +} + +function get_bdev_size() +{ + local bdev_name=$1 + local bdev_info=$($rpc_py get_bdevs -b $bdev_name) + local bs=$(jq ".[] .block_size" <<< "$bdev_info") + local nb=$(jq ".[] .num_blocks" <<< "$bdev_info") + + # Change to MB's + bdev_size=$((bs*nb/1024/1024)) + echo "$bdev_size" +} + +function autotest_cleanup() +{ + $rootdir/scripts/setup.sh reset + $rootdir/scripts/setup.sh cleanup + if [ $(uname -s) = "Linux" ]; then + if grep -q '#define SPDK_CONFIG_IGB_UIO_DRIVER 1' $rootdir/include/spdk/config.h; then + rmmod igb_uio + else + modprobe -r uio_pci_generic + fi + fi +} + +function freebsd_update_contigmem_mod() +{ + if [ `uname` = FreeBSD ]; then + kldunload contigmem.ko || true + if [ ! -z "$WITH_DPDK_DIR" ]; then + echo "Warning: SPDK only works on FreeBSD with patches that only exist in SPDK's dpdk submodule" + cp -f "$WITH_DPDK_DIR/kmod/contigmem.ko" /boot/modules/ + cp -f "$WITH_DPDK_DIR/kmod/contigmem.ko" /boot/kernel/ + else + cp -f "$rootdir/dpdk/build/kmod/contigmem.ko" /boot/modules/ + cp -f "$rootdir/dpdk/build/kmod/contigmem.ko" /boot/kernel/ + fi + fi +} + +set -o errtrace +trap "trap - ERR; print_backtrace >&2" ERR diff --git a/src/spdk/test/common/config/README.md b/src/spdk/test/common/config/README.md new file mode 100644 index 00000000..609f9de8 --- /dev/null +++ b/src/spdk/test/common/config/README.md @@ -0,0 +1,99 @@ +# Virtual Test Configuration + +This readme and the associated bash script, vm_setup.sh, are intended to assist developers in quickly +preparing a virtual test environment on which to run the SPDK validation tests rooted at autorun.sh. +This file contains basic information about SPDK environment requirements, an introduction to the +autorun-spdk.conf files used to moderate which tests are run by autorun.sh, and step-by-step instructions +for spinning up a VM capable of running the SPDK test suite. +There is no need for external hardware to run these tests. The linux kernel comes with the drivers necessary +to emulate an RDMA enabled NIC. NVMe controllers can also be virtualized in emulators such as QEMU. + + +## VM Envronment Requirements (Host): +- 8 GiB of RAM (for DPDK) +- Enable intel_kvm on the host machine from the bios. +- Enable nesting for VMs in kernel command line (for vhost tests). + - In `/etc/default/grub` append the following to the GRUB_CMDLINE_LINUX line: intel_iommu=on kvm-intel.nested=1. + +## VM Specs +When creating the user during the fedora installation, it is best to use the name sys_sgsw. Efforts are being made +to remove all references to this user, or files specific to this user from the codebase, but there are still some +trailing references to it. + +## Autorun-spdk.conf +Every machine that runs the autotest scripts should include a file titled autorun-spdk.conf in the home directory +of the user that will run them. This file consists of several lines of the form 'variable_name=0/1'. autorun.sh sources +this file each time it is run, and determines which tests to attempt based on which variables are defined in the +configuration file. For a full list of the variable declarations available for autorun-spdk.conf, please see +`test/common/autotest_common.sh` starting at line 13. + +## Steps for Configuring the VM +1. Download a fresh Fedora 26 image. +2. Perform the installation of Fedora 26 server. +3. Create an admin user sys_sgsw (enabling passwordless sudo for this account will make life easier during the tests). +4. Run the vm_setup.sh script which will install all proper dependencies. +5. Modify the autorun-spdk.conf file in the home directory. +6. Reboot the VM. +7. Run autorun.sh for SPDK. Any output files will be placed in `~/spdk_repo/output/`. + +## Additional Steps for Preparing the Vhost Tests +The Vhost tests also require the creation of a second virtual machine nested inside of the test VM. +Please follow the directions below to complete that installation. Note that host refers to the Fedora VM +created above and guest or VM refer to the Ubuntu VM created in this section. + +1. Follow instructions from spdk/scripts/vagrant/README.md + - install all needed packages mentioned in "Mac OSX Setup" or "Windows 10 Setup" sections + - follow steps from "Configure Vagrant" section + +2. Use Vagrant scripts located in spdk/scripts/vagrant to automatically generate + VM image to use in SPDK vhost tests. + Example command: + ~~~{.sh} + spdk/scripts/vagrant/create_vhost_vm.sh --move-to-def-dirs ubuntu16 + ~~~ + This command will: + - Download a Ubuntu 16.04 image file + - upgrade the system and install needed dependencies (fio, sg3-utils, bc) + - add entry to VM's ~/.ssh/autorized_keys + - add appropriate options to GRUB command line and update grub + - convert the image to .qcow2 format + - move .qcow2 file and ssh keys to default locations used by vhost test scripts + +Alternatively it is possible to create the VM image manually using following steps: +1. Create an image file for the VM. It does not have to be large, about 3.5G should suffice. +2. Create an ssh keypair for host-guest communications (performed on the host): + - Generate an ssh keypair with the name spdk_vhost_id_rsa and save it in `/root/.ssh`. + - Make sure that only root has read access to the private key. +3. Install the OS in the VM image (performed on guest): + - Use the latest Ubuntu server (Currently 16.04 LTS). + - When partitioning the disk, make one partion that consumes the whole disk mounted at /. Do not encrypt the disk or enable LVM. + - Choose the OpenSSH server packages during install. +4. Post installation configuration (performed on guest): + - Run the following commands to enable all necessary dependencies: + ~~~{.sh} + sudo apt update + sudo apt upgrade + sudo apt install fio sg3-utils bc + ~~~ + - Enable the root user: "sudo passwd root -> root". + - Enable root login over ssh: vim `/etc/ssh/sshd_config` -> PermitRootLogin=yes. + - Disable DNS for ssh: `/etc/ssh/sshd_config` -> UseDNS=no. + - Add the spdk_vhost key to root's known hosts: `/root/.ssh/authorized_keys` -> add spdk_vhost_id_rsa.pub key to authorized keys. + Remember to save the private key in `~/.ssh/spdk_vhost_id_rsa` on the host. + - Change the grub boot options for the guest as follows: + - Add "console=ttyS0 earlyprintk=ttyS0" to the boot options in `/etc/default/grub` (for serial output redirect). + - Add "scsi_mod.use_blk_mq=1" to boot options in `/etc/default/grub`. + ~~~{.sh} + sudo update-grub + ~~~ + - Reboot the VM. + - Remove any unnecessary packages (this is to make booting the VM faster): + ~~~{.sh} + apt purge snapd + apt purge Ubuntu-core-launcher + apt purge squashfs-tools + apt purge unattended-upgrades + ~~~ +5. Copy the fio binary from the guest location `/usr/bin/fio` to the host location `/home/sys_sgsw/fio_ubuntu`. +6. Place the guest VM in the host at the following location: `/home/sys_sgsw/vhost_vm_image.qcow2`. +7. On the host, edit the `~/autorun-spdk.conf` file to include the following line: SPDK_TEST_VHOST=1. diff --git a/src/spdk/test/common/config/vm_setup.conf b/src/spdk/test/common/config/vm_setup.conf new file mode 100644 index 00000000..40acea66 --- /dev/null +++ b/src/spdk/test/common/config/vm_setup.conf @@ -0,0 +1,12 @@ +# This configuration file is provided for reference purposes. +GIT_REPO_SPDK=https://review.gerrithub.io/spdk/spdk +GIT_REPO_DPDK=https://github.com/spdk/dpdk.git +GIT_REPO_LIBRXE=https://github.com/SoftRoCE/librxe-dev.git +GIT_REPO_OPEN_ISCSI=https://github.com/open-iscsi/open-iscsi +GIT_REPO_ROCKSDB=https://review.gerrithub.io/spdk/rocksdb +GIT_REPO_FIO=http://git.kernel.dk/fio.git +GIT_REPO_FLAMEGRAPH=https://github.com/brendangregg/FlameGraph.git +GIT_REPO_QEMU=https://github.com/spdk/qemu +GIT_REPO_VPP=https://gerrit.fd.io/r/vpp +GIT_REPO_LIBISCSI=https://github.com/sahlberg/libiscsi +GIT_REPO_SPDK_NVME_CLI=https://github.com/spdk/nvme-cli diff --git a/src/spdk/test/common/config/vm_setup.sh b/src/spdk/test/common/config/vm_setup.sh new file mode 100755 index 00000000..e01b8879 --- /dev/null +++ b/src/spdk/test/common/config/vm_setup.sh @@ -0,0 +1,424 @@ +#!/usr/bin/env bash + +# Virtual Machine environment requirements: +# 8 GiB of RAM (for DPDK) +# enable intel_kvm on your host machine + +# The purpose of this script is to provide a simple procedure for spinning up a new +# virtual test environment capable of running our whole test suite. This script, when +# applied to a fresh install of fedora 26 server will install all of the necessary dependencies +# to run almost the complete test suite. The main exception being VHost. Vhost requires the +# configuration of a second virtual machine. instructions for how to configure +# that vm are included in the file TEST_ENV_SETUP_README inside this repository + +# it is important to enable nesting for vms in kernel command line of your machine for the vhost tests. +# in /etc/default/grub +# append the following to the GRUB_CMDLINE_LINUX line +# intel_iommu=on kvm-intel.nested=1 + +# We have made a lot of progress with removing hardcoded paths from the tests, + +set -e + +VM_SETUP_PATH=$(readlink -f ${BASH_SOURCE%/*}) + +UPGRADE=false +INSTALL=false +CONF="librxe,iscsi,rocksdb,fio,flamegraph,tsocks,qemu,vpp,libiscsi,nvmecli" + +function install_rxe_cfg() +{ + if echo $CONF | grep -q librxe; then + # rxe_cfg is used in the NVMe-oF tests + # The librxe-dev repository provides a command line tool called rxe_cfg which makes it + # very easy to use Soft-RoCE. The build pool utilizes this command line tool in the absence + # of any real RDMA NICs to simulate one for the NVMe-oF tests. + if hash rxe_cfg 2> /dev/null; then + echo "rxe_cfg is already installed. skipping" + else + if [ -d librxe-dev ]; then + echo "librxe-dev source already present, not cloning" + else + git clone "${GIT_REPO_LIBRXE}" + fi + + ./librxe-dev/configure --libdir=/usr/lib64/ --prefix= + make -C librxe-dev -j${jobs} + sudo make -C librxe-dev install + fi + fi +} + +function install_iscsi_adm() +{ + if echo $CONF | grep -q iscsi; then + # iscsiadm is used in the iscsi_tgt tests + # The version of iscsiadm that ships with fedora 26 was broken as of November 3 2017. + # There is already a bug report out about it, and hopefully it is fixed soon, but in the event that + # that version is still broken when you do your setup, the below steps will fix the issue. + CURRENT_VERSION=$(iscsiadm --version) + OPEN_ISCSI_VER='iscsiadm version 6.2.0.874' + if [ "$CURRENT_VERSION" == "$OPEN_ISCSI_VER" ]; then + if [ ! -d open-iscsi-install ]; then + mkdir -p open-iscsi-install/patches + sudo dnf download --downloaddir=./open-iscsi-install --source iscsi-initiator-utils + rpm2cpio open-iscsi-install/$(ls ~/open-iscsi-install) | cpio -D open-iscsi-install -idmv + mv open-iscsi-install/00* open-iscsi-install/patches/ + git clone "${GIT_REPO_OPEN_ISCSI}" open-iscsi-install/open-iscsi + + # the configurations of username and email are needed for applying patches to iscsiadm. + git -C open-iscsi-install/open-iscsi config user.name none + git -C open-iscsi-install/open-iscsi config user.email none + + git -C open-iscsi-install/open-iscsi checkout 86e8892 + for patch in `ls open-iscsi-install/patches`; do + git -C open-iscsi-install/open-iscsi am ../patches/$patch + done + sed -i '427s/.*/-1);/' open-iscsi-install/open-iscsi/usr/session_info.c + make -C open-iscsi-install/open-iscsi -j${jobs} + sudo make -C open-iscsi-install/open-iscsi install + else + echo "custom open-iscsi install located, not reinstalling" + fi + fi + fi +} + +function install_rocksdb() +{ + if echo $CONF | grep -q rocksdb; then + # Rocksdb is installed for use with the blobfs tests. + if [ ! -d /usr/src/rocksdb ]; then + git clone "${GIT_REPO_ROCKSDB}" + git -C ./rocksdb checkout spdk-v5.6.1 + sudo mv rocksdb /usr/src/ + else + sudo git -C /usr/src/rocksdb checkout spdk-v5.6.1 + echo "rocksdb already in /usr/src. Not checking out again" + fi + fi +} + +function install_fio() +{ + if echo $CONF | grep -q fio; then + # This version of fio is installed in /usr/src/fio to enable + # building the spdk fio plugin. + if [ ! -d /usr/src/fio ]; then + if [ ! -d fio ]; then + git clone "${GIT_REPO_FIO}" + sudo mv fio /usr/src/ + else + sudo mv fio /usr/src/ + fi + ( + git -C /usr/src/fio checkout master && + git -C /usr/src/fio pull && + git -C /usr/src/fio checkout fio-3.3 && + make -C /usr/src/fio -j${jobs} && + sudo make -C /usr/src/fio install + ) + else + echo "fio already in /usr/src/fio. Not installing" + fi + fi +} + +function install_flamegraph() +{ + if echo $CONF | grep -q flamegraph; then + # Flamegraph is used when printing out timing graphs for the tests. + if [ ! -d /usr/local/FlameGraph ]; then + git clone "${GIT_REPO_FLAMEGRAPH}" + mkdir -p /usr/local + sudo mv FlameGraph /usr/local/FlameGraph + else + echo "flamegraph already installed. Skipping" + fi + fi +} + +function install_qemu() +{ + if echo $CONF | grep -q qemu; then + # Qemu is used in the vhost tests. + SPDK_QEMU_BRANCH=spdk-2.12 + mkdir -p qemu + if [ ! -d "qemu/$SPDK_QEMU_BRANCH" ]; then + git -C ./qemu clone "${GIT_REPO_QEMU}" -b "$SPDK_QEMU_BRANCH" "$SPDK_QEMU_BRANCH" + else + echo "qemu already checked out. Skipping" + fi + + declare -a opt_params=("--prefix=/usr/local/qemu/$SPDK_QEMU_BRANCH") + + # Most tsocks proxies rely on a configuration file in /etc/tsocks.conf. + # If using tsocks, please make sure to complete this config before trying to build qemu. + if echo $CONF | grep -q tsocks; then + if hash tsocks 2> /dev/null; then + opt_params+=(--with-git='tsocks git') + fi + fi + + # The qemu configure script places several output files in the CWD. + (cd qemu/$SPDK_QEMU_BRANCH && ./configure "${opt_params[@]}" --target-list="x86_64-softmmu" --enable-kvm --enable-linux-aio --enable-numa) + + make -C ./qemu/$SPDK_QEMU_BRANCH -j${jobs} + sudo make -C ./qemu/$SPDK_QEMU_BRANCH install + fi +} + +function install_vpp() +{ + if echo $CONF | grep -q vpp; then + # Vector packet processing (VPP) is installed for use with iSCSI tests. + # At least on fedora 28, the yum setup that vpp uses is deprecated and fails. + # The actions taken under the vpp_setup script are necessary to fix this issue. + if [ -d vpp_setup ]; then + echo "vpp setup already done." + else + echo "%_topdir $HOME/vpp_setup/src/rpm" >> ~/.rpmmacros + sudo dnf install -y perl-generators + mkdir -p ~/vpp_setup/src/rpm + mkdir -p vpp_setup/src/rpm/BUILD vpp_setup/src/rpm/RPMS vpp_setup/src/rpm/SOURCES \ + vpp_setup/src/rpm/SPECS vpp_setup/src/rpm/SRPMS + dnf download --downloaddir=./vpp_setup/src/rpm --source redhat-rpm-config + rpm -ivh ~/vpp_setup/src/rpm/redhat-rpm-config* + sed -i s/"Requires: (annobin if gcc)"//g ~/vpp_setup/src/rpm/SPECS/redhat-rpm-config.spec + rpmbuild -ba ~/vpp_setup/src/rpm/SPECS/*.spec + sudo dnf remove -y --noautoremove redhat-rpm-config + sudo rpm -Uvh ~/vpp_setup/src/rpm/RPMS/noarch/* + fi + + if [ -d vpp ]; then + echo "vpp already cloned." + if [ ! -d vpp/build-root ]; then + echo "build-root has not been done" + echo "remove the `pwd` and start again" + exit 1 + fi + else + git clone "${GIT_REPO_VPP}" + git -C ./vpp checkout v18.01.1 + # VPP 18.01.1 does not support OpenSSL 1.1. + # For compilation, a compatibility package is used temporarily. + sudo dnf install -y --allowerasing compat-openssl10-devel + # Installing required dependencies for building VPP + yes | make -C ./vpp install-dep + + make -C ./vpp pkg-rpm -j${jobs} + # Reinstall latest OpenSSL devel package. + sudo dnf install -y --allowerasing openssl-devel + sudo dnf install -y \ + ./vpp/build_root/vpp-lib-18.01.1-release.x86_64.rpm \ + ./vpp/build_root/vpp-devel-18.01.1-release.x86_64.rpm \ + ./vpp/build_root/vpp-18.01.1-release.x86_64.rpm + # Since hugepage configuration is done via spdk/scripts/setup.sh, + # this default config is not needed. + # + # NOTE: Parameters kernel.shmmax and vm.max_map_count are set to + # very low count and cause issues with hugepage total sizes above 1GB. + sudo rm -f /etc/sysctl.d/80-vpp.conf + fi + fi +} + +function install_nvmecli() +{ + if echo $CONF | grep -q nvmecli; then + SPDK_NVME_CLI_BRANCH=spdk-1.6 + if [ ! -d nvme-cli ]; then + git clone "${GIT_REPO_SPDK_NVME_CLI}" -b "$SPDK_NVME_CLI_BRANCH" + else + echo "nvme-cli already checked out. Skipping" + fi + fi +} + +function install_libiscsi() +{ + if echo $CONF | grep -q libiscsi; then + # We currently don't make any changes to the libiscsi repository for our tests, but it is possible that we will need + # to later. Cloning from git is just future proofing the machines. + if [ ! -d libiscsi ]; then + git clone "${GIT_REPO_LIBISCSI}" + else + echo "libiscsi already checked out. Skipping" + fi + ( cd libiscsi && ./autogen.sh && ./configure --prefix=/usr/local/libiscsi) + make -C ./libiscsi -j${jobs} + sudo make -C ./libiscsi install + fi +} + +function usage() +{ + echo "This script is intended to automate the environment setup for a fedora linux virtual machine." + echo "Please run this script as your regular user. The script will make calls to sudo as needed." + echo "" + echo "./vm_setup.sh" + echo " -h --help" + echo " -u --upgrade Run dnf upgrade" + echo " -i --install-deps Install dnf based dependencies" + echo " -t --test-conf List of test configurations to enable (${CONF})" + echo " -c --conf-path Path to configuration file" + exit 0 +} + +while getopts 'iuht:c:-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage;; + upgrade) UPGRADE=true;; + install-deps) INSTALL=true;; + test-conf=*) CONF="${OPTARG#*=}";; + conf-path=*) CONF_PATH="${OPTARG#*=}";; + *) echo "Invalid argument '$OPTARG'" + usage;; + esac + ;; + h) usage;; + u) UPGRADE=true;; + i) INSTALL=true;; + t) CONF="$OPTARG";; + c) CONF_PATH="$OPTARG";; + *) echo "Invalid argument '$OPTARG'" + usage;; + esac +done + +if [ ! -z "$CONF_PATH" ]; then + if [ ! -f "$CONF_PATH" ]; then + echo Configuration file does not exist: "$CONF_PATH" + exit 1 + else + source "$CONF_PATH" + fi +fi + +cd ~ + +: ${GIT_REPO_SPDK=https://review.gerrithub.io/spdk/spdk}; export GIT_REPO_SPDK +: ${GIT_REPO_DPDK=https://github.com/spdk/dpdk.git}; export GIT_REPO_DPDK +: ${GIT_REPO_LIBRXE=https://github.com/SoftRoCE/librxe-dev.git}; export GIT_REPO_LIBRXE +: ${GIT_REPO_OPEN_ISCSI=https://github.com/open-iscsi/open-iscsi}; export GIT_REPO_OPEN_ISCSI +: ${GIT_REPO_ROCKSDB=https://review.gerrithub.io/spdk/rocksdb}; export GIT_REPO_ROCKSDB +: ${GIT_REPO_FIO=http://git.kernel.dk/fio.git}; export GIT_REPO_FIO +: ${GIT_REPO_FLAMEGRAPH=https://github.com/brendangregg/FlameGraph.git}; export GIT_REPO_FLAMEGRAPH +: ${GIT_REPO_QEMU=https://github.com/spdk/qemu}; export GIT_REPO_QEMU +: ${GIT_REPO_VPP=https://gerrit.fd.io/r/vpp}; export GIT_REPO_VPP +: ${GIT_REPO_LIBISCSI=https://github.com/sahlberg/libiscsi}; export GIT_REPO_LIBISCSI +: ${GIT_REPO_SPDK_NVME_CLI=https://github.com/spdk/nvme-cli}; export GIT_REPO_SPDK_NVME_CLI + +jobs=$(($(nproc)*2)) + +if $UPGRADE; then + sudo dnf upgrade -y +fi + +if $INSTALL; then + sudo dnf install -y git +fi + +mkdir -p spdk_repo/output + +if [ -d spdk_repo/spdk ]; then + echo "spdk source already present, not cloning" +else + git -C spdk_repo clone "${GIT_REPO_SPDK}" +fi +git -C spdk_repo/spdk config submodule.dpdk.url "${GIT_REPO_DPDK}" +git -C spdk_repo/spdk submodule update --init --recursive + +if $INSTALL; then + sudo ./scripts/pkgdep.sh + + if echo $CONF | grep -q tsocks; then + sudo dnf install -y tsocks + fi + + sudo dnf install -y \ + valgrind \ + jq \ + nvme-cli \ + ceph \ + gdb \ + fio \ + librbd-devel \ + kernel-devel \ + gflags-devel \ + libasan \ + libubsan \ + autoconf \ + automake \ + libtool \ + libmount-devel \ + iscsi-initiator-utils \ + isns-utils-devel \ + pmempool \ + perl-open \ + glib2-devel \ + pixman-devel \ + astyle-devel \ + elfutils \ + elfutils-libelf-devel \ + flex \ + bison \ + targetcli \ + perl-Switch \ + librdmacm-utils \ + libibverbs-utils \ + gdisk \ + socat \ + sshfs +fi + +sudo mkdir -p /usr/src + +install_rxe_cfg& +install_iscsi_adm& +install_rocksdb& +install_fio& +install_flamegraph& +install_qemu& +install_vpp& +install_nvmecli& +install_libiscsi& + +wait +# create autorun-spdk.conf in home folder. This is sourced by the autotest_common.sh file. +# By setting any one of the values below to 0, you can skip that specific test. If you are +# using your autotest platform to do sanity checks before uploading to the build pool, it is +# probably best to only run the tests that you believe your changes have modified along with +# Scanbuild and check format. This is because running the whole suite of tests in series can +# take ~40 minutes to complete. +if [ ! -e ~/autorun-spdk.conf ]; then + cat > ~/autorun-spdk.conf << EOF +# assign a value of 1 to all of the pertinent tests +SPDK_BUILD_DOC=1 +SPDK_RUN_CHECK_FORMAT=1 +SPDK_RUN_SCANBUILD=1 +SPDK_RUN_VALGRIND=1 +SPDK_TEST_UNITTEST=1 +SPDK_TEST_ISCSI=1 +SPDK_TEST_ISCSI_INITIATOR=1 +# nvme and nvme-cli cannot be run at the same time on a VM. +SPDK_TEST_NVME=1 +SPDK_TEST_NVME_CLI=0 +SPDK_TEST_NVMF=1 +SPDK_TEST_RBD=1 +# requires some extra configuration. see TEST_ENV_SETUP_README +SPDK_TEST_VHOST=0 +SPDK_TEST_VHOST_INIT=0 +SPDK_TEST_BLOCKDEV=1 +# doesn't work on vm +SPDK_TEST_IOAT=0 +SPDK_TEST_EVENT=1 +SPDK_TEST_BLOBFS=1 +SPDK_TEST_PMDK=1 +SPDK_TEST_LVOL=1 +SPDK_RUN_ASAN=1 +SPDK_RUN_UBSAN=1 +EOF +fi diff --git a/src/spdk/test/common/lib/test_env.c b/src/spdk/test/common/lib/test_env.c new file mode 100644 index 00000000..4c600516 --- /dev/null +++ b/src/spdk/test/common/lib/test_env.c @@ -0,0 +1,469 @@ +/*- + * 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_internal/mock.h" + +#include "spdk/env.h" +#include "spdk/queue.h" + +DEFINE_STUB(spdk_process_is_primary, bool, (void), true) +DEFINE_STUB(spdk_memzone_lookup, void *, (const char *name), NULL) + +/* + * These mocks don't use the DEFINE_STUB macros because + * their default implementation is more complex. + */ + +DEFINE_RETURN_MOCK(spdk_memzone_reserve, void *); +void * +spdk_memzone_reserve(const char *name, size_t len, int socket_id, unsigned flags) +{ + HANDLE_RETURN_MOCK(spdk_memzone_reserve); + + return malloc(len); +} + +DEFINE_RETURN_MOCK(spdk_memzone_reserve_aligned, void *); +void * +spdk_memzone_reserve_aligned(const char *name, size_t len, int socket_id, + unsigned flags, unsigned align) +{ + HANDLE_RETURN_MOCK(spdk_memzone_reserve_aligned); + + return malloc(len); +} + +DEFINE_RETURN_MOCK(spdk_malloc, void *); +void * +spdk_malloc(size_t size, size_t align, uint64_t *phys_addr, int socket_id, uint32_t flags) +{ + HANDLE_RETURN_MOCK(spdk_malloc); + + void *buf = NULL; + if (posix_memalign(&buf, align, size)) { + return NULL; + } + if (phys_addr) { + *phys_addr = (uint64_t)buf; + } + + return buf; +} + +DEFINE_RETURN_MOCK(spdk_zmalloc, void *); +void * +spdk_zmalloc(size_t size, size_t align, uint64_t *phys_addr, int socket_id, uint32_t flags) +{ + HANDLE_RETURN_MOCK(spdk_zmalloc); + + void *buf = spdk_malloc(size, align, phys_addr, -1, 1); + + if (buf != NULL) { + memset(buf, 0, size); + } + return buf; +} + +DEFINE_RETURN_MOCK(spdk_dma_malloc, void *); +void * +spdk_dma_malloc(size_t size, size_t align, uint64_t *phys_addr) +{ + HANDLE_RETURN_MOCK(spdk_dma_malloc); + + return spdk_malloc(size, align, phys_addr, -1, 1); +} + +DEFINE_RETURN_MOCK(spdk_dma_zmalloc, void *); +void * +spdk_dma_zmalloc(size_t size, size_t align, uint64_t *phys_addr) +{ + HANDLE_RETURN_MOCK(spdk_dma_zmalloc); + + return spdk_zmalloc(size, align, phys_addr, -1, 1); +} + +DEFINE_RETURN_MOCK(spdk_dma_malloc_socket, void *); +void * +spdk_dma_malloc_socket(size_t size, size_t align, uint64_t *phys_addr, int socket_id) +{ + HANDLE_RETURN_MOCK(spdk_dma_malloc_socket); + + return spdk_dma_malloc(size, align, phys_addr); +} + +DEFINE_RETURN_MOCK(spdk_dma_zmalloc_socket, void *); +void * +spdk_dma_zmalloc_socket(size_t size, size_t align, uint64_t *phys_addr, int socket_id) +{ + HANDLE_RETURN_MOCK(spdk_dma_zmalloc_socket); + + return spdk_dma_zmalloc(size, align, phys_addr); +} + +DEFINE_RETURN_MOCK(spdk_dma_realloc, void *); +void * +spdk_dma_realloc(void *buf, size_t size, size_t align, uint64_t *phys_addr) +{ + HANDLE_RETURN_MOCK(spdk_dma_realloc); + + return realloc(buf, size); +} + +void +spdk_free(void *buf) +{ + free(buf); +} + +void +spdk_dma_free(void *buf) +{ + return spdk_free(buf); +} + +DEFINE_RETURN_MOCK(spdk_vtophys, uint64_t); +uint64_t +spdk_vtophys(void *buf) +{ + HANDLE_RETURN_MOCK(spdk_vtophys); + + return (uintptr_t)buf; +} + +void +spdk_memzone_dump(FILE *f) +{ + return; +} + +DEFINE_RETURN_MOCK(spdk_memzone_free, int); +int +spdk_memzone_free(const char *name) +{ + HANDLE_RETURN_MOCK(spdk_memzone_free); + + return 0; +} + +struct test_mempool { + size_t count; +}; + +DEFINE_RETURN_MOCK(spdk_mempool_create, struct spdk_mempool *); +struct spdk_mempool * +spdk_mempool_create(const char *name, size_t count, + size_t ele_size, size_t cache_size, int socket_id) +{ + struct test_mempool *mp; + + HANDLE_RETURN_MOCK(spdk_mempool_create); + + mp = calloc(1, sizeof(*mp)); + if (mp == NULL) { + return NULL; + } + + mp->count = count; + + return (struct spdk_mempool *)mp; +} + +void +spdk_mempool_free(struct spdk_mempool *_mp) +{ + struct test_mempool *mp = (struct test_mempool *)_mp; + + free(mp); +} + +DEFINE_RETURN_MOCK(spdk_mempool_get, void *); +void * +spdk_mempool_get(struct spdk_mempool *_mp) +{ + struct test_mempool *mp = (struct test_mempool *)_mp; + void *buf; + + HANDLE_RETURN_MOCK(spdk_mempool_get); + + if (mp && mp->count == 0) { + return NULL; + } + + if (posix_memalign(&buf, 64, 0x1000)) { + return NULL; + } else { + if (mp) { + mp->count--; + } + return buf; + } +} + +int +spdk_mempool_get_bulk(struct spdk_mempool *mp, void **ele_arr, size_t count) +{ + for (size_t i = 0; i < count; i++) { + ele_arr[i] = spdk_mempool_get(mp); + if (ele_arr[i] == NULL) { + return -1; + } + } + return 0; +} + +void +spdk_mempool_put(struct spdk_mempool *_mp, void *ele) +{ + struct test_mempool *mp = (struct test_mempool *)_mp; + + if (mp) { + mp->count++; + } + free(ele); +} + +void +spdk_mempool_put_bulk(struct spdk_mempool *mp, void **ele_arr, size_t count) +{ + for (size_t i = 0; i < count; i++) { + spdk_mempool_put(mp, ele_arr[i]); + } +} + +DEFINE_RETURN_MOCK(spdk_mempool_count, size_t); +size_t +spdk_mempool_count(const struct spdk_mempool *_mp) +{ + struct test_mempool *mp = (struct test_mempool *)_mp; + + HANDLE_RETURN_MOCK(spdk_mempool_count); + + if (mp) { + return mp->count; + } else { + return 1024; + } +} + +struct spdk_ring_ele { + void *ele; + TAILQ_ENTRY(spdk_ring_ele) link; +}; + +struct spdk_ring { + TAILQ_HEAD(, spdk_ring_ele) elements; +}; + +DEFINE_RETURN_MOCK(spdk_ring_create, struct spdk_ring *); +struct spdk_ring * +spdk_ring_create(enum spdk_ring_type type, size_t count, int socket_id) +{ + struct spdk_ring *ring; + + HANDLE_RETURN_MOCK(spdk_ring_create); + + ring = calloc(1, sizeof(*ring)); + if (ring) { + TAILQ_INIT(&ring->elements); + } + + return ring; +} + +void +spdk_ring_free(struct spdk_ring *ring) +{ + free(ring); +} + +DEFINE_RETURN_MOCK(spdk_ring_enqueue, size_t); +size_t +spdk_ring_enqueue(struct spdk_ring *ring, void **objs, size_t count) +{ + struct spdk_ring_ele *ele; + size_t i; + + HANDLE_RETURN_MOCK(spdk_ring_enqueue); + + for (i = 0; i < count; i++) { + ele = calloc(1, sizeof(*ele)); + if (!ele) { + break; + } + + ele->ele = objs[i]; + TAILQ_INSERT_TAIL(&ring->elements, ele, link); + } + + return i; +} + +DEFINE_RETURN_MOCK(spdk_ring_dequeue, size_t); +size_t +spdk_ring_dequeue(struct spdk_ring *ring, void **objs, size_t count) +{ + struct spdk_ring_ele *ele, *tmp; + size_t i = 0; + + HANDLE_RETURN_MOCK(spdk_ring_dequeue); + + if (count == 0) { + return 0; + } + + TAILQ_FOREACH_SAFE(ele, &ring->elements, link, tmp) { + TAILQ_REMOVE(&ring->elements, ele, link); + objs[i] = ele->ele; + free(ele); + i++; + if (i >= count) { + break; + } + } + + return i; + +} + +DEFINE_RETURN_MOCK(spdk_get_ticks, uint64_t); +uint64_t +spdk_get_ticks(void) +{ + HANDLE_RETURN_MOCK(spdk_get_ticks); + + return ut_spdk_get_ticks; +} + +DEFINE_RETURN_MOCK(spdk_get_ticks_hz, uint64_t); +uint64_t +spdk_get_ticks_hz(void) +{ + HANDLE_RETURN_MOCK(spdk_get_ticks_hz); + + return 1000000; +} + +void +spdk_delay_us(unsigned int us) +{ + /* spdk_get_ticks_hz is 1000000, meaning 1 tick per us. */ + ut_spdk_get_ticks += us; +} + +DEFINE_RETURN_MOCK(spdk_pci_addr_parse, int); +int +spdk_pci_addr_parse(struct spdk_pci_addr *addr, const char *bdf) +{ + unsigned domain, bus, dev, func; + + HANDLE_RETURN_MOCK(spdk_pci_addr_parse); + + if (addr == NULL || bdf == NULL) { + return -EINVAL; + } + + if ((sscanf(bdf, "%x:%x:%x.%x", &domain, &bus, &dev, &func) == 4) || + (sscanf(bdf, "%x.%x.%x.%x", &domain, &bus, &dev, &func) == 4)) { + /* Matched a full address - all variables are initialized */ + } else if (sscanf(bdf, "%x:%x:%x", &domain, &bus, &dev) == 3) { + func = 0; + } else if ((sscanf(bdf, "%x:%x.%x", &bus, &dev, &func) == 3) || + (sscanf(bdf, "%x.%x.%x", &bus, &dev, &func) == 3)) { + domain = 0; + } else if ((sscanf(bdf, "%x:%x", &bus, &dev) == 2) || + (sscanf(bdf, "%x.%x", &bus, &dev) == 2)) { + domain = 0; + func = 0; + } else { + return -EINVAL; + } + + if (bus > 0xFF || dev > 0x1F || func > 7) { + return -EINVAL; + } + + addr->domain = domain; + addr->bus = bus; + addr->dev = dev; + addr->func = func; + + return 0; +} + +DEFINE_RETURN_MOCK(spdk_pci_addr_fmt, int); +int +spdk_pci_addr_fmt(char *bdf, size_t sz, const struct spdk_pci_addr *addr) +{ + int rc; + + HANDLE_RETURN_MOCK(spdk_pci_addr_fmt); + + rc = snprintf(bdf, sz, "%04x:%02x:%02x.%x", + addr->domain, addr->bus, + addr->dev, addr->func); + + if (rc > 0 && (size_t)rc < sz) { + return 0; + } + + return -1; +} + +DEFINE_RETURN_MOCK(spdk_pci_addr_compare, int); +int +spdk_pci_addr_compare(const struct spdk_pci_addr *a1, const struct spdk_pci_addr *a2) +{ + HANDLE_RETURN_MOCK(spdk_pci_addr_compare); + + if (a1->domain > a2->domain) { + return 1; + } else if (a1->domain < a2->domain) { + return -1; + } else if (a1->bus > a2->bus) { + return 1; + } else if (a1->bus < a2->bus) { + return -1; + } else if (a1->dev > a2->dev) { + return 1; + } else if (a1->dev < a2->dev) { + return -1; + } else if (a1->func > a2->func) { + return 1; + } else if (a1->func < a2->func) { + return -1; + } + + return 0; +} diff --git a/src/spdk/test/common/lib/ut_multithread.c b/src/spdk/test/common/lib/ut_multithread.c new file mode 100644 index 00000000..85fcee2a --- /dev/null +++ b/src/spdk/test/common/lib/ut_multithread.c @@ -0,0 +1,278 @@ +/*- + * 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_cunit.h" +#include "spdk/thread.h" +#include "spdk_internal/mock.h" + +static uint32_t g_ut_num_threads; +static uint64_t g_current_time_in_us = 0; + +int allocate_threads(int num_threads); +void free_threads(void); +void poll_threads(void); +int poll_thread(uintptr_t thread_id); +void increment_time(uint64_t time_in_us); +void reset_time(void); + +struct ut_msg { + spdk_thread_fn fn; + void *ctx; + TAILQ_ENTRY(ut_msg) link; +}; + +struct ut_thread { + struct spdk_thread *thread; + struct spdk_io_channel *ch; + TAILQ_HEAD(, ut_msg) msgs; + TAILQ_HEAD(, ut_poller) pollers; +}; + +struct ut_thread *g_ut_threads; + +struct ut_poller { + spdk_poller_fn fn; + void *arg; + TAILQ_ENTRY(ut_poller) tailq; + uint64_t period_us; + uint64_t next_expiration_in_us; +}; + +static void +__send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx) +{ + struct ut_thread *thread = thread_ctx; + struct ut_msg *msg; + + msg = calloc(1, sizeof(*msg)); + SPDK_CU_ASSERT_FATAL(msg != NULL); + + msg->fn = fn; + msg->ctx = ctx; + TAILQ_INSERT_TAIL(&thread->msgs, msg, link); +} + +static struct spdk_poller * +__start_poller(void *thread_ctx, spdk_poller_fn fn, void *arg, uint64_t period_microseconds) +{ + struct ut_thread *thread = thread_ctx; + struct ut_poller *poller = calloc(1, sizeof(struct ut_poller)); + + SPDK_CU_ASSERT_FATAL(poller != NULL); + + poller->fn = fn; + poller->arg = arg; + poller->period_us = period_microseconds; + poller->next_expiration_in_us = g_current_time_in_us + poller->period_us; + + TAILQ_INSERT_TAIL(&thread->pollers, poller, tailq); + + return (struct spdk_poller *)poller; +} + +static void +__stop_poller(struct spdk_poller *poller, void *thread_ctx) +{ + struct ut_thread *thread = thread_ctx; + + TAILQ_REMOVE(&thread->pollers, (struct ut_poller *)poller, tailq); + + free(poller); +} + +#define INVALID_THREAD 0x1000 + +static uintptr_t g_thread_id = INVALID_THREAD; + +static void +set_thread(uintptr_t thread_id) +{ + g_thread_id = thread_id; + if (thread_id == INVALID_THREAD) { + MOCK_CLEAR(pthread_self); + } else { + MOCK_SET(pthread_self, (pthread_t)thread_id); + } +} + +int +allocate_threads(int num_threads) +{ + struct spdk_thread *thread; + uint32_t i; + + g_ut_num_threads = num_threads; + + g_ut_threads = calloc(num_threads, sizeof(*g_ut_threads)); + SPDK_CU_ASSERT_FATAL(g_ut_threads != NULL); + + for (i = 0; i < g_ut_num_threads; i++) { + set_thread(i); + spdk_allocate_thread(__send_msg, __start_poller, __stop_poller, + &g_ut_threads[i], NULL); + thread = spdk_get_thread(); + SPDK_CU_ASSERT_FATAL(thread != NULL); + g_ut_threads[i].thread = thread; + TAILQ_INIT(&g_ut_threads[i].msgs); + TAILQ_INIT(&g_ut_threads[i].pollers); + } + + set_thread(INVALID_THREAD); + return 0; +} + +void +free_threads(void) +{ + uint32_t i; + + for (i = 0; i < g_ut_num_threads; i++) { + set_thread(i); + spdk_free_thread(); + } + + g_ut_num_threads = 0; + free(g_ut_threads); + g_ut_threads = NULL; +} + +void +increment_time(uint64_t time_in_us) +{ + g_current_time_in_us += time_in_us; + spdk_delay_us(time_in_us); +} + +static void +reset_pollers(void) +{ + uint32_t i = 0; + struct ut_thread *thread = NULL; + struct ut_poller *poller = NULL; + uintptr_t original_thread_id = g_thread_id; + + CU_ASSERT(g_current_time_in_us == 0); + + for (i = 0; i < g_ut_num_threads; i++) { + set_thread(i); + thread = &g_ut_threads[i]; + + TAILQ_FOREACH(poller, &thread->pollers, tailq) { + poller->next_expiration_in_us = g_current_time_in_us + poller->period_us; + } + } + + set_thread(original_thread_id); +} + +void +reset_time(void) +{ + g_current_time_in_us = 0; + reset_pollers(); +} + +int +poll_thread(uintptr_t thread_id) +{ + int count = 0; + struct ut_thread *thread = &g_ut_threads[thread_id]; + struct ut_msg *msg; + struct ut_poller *poller; + uintptr_t original_thread_id; + TAILQ_HEAD(, ut_poller) tmp_pollers; + + CU_ASSERT(thread_id != (uintptr_t)INVALID_THREAD); + CU_ASSERT(thread_id < g_ut_num_threads); + + original_thread_id = g_thread_id; + set_thread(thread_id); + + while (!TAILQ_EMPTY(&thread->msgs)) { + msg = TAILQ_FIRST(&thread->msgs); + TAILQ_REMOVE(&thread->msgs, msg, link); + + msg->fn(msg->ctx); + count++; + free(msg); + } + + TAILQ_INIT(&tmp_pollers); + + while (!TAILQ_EMPTY(&thread->pollers)) { + poller = TAILQ_FIRST(&thread->pollers); + TAILQ_REMOVE(&thread->pollers, poller, tailq); + + if (g_current_time_in_us >= poller->next_expiration_in_us) { + if (poller->fn) { + poller->fn(poller->arg); + } + + if (poller->period_us == 0) { + break; + } else { + poller->next_expiration_in_us += poller->period_us; + } + } + + TAILQ_INSERT_TAIL(&tmp_pollers, poller, tailq); + } + + TAILQ_SWAP(&tmp_pollers, &thread->pollers, ut_poller, tailq); + + set_thread(original_thread_id); + + return count; +} + +void +poll_threads(void) +{ + bool msg_processed; + uint32_t i, count; + + while (true) { + msg_processed = false; + + for (i = 0; i < g_ut_num_threads; i++) { + count = poll_thread(i); + if (count > 0) { + msg_processed = true; + } + } + + if (!msg_processed) { + break; + } + } +} diff --git a/src/spdk/test/config_converter/config.ini b/src/spdk/test/config_converter/config.ini new file mode 100644 index 00000000..2d71f982 --- /dev/null +++ b/src/spdk/test/config_converter/config.ini @@ -0,0 +1,151 @@ +#comment1 +[Global] + Comment "Global section"#comment2 + ReactorMask 0xF #comment3 +#comment4 + #comment5 +[Nvmf] + MaxQueuesPerSession 4 + MaxQueueDepth 128 + InCapsuleDataSize 4096 + MaxIOSize 131072 + AcceptorPollRate 10000 + IOUnitSize 131072 + +[Nvme] + TransportID "trtype:PCIe traddr:0000:00:04.0" Nvme0 + +[Bdev] + BdevIoPoolSize 65536 + BdevIoCacheSize 256 + +[Split] + Split Nvme0n1 8 + +[Nvme] + RetryCount 4 + TimeoutUsec 0 + ActionOnTimeout None + AdminPollRate 100000 + HotplugEnable Yes + +[iSCSI] + NodeBase "iqn.2016-06.io.spdk" + AuthFile /usr/local/etc/spdk/auth.conf + Timeout 30 + DiscoveryAuthMethod Auto + DiscoveryAuthGroup AuthGroup1 + MaxSessions 16 + ImmediateData Yes + ErrorRecoveryLevel 0 + MaxR2T 256 + NopInInterval 10 + AllowDuplicateIsid Yes + MinConnectionsPerCore 4 + DefaultTime2Wait 2 + QueueDepth 128 + +[Malloc] + NumberOfLuns 8 + LunSizeInMB 128 + BlockSize 4096 + +[Pmem] + Blk /tmp/sample_pmem Pmem0 + +[AIO] + AIO /tmp/sample_aio0 AIO0 2048 + AIO /tmp/sample_aio1 AIO1 2048 + AIO /tmp/sample_aio2 AIO2 2048 + AIO /tmp/sample_aio1 AIO3 2048 + AIO /tmp/sample_aio2 AIO4 2048 + +[VhostBlk0] + Name vhost.1 + Dev Malloc6 + ReadOnly yes + Cpumask 0x1 + +[VhostScsi0] + Name naa.vhost.0 + Target 0 Malloc4 + Target 1 AIO3 + Target 2 Nvme0n1p2 + # Target 3 Nvme1n1p2 + Cpumask 0x1 + +[VhostScsi1] + Name naa.vhost.1 + Target 0 AIO4 + Cpumask 0x1 + +[VhostBlk1] + Name naa.vhost.2 + Dev Malloc5 + ReadOnly no + Cpumask 0x1 + +[VhostNvme0] + Name naa.vhost.3 + NumberOfQueues 2 + Namespace Nvme0n1p0 + Namespace Nvme0n1p1 + Cpumask 0x1 + +[Subsystem1] + NQN nqn.2016-06.io.spdk:cnode1 + Listen RDMA 10.0.2.15:4420 + AllowAnyHost No + Host nqn.2016-06.io.spdk:init + SN SPDK00000000000001 + MaxNamespaces 20 + Namespace Nvme0n1p5 1 + Namespace Nvme0n1p6 2 + +[Subsystem2] + NQN nqn.2016-06.io.spdk:cnode2 + Listen RDMA 10.0.2.15:4421 + AllowAnyHost No + Host nqn.2016-06.io.spdk:init + SN SPDK00000000000002 + Namespace Malloc1 + Namespace Malloc2 + Namespace AIO0 + Namespace AIO1 + +[InitiatorGroup1] + InitiatorName ANY + Netmask 127.0.0.1/32 + +[PortalGroup1] + Portal DA1 127.0.0.1:4000 + Portal DA2 127.0.0.1:4001@0xF + +[TargetNode1] + TargetName disk1 + TargetAlias "Data Disk1" + Mapping PortalGroup1 InitiatorGroup1 + AuthMethod Auto + AuthGroup AuthGroup1 + # Enable header and data digest + # UseDigest Header Data + UseDigest Auto + # Use the first malloc target + LUN0 Malloc0 + # Using the first AIO target + LUN1 AIO2 + # Using the second storage target + LUN2 AIO3 + # Using the third storage target + LUN3 AIO4 + QueueDepth 128 + +[TargetNode2] + TargetName disk2 + TargetAlias "Data Disk2" + Mapping PortalGroup1 InitiatorGroup1 + AuthMethod Auto + AuthGroup AuthGroup1 + UseDigest Auto + LUN0 Nvme0n1p3 + QueueDepth 32 diff --git a/src/spdk/test/config_converter/config_virtio.ini b/src/spdk/test/config_converter/config_virtio.ini new file mode 100644 index 00000000..b2b7f4c7 --- /dev/null +++ b/src/spdk/test/config_converter/config_virtio.ini @@ -0,0 +1,21 @@ +[VirtioUser0] + Path naa.vhost.0 + Queues 8 + +[VirtioUser1] + Path naa.vhost.1 + Queues 8 + +#[VirtioUser2] +# Path naa.vhost.3 +# Queues 8 + +#[VirtioUser3] +# Path naa.vhost.2 +# Type Blk +# Queues 8 + +[VirtioUser4] + Path vhost.1 + Type Blk +# Queues 8 diff --git a/src/spdk/test/config_converter/spdk_config.json b/src/spdk/test/config_converter/spdk_config.json new file mode 100644 index 00000000..4b4ba572 --- /dev/null +++ b/src/spdk/test/config_converter/spdk_config.json @@ -0,0 +1,481 @@ +{ + "subsystems": [ + { + "subsystem": "copy", + "config": null + }, + { + "subsystem": "interface", + "config": null + }, + { + "subsystem": "net_framework", + "config": null + }, + { + "subsystem": "bdev", + "config": [ + { + "params": { + "bdev_io_pool_size": 65536, + "bdev_io_cache_size": 256 + }, + "method": "set_bdev_options" + }, + { + "params": { + "base_bdev": "Nvme0n1", + "split_size_mb": 0, + "split_count": 8 + }, + "method": "construct_split_vbdev" + }, + { + "params": { + "retry_count": 4, + "timeout_us": 0, + "nvme_adminq_poll_period_us": 100000, + "action_on_timeout": "none" + }, + "method": "set_bdev_nvme_options" + }, + { + "params": { + "trtype": "PCIe", + "name": "Nvme0", + "traddr": "0000:00:04.0" + }, + "method": "construct_nvme_bdev" + }, + { + "params": { + "enable": true, + "period_us": 10000000 + }, + "method": "set_bdev_nvme_hotplug" + }, + { + "params": { + "block_size": 4096, + "num_blocks": 32768, + "name": "Malloc0" + }, + "method": "construct_malloc_bdev" + }, + { + "params": { + "block_size": 4096, + "num_blocks": 32768, + "name": "Malloc1" + }, + "method": "construct_malloc_bdev" + }, + { + "params": { + "block_size": 4096, + "num_blocks": 32768, + "name": "Malloc2" + }, + "method": "construct_malloc_bdev" + }, + { + "params": { + "block_size": 4096, + "num_blocks": 32768, + "name": "Malloc3" + }, + "method": "construct_malloc_bdev" + }, + { + "params": { + "block_size": 4096, + "num_blocks": 32768, + "name": "Malloc4" + }, + "method": "construct_malloc_bdev" + }, + { + "params": { + "block_size": 4096, + "num_blocks": 32768, + "name": "Malloc5" + }, + "method": "construct_malloc_bdev" + }, + { + "params": { + "block_size": 4096, + "num_blocks": 32768, + "name": "Malloc6" + }, + "method": "construct_malloc_bdev" + }, + { + "params": { + "block_size": 4096, + "num_blocks": 32768, + "name": "Malloc7" + }, + "method": "construct_malloc_bdev" + }, + { + "params": { + "block_size": 2048, + "name": "AIO0", + "filename": "/tmp/sample_aio0" + }, + "method": "construct_aio_bdev" + }, + { + "params": { + "block_size": 2048, + "name": "AIO1", + "filename": "/tmp/sample_aio1" + }, + "method": "construct_aio_bdev" + }, + { + "params": { + "block_size": 2048, + "name": "AIO2", + "filename": "/tmp/sample_aio2" + }, + "method": "construct_aio_bdev" + }, + { + "params": { + "block_size": 2048, + "name": "AIO3", + "filename": "/tmp/sample_aio1" + }, + "method": "construct_aio_bdev" + }, + { + "params": { + "block_size": 2048, + "name": "AIO4", + "filename": "/tmp/sample_aio2" + }, + "method": "construct_aio_bdev" + }, + { + "params": { + "name": "Pmem0", + "pmem_file": "/tmp/sample_pmem" + }, + "method": "construct_pmem_bdev" + } + ] + }, + { + "subsystem": "scsi", + "config": null + }, + { + "subsystem": "nvmf", + "config": [ + { + "params": { + "acceptor_poll_rate": 10000 + }, + "method": "set_nvmf_target_config" + }, + { + "params": { + "in_capsule_data_size": 4096, + "io_unit_size": 131072, + "max_qpairs_per_ctrlr": 4, + "max_queue_depth": 128, + "max_io_size": 131072, + "max_subsystems": 1024 + }, + "method": "set_nvmf_target_options" + }, + { + "params": { + "max_namespaces": 20, + "listen_addresses": [ + { + "trtype": "RDMA", + "adrfam": "IPv4", + "trsvcid": "4420", + "traddr": "10.0.2.15" + } + ], + "hosts": [ + "nqn.2016-06.io.spdk:init" + ], + "namespaces": [ + { + "bdev_name": "Nvme0n1p5", + "nsid": 1 + }, + { + "bdev_name": "Nvme0n1p6", + "nsid": 2 + } + ], + "allow_any_host": false, + "serial_number": "SPDK00000000000001", + "nqn": "nqn.2016-06.io.spdk:cnode1" + }, + "method": "construct_nvmf_subsystem" + }, + { + "params": { + "listen_addresses": [ + { + "trtype": "RDMA", + "adrfam": "IPv4", + "trsvcid": "4421", + "traddr": "10.0.2.15" + } + ], + "hosts": [ + "nqn.2016-06.io.spdk:init" + ], + "namespaces": [ + { + "bdev_name": "Malloc1", + "nsid": 1 + }, + { + "bdev_name": "Malloc2", + "nsid": 2 + }, + { + "bdev_name": "AIO0", + "nsid": 3 + }, + { + "bdev_name": "AIO1", + "nsid": 4 + } + ], + "allow_any_host": false, + "serial_number": "SPDK00000000000002", + "nqn": "nqn.2016-06.io.spdk:cnode2" + }, + "method": "construct_nvmf_subsystem" + } + ] + }, + { + "subsystem": "nbd", + "config": [] + }, + { + "subsystem": "vhost", + "config": [ + { + "params": { + "cpumask": "1", + "ctrlr": "naa.vhost.0" + }, + "method": "construct_vhost_scsi_controller" + }, + { + "params": { + "scsi_target_num": 0, + "bdev_name": "Malloc4", + "ctrlr": "naa.vhost.0" + }, + "method": "add_vhost_scsi_lun" + }, + { + "params": { + "scsi_target_num": 1, + "bdev_name": "AIO3", + "ctrlr": "naa.vhost.0" + }, + "method": "add_vhost_scsi_lun" + }, + { + "params": { + "scsi_target_num": 2, + "bdev_name": "Nvme0n1p2", + "ctrlr": "naa.vhost.0" + }, + "method": "add_vhost_scsi_lun" + }, + { + "params": { + "cpumask": "1", + "ctrlr": "naa.vhost.1" + }, + "method": "construct_vhost_scsi_controller" + }, + { + "params": { + "scsi_target_num": 0, + "bdev_name": "AIO4", + "ctrlr": "naa.vhost.1" + }, + "method": "add_vhost_scsi_lun" + }, + { + "params": { + "dev_name": "Malloc6", + "readonly": true, + "ctrlr": "vhost.1", + "cpumask": "1" + }, + "method": "construct_vhost_blk_controller" + }, + { + "params": { + "dev_name": "Malloc5", + "readonly": false, + "ctrlr": "naa.vhost.2", + "cpumask": "1" + }, + "method": "construct_vhost_blk_controller" + }, + { + "params": { + "cpumask": "1", + "io_queues": 2, + "ctrlr": "naa.vhost.3" + }, + "method": "construct_vhost_nvme_controller" + }, + { + "params": { + "bdev_name": "Nvme0n1p0", + "ctrlr": "naa.vhost.3" + }, + "method": "add_vhost_nvme_ns" + }, + { + "params": { + "bdev_name": "Nvme0n1p1", + "ctrlr": "naa.vhost.3" + }, + "method": "add_vhost_nvme_ns" + } + ] + }, + { + "subsystem": "iscsi", + "config": [ + { + "params": { + "allow_duplicated_isid": true, + "default_time2retain": 20, + "mutual_chap": false, + "require_chap": false, + "immediate_data": true, + "node_base": "iqn.2016-06.io.spdk", + "nop_in_interval": 10, + "max_connections_per_session": 2, + "first_burst_length": 8192, + "max_queue_depth": 64, + "nop_timeout": 30, + "chap_group": 1, + "max_sessions": 16, + "error_recovery_level": 0, + "disable_chap": false, + "auth_file": "/usr/local/etc/spdk/auth.conf", + "min_connections_per_core": 4, + "default_time2wait": 2 + }, + "method": "set_iscsi_options" + }, + { + "params": { + "portals": [ + { + "cpumask": "0x1", + "host": "127.0.0.1", + "port": "4000" + }, + { + "cpumask": "0x1", + "host": "127.0.0.1", + "port": "4001" + } + ], + "tag": 1 + }, + "method": "add_portal_group" + }, + { + "params": { + "initiators": [ + "ANY" + ], + "tag": 1, + "netmasks": [ + "127.0.0.1/32" + ] + }, + "method": "add_initiator_group" + }, + { + "params": { + "luns": [ + { + "lun_id": 0, + "bdev_name": "Malloc0" + }, + { + "lun_id": 1, + "bdev_name": "AIO2" + }, + { + "lun_id": 2, + "bdev_name": "AIO3" + }, + { + "lun_id": 3, + "bdev_name": "AIO4" + } + ], + "mutual_chap": false, + "name": "iqn.2016-06.io.spdk:disk1", + "alias_name": "Data Disk1", + "require_chap": false, + "chap_group": 1, + "pg_ig_maps": [ + { + "ig_tag": 1, + "pg_tag": 1 + } + ], + "data_digest": false, + "disable_chap": false, + "header_digest": false, + "queue_depth": 64 + }, + "method": "construct_target_node" + }, + { + "params": { + "luns": [ + { + "lun_id": 0, + "bdev_name": "Nvme0n1p3" + } + ], + "mutual_chap": false, + "name": "iqn.2016-06.io.spdk:disk2", + "alias_name": "Data Disk2", + "require_chap": false, + "chap_group": 1, + "pg_ig_maps": [ + { + "ig_tag": 1, + "pg_tag": 1 + } + ], + "data_digest": false, + "disable_chap": false, + "header_digest": false, + "queue_depth": 32 + }, + "method": "construct_target_node" + } + ] + } + ] +} diff --git a/src/spdk/test/config_converter/spdk_config_virtio.json b/src/spdk/test/config_converter/spdk_config_virtio.json new file mode 100644 index 00000000..00391f0b --- /dev/null +++ b/src/spdk/test/config_converter/spdk_config_virtio.json @@ -0,0 +1,138 @@ +{ + "subsystems": [ + { + "subsystem": "copy", + "config": null + }, + { + "subsystem": "interface", + "config": null + }, + { + "subsystem": "net_framework", + "config": null + }, + { + "subsystem": "bdev", + "config": [ + { + "params": { + "bdev_io_pool_size": 65536, + "bdev_io_cache_size": 256 + }, + "method": "set_bdev_options" + }, + { + "params": { + "retry_count": 4, + "timeout_us": 0, + "nvme_adminq_poll_period_us": 1000000, + "action_on_timeout": "none" + }, + "method": "set_bdev_nvme_options" + }, + { + "params": { + "enable": false, + "period_us": 100000 + }, + "method": "set_bdev_nvme_hotplug" + }, + { + "params": { + "name": "VirtioScsi0", + "dev_type": "scsi", + "vq_size": 512, + "trtype": "user", + "traddr": "naa.vhost.0", + "vq_count": 8 + }, + "method": "construct_virtio_dev" + }, + { + "params": { + "name": "VirtioScsi1", + "dev_type": "scsi", + "vq_size": 512, + "trtype": "user", + "traddr": "naa.vhost.1", + "vq_count": 8 + }, + "method": "construct_virtio_dev" + }, + { + "params": { + "name": "VirtioBlk4", + "dev_type": "blk", + "vq_size": 512, + "trtype": "user", + "traddr": "vhost.1", + "vq_count": 1 + }, + "method": "construct_virtio_dev" + } + ] + }, + { + "subsystem": "scsi", + "config": null + }, + { + "subsystem": "nvmf", + "config": [ + { + "params": { + "acceptor_poll_rate": 10000 + }, + "method": "set_nvmf_target_config" + }, + { + "params": { + "in_capsule_data_size": 4096, + "io_unit_size": 131072, + "max_qpairs_per_ctrlr": 64, + "max_queue_depth": 128, + "max_io_size": 131072, + "max_subsystems": 1024 + }, + "method": "set_nvmf_target_options" + } + ] + }, + { + "subsystem": "nbd", + "config": [] + }, + { + "subsystem": "vhost", + "config": [] + }, + { + "subsystem": "iscsi", + "config": [ + { + "params": { + "allow_duplicated_isid": false, + "default_time2retain": 20, + "mutual_chap": false, + "require_chap": false, + "immediate_data": true, + "node_base": "iqn.2016-06.io.spdk", + "nop_in_interval": 30, + "max_connections_per_session": 2, + "first_burst_length": 8192, + "max_queue_depth": 64, + "nop_timeout": 60, + "chap_group": 0, + "max_sessions": 128, + "error_recovery_level": 0, + "disable_chap": false, + "min_connections_per_core": 4, + "default_time2wait": 2 + }, + "method": "set_iscsi_options" + } + ] + } + ] +} diff --git a/src/spdk/test/config_converter/test_converter.sh b/src/spdk/test/config_converter/test_converter.sh new file mode 100755 index 00000000..5594df2d --- /dev/null +++ b/src/spdk/test/config_converter/test_converter.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +CONVERTER_DIR=$(readlink -f $(dirname $0)) +SPDK_BUILD_DIR=$CONVERTER_DIR/../../ +source $CONVERTER_DIR/../common/autotest_common.sh + +function test_cleanup() { + rm -f $CONVERTER_DIR/config_converter.json $CONVERTER_DIR/config_virtio_converter.json +} + +function on_error_exit() { + set +e + test_cleanup + print_backtrace + exit 1 +} + +trap 'on_error_exit' ERR + +cat $CONVERTER_DIR/config.ini | python3 $SPDK_BUILD_DIR/scripts/config_converter.py > $CONVERTER_DIR/config_converter.json +cat $CONVERTER_DIR/config_virtio.ini | python3 $SPDK_BUILD_DIR/scripts/config_converter.py > $CONVERTER_DIR/config_virtio_converter.json +diff -I "cpumask" -I "max_queue_depth" -I "queue_depth" <(jq -S . $CONVERTER_DIR/config_converter.json) <(jq -S . $CONVERTER_DIR/spdk_config.json) +diff <(jq -S . $CONVERTER_DIR/config_virtio_converter.json) <(jq -S . $CONVERTER_DIR/spdk_config_virtio.json) +test_cleanup diff --git a/src/spdk/test/cpp_headers/.gitignore b/src/spdk/test/cpp_headers/.gitignore new file mode 100644 index 00000000..ce1da4c5 --- /dev/null +++ b/src/spdk/test/cpp_headers/.gitignore @@ -0,0 +1 @@ +*.cpp diff --git a/src/spdk/test/cpp_headers/Makefile b/src/spdk/test/cpp_headers/Makefile new file mode 100644 index 00000000..cd159bb5 --- /dev/null +++ b/src/spdk/test/cpp_headers/Makefile @@ -0,0 +1,51 @@ +# +# 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)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +HEADERS := $(wildcard $(SPDK_ROOT_DIR)/include/spdk/*.h) +CXX_SRCS := $(patsubst %.h,%.cpp,$(notdir $(HEADERS))) + +%.cpp: $(SPDK_ROOT_DIR)/include/spdk/%.h + $(Q)echo " TEST_HEADER include/spdk/$(notdir $<)"; \ + echo '#include "spdk/$(notdir $<)"' > $@ + +all : $(CXX_SRCS) $(OBJS) + @: + +clean : + $(CLEAN_C) $(CXX_SRCS) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/env/Makefile b/src/spdk/test/env/Makefile new file mode 100644 index 00000000..d90696a1 --- /dev/null +++ b/src/spdk/test/env/Makefile @@ -0,0 +1,50 @@ +# +# 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)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +ENV_NAME := $(notdir $(CONFIG_ENV)) + +DIRS-y = vtophys + +ifeq ($(ENV_NAME),env_dpdk) +DIRS-y += memory pci +endif + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/env/env.sh b/src/spdk/test/env/env.sh new file mode 100755 index 00000000..7aa560f2 --- /dev/null +++ b/src/spdk/test/env/env.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -e + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) +source $rootdir/test/common/autotest_common.sh + +timing_enter env + +timing_enter memory +$testdir/memory/memory_ut +timing_exit memory + +timing_enter vtophys +$testdir/vtophys/vtophys +timing_exit vtophys + +timing_enter pci +$testdir/pci/pci_ut +timing_exit pci + +report_test_completion "env" +timing_exit env diff --git a/src/spdk/test/env/memory/.gitignore b/src/spdk/test/env/memory/.gitignore new file mode 100644 index 00000000..7bef3dc0 --- /dev/null +++ b/src/spdk/test/env/memory/.gitignore @@ -0,0 +1 @@ +memory_ut diff --git a/src/spdk/test/env/memory/Makefile b/src/spdk/test/env/memory/Makefile new file mode 100644 index 00000000..b6fb88e0 --- /dev/null +++ b/src/spdk/test/env/memory/Makefile @@ -0,0 +1,43 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +CFLAGS += $(ENV_CFLAGS) +CFLAGS += -I$(SPDK_ROOT_DIR)/test/lib +TEST_FILE = memory_ut.c + +ADDITIONAL_LIBS += $(ENV_LIBS) +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/env/memory/memory_ut.c b/src/spdk/test/env/memory/memory_ut.c new file mode 100644 index 00000000..39a89a10 --- /dev/null +++ b/src/spdk/test/env/memory/memory_ut.c @@ -0,0 +1,504 @@ +/*- + * 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 "env_dpdk/memory.c" + +#include "common/lib/test_env.c" +#include "spdk_cunit.h" + +#include "spdk/bit_array.h" + +static struct rte_mem_config g_mcfg = {}; + +static struct rte_config g_cfg = { + .mem_config = &g_mcfg, +}; + +struct rte_config * +rte_eal_get_configuration(void) +{ + return &g_cfg; +} + +#if RTE_VERSION >= RTE_VERSION_NUM(18, 05, 0, 0) +DEFINE_STUB(rte_mem_event_callback_register, int, (const char *name, rte_mem_event_callback_t clb, + void *arg), 0); +DEFINE_STUB(rte_memseg_contig_walk, int, (rte_memseg_contig_walk_t func, void *arg), 0); +DEFINE_STUB(rte_mem_virt2memseg, struct rte_memseg *, (const void *addr, + const struct rte_memseg_list *msl), NULL); +#endif + +#define PAGE_ARRAY_SIZE (100) +static struct spdk_bit_array *g_page_array; +static void *g_vaddr_to_fail = (void *)UINT64_MAX; + +static int +test_mem_map_notify(void *cb_ctx, struct spdk_mem_map *map, + enum spdk_mem_map_notify_action action, + void *vaddr, size_t len) +{ + uint32_t i, end; + + SPDK_CU_ASSERT_FATAL(((uintptr_t)vaddr & MASK_2MB) == 0); + SPDK_CU_ASSERT_FATAL((len & MASK_2MB) == 0); + + /* + * This is a test requirement - the bit array we use to verify + * pages are valid is only so large. + */ + SPDK_CU_ASSERT_FATAL((uintptr_t)vaddr < (VALUE_2MB * PAGE_ARRAY_SIZE)); + + i = (uintptr_t)vaddr >> SHIFT_2MB; + end = i + (len >> SHIFT_2MB); + for (; i < end; i++) { + switch (action) { + case SPDK_MEM_MAP_NOTIFY_REGISTER: + /* This page should not already be registered */ + SPDK_CU_ASSERT_FATAL(spdk_bit_array_get(g_page_array, i) == false); + SPDK_CU_ASSERT_FATAL(spdk_bit_array_set(g_page_array, i) == 0); + break; + case SPDK_MEM_MAP_NOTIFY_UNREGISTER: + SPDK_CU_ASSERT_FATAL(spdk_bit_array_get(g_page_array, i) == true); + spdk_bit_array_clear(g_page_array, i); + break; + default: + SPDK_UNREACHABLE(); + } + } + + return 0; +} + +static int +test_mem_map_notify_fail(void *cb_ctx, struct spdk_mem_map *map, + enum spdk_mem_map_notify_action action, void *vaddr, size_t size) +{ + struct spdk_mem_map *reg_map = cb_ctx; + + switch (action) { + case SPDK_MEM_MAP_NOTIFY_REGISTER: + if (vaddr == g_vaddr_to_fail) { + /* Test the error handling. */ + return -1; + } + break; + case SPDK_MEM_MAP_NOTIFY_UNREGISTER: + /* Clear the same region in the other mem_map to be able to + * verify that there was no memory left still registered after + * the mem_map creation failure. + */ + spdk_mem_map_clear_translation(reg_map, (uint64_t)vaddr, size); + break; + } + + return 0; +} + +static int +test_mem_map_notify_checklen(void *cb_ctx, struct spdk_mem_map *map, + enum spdk_mem_map_notify_action action, void *vaddr, size_t size) +{ + size_t *len_arr = cb_ctx; + + /* + * This is a test requirement - the len array we use to verify + * pages are valid is only so large. + */ + SPDK_CU_ASSERT_FATAL((uintptr_t)vaddr < (VALUE_2MB * PAGE_ARRAY_SIZE)); + + switch (action) { + case SPDK_MEM_MAP_NOTIFY_REGISTER: + assert(size == len_arr[(uintptr_t)vaddr / VALUE_2MB]); + break; + case SPDK_MEM_MAP_NOTIFY_UNREGISTER: + CU_ASSERT(size == len_arr[(uintptr_t)vaddr / VALUE_2MB]); + break; + } + + return 0; +} + +static int +test_check_regions_contiguous(uint64_t addr1, uint64_t addr2) +{ + return addr1 == addr2; +} + +const struct spdk_mem_map_ops test_mem_map_ops = { + .notify_cb = test_mem_map_notify, + .are_contiguous = test_check_regions_contiguous +}; + +const struct spdk_mem_map_ops test_mem_map_ops_no_contig = { + .notify_cb = test_mem_map_notify, + .are_contiguous = NULL +}; + +struct spdk_mem_map_ops test_map_ops_notify_fail = { + .notify_cb = test_mem_map_notify_fail, + .are_contiguous = NULL +}; + +struct spdk_mem_map_ops test_map_ops_notify_checklen = { + .notify_cb = test_mem_map_notify_checklen, + .are_contiguous = NULL +}; + +static void +test_mem_map_alloc_free(void) +{ + struct spdk_mem_map *map, *failed_map; + uint64_t default_translation = 0xDEADBEEF0BADF00D; + int i; + + map = spdk_mem_map_alloc(default_translation, &test_mem_map_ops, NULL); + SPDK_CU_ASSERT_FATAL(map != NULL); + spdk_mem_map_free(&map); + CU_ASSERT(map == NULL); + + map = spdk_mem_map_alloc(default_translation, NULL, NULL); + SPDK_CU_ASSERT_FATAL(map != NULL); + + /* Register some memory for the initial memory walk in + * spdk_mem_map_alloc(). We'll fail registering the last region + * and will check if the mem_map cleaned up all its previously + * initialized translations. + */ + for (i = 0; i < 5; i++) { + spdk_mem_register((void *)(uintptr_t)(2 * i * VALUE_2MB), VALUE_2MB); + } + + /* The last region */ + g_vaddr_to_fail = (void *)(8 * VALUE_2MB); + failed_map = spdk_mem_map_alloc(default_translation, &test_map_ops_notify_fail, map); + CU_ASSERT(failed_map == NULL); + + for (i = 0; i < 4; i++) { + uint64_t reg, size = VALUE_2MB; + + reg = spdk_mem_map_translate(map, 2 * i * VALUE_2MB, &size); + /* check if `failed_map` didn't leave any translations behind */ + CU_ASSERT(reg == default_translation); + } + + for (i = 0; i < 5; i++) { + spdk_mem_unregister((void *)(uintptr_t)(2 * i * VALUE_2MB), VALUE_2MB); + } + + spdk_mem_map_free(&map); + CU_ASSERT(map == NULL); +} + +static void +test_mem_map_translation(void) +{ + struct spdk_mem_map *map; + uint64_t default_translation = 0xDEADBEEF0BADF00D; + uint64_t addr; + uint64_t mapping_length; + int rc; + + map = spdk_mem_map_alloc(default_translation, &test_mem_map_ops, NULL); + SPDK_CU_ASSERT_FATAL(map != NULL); + + /* Try to get translation for address with no translation */ + addr = spdk_mem_map_translate(map, 10, NULL); + CU_ASSERT(addr == default_translation); + + /* Set translation for region of non-2MB multiple size */ + rc = spdk_mem_map_set_translation(map, VALUE_2MB, 1234, VALUE_2MB); + CU_ASSERT(rc == -EINVAL); + + /* Set translation for vaddr that isn't 2MB aligned */ + rc = spdk_mem_map_set_translation(map, 1234, VALUE_2MB, VALUE_2MB); + CU_ASSERT(rc == -EINVAL); + + /* Set translation for one 2MB page */ + rc = spdk_mem_map_set_translation(map, VALUE_2MB, VALUE_2MB, VALUE_2MB); + CU_ASSERT(rc == 0); + + /* Set translation for region that overlaps the previous translation */ + rc = spdk_mem_map_set_translation(map, 0, 3 * VALUE_2MB, 0); + CU_ASSERT(rc == 0); + + /* Make sure we indicate that the three regions are contiguous */ + mapping_length = VALUE_2MB * 3; + addr = spdk_mem_map_translate(map, 0, &mapping_length); + CU_ASSERT(addr == 0); + CU_ASSERT(mapping_length == VALUE_2MB * 3) + + /* Clear translation for the middle page of the larger region. */ + rc = spdk_mem_map_clear_translation(map, VALUE_2MB, VALUE_2MB); + CU_ASSERT(rc == 0); + + /* Get translation for first page */ + addr = spdk_mem_map_translate(map, 0, NULL); + CU_ASSERT(addr == 0); + + /* Make sure we indicate that the three regions are no longer contiguous */ + mapping_length = VALUE_2MB * 3; + addr = spdk_mem_map_translate(map, 0, &mapping_length); + CU_ASSERT(addr == 0); + CU_ASSERT(mapping_length == VALUE_2MB) + + /* Get translation for an unallocated block. Make sure size is 0 */ + mapping_length = VALUE_2MB * 3; + addr = spdk_mem_map_translate(map, VALUE_2MB, &mapping_length); + CU_ASSERT(addr == default_translation); + CU_ASSERT(mapping_length == VALUE_2MB) + + /* Verify translation for 2nd page is the default */ + addr = spdk_mem_map_translate(map, VALUE_2MB, NULL); + CU_ASSERT(addr == default_translation); + + /* Get translation for third page */ + addr = spdk_mem_map_translate(map, 2 * VALUE_2MB, NULL); + /* + * Note that addr should be 0, not 4MB. When we set the + * translation above, we said the whole 6MB region + * should translate to 0. + */ + CU_ASSERT(addr == 0); + + /* Clear translation for the first page */ + rc = spdk_mem_map_clear_translation(map, 0, VALUE_2MB); + CU_ASSERT(rc == 0); + + /* Get translation for the first page */ + addr = spdk_mem_map_translate(map, 0, NULL); + CU_ASSERT(addr == default_translation); + + /* Clear translation for the third page */ + rc = spdk_mem_map_clear_translation(map, 2 * VALUE_2MB, VALUE_2MB); + CU_ASSERT(rc == 0); + + /* Get translation for the third page */ + addr = spdk_mem_map_translate(map, 2 * VALUE_2MB, NULL); + CU_ASSERT(addr == default_translation); + + /* Set translation for the last valid 2MB region */ + rc = spdk_mem_map_set_translation(map, 0xffffffe00000ULL, VALUE_2MB, 0x1234); + CU_ASSERT(rc == 0); + + /* Verify translation for last valid 2MB region */ + addr = spdk_mem_map_translate(map, 0xffffffe00000ULL, NULL); + CU_ASSERT(addr == 0x1234); + + /* Attempt to set translation for the first invalid address */ + rc = spdk_mem_map_set_translation(map, 0x1000000000000ULL, VALUE_2MB, 0x5678); + CU_ASSERT(rc == -EINVAL); + + /* Attempt to set translation starting at a valid address but exceeding the valid range */ + rc = spdk_mem_map_set_translation(map, 0xffffffe00000ULL, VALUE_2MB * 2, 0x123123); + CU_ASSERT(rc != 0); + + spdk_mem_map_free(&map); + CU_ASSERT(map == NULL); + + /* Allocate a map without a contiguous region checker */ + map = spdk_mem_map_alloc(default_translation, &test_mem_map_ops_no_contig, NULL); + SPDK_CU_ASSERT_FATAL(map != NULL); + + /* map three contiguous regions */ + rc = spdk_mem_map_set_translation(map, 0, 3 * VALUE_2MB, 0); + CU_ASSERT(rc == 0); + + /* Since we can't check their contiguity, make sure we only return the size of one page */ + mapping_length = VALUE_2MB * 3; + addr = spdk_mem_map_translate(map, 0, &mapping_length); + CU_ASSERT(addr == 0); + CU_ASSERT(mapping_length == VALUE_2MB) + + /* Clear the translation */ + rc = spdk_mem_map_clear_translation(map, 0, VALUE_2MB * 3); + CU_ASSERT(rc == 0); + + spdk_mem_map_free(&map); + CU_ASSERT(map == NULL); +} + +static void +test_mem_map_registration(void) +{ + int rc; + struct spdk_mem_map *map; + uint64_t default_translation = 0xDEADBEEF0BADF00D; + + map = spdk_mem_map_alloc(default_translation, &test_mem_map_ops, NULL); + SPDK_CU_ASSERT_FATAL(map != NULL); + + /* Unregister memory region that wasn't previously registered */ + rc = spdk_mem_unregister((void *)VALUE_2MB, VALUE_2MB); + CU_ASSERT(rc == -EINVAL); + + /* Register non-2MB multiple size */ + rc = spdk_mem_register((void *)VALUE_2MB, 1234); + CU_ASSERT(rc == -EINVAL); + + /* Register region that isn't 2MB aligned */ + rc = spdk_mem_register((void *)1234, VALUE_2MB); + CU_ASSERT(rc == -EINVAL); + + /* Register one 2MB page */ + rc = spdk_mem_register((void *)VALUE_2MB, VALUE_2MB); + CU_ASSERT(rc == 0); + + /* Register an overlapping address range */ + rc = spdk_mem_register((void *)0, 3 * VALUE_2MB); + CU_ASSERT(rc == -EBUSY); + + /* Unregister a 2MB page */ + rc = spdk_mem_unregister((void *)VALUE_2MB, VALUE_2MB); + CU_ASSERT(rc == 0); + + /* Register non overlapping address range */ + rc = spdk_mem_register((void *)0, 3 * VALUE_2MB); + CU_ASSERT(rc == 0); + + /* Unregister the middle page of the larger region. */ + rc = spdk_mem_unregister((void *)VALUE_2MB, VALUE_2MB); + CU_ASSERT(rc == -ERANGE); + + /* Unregister the first page */ + rc = spdk_mem_unregister((void *)0, VALUE_2MB); + CU_ASSERT(rc == -ERANGE); + + /* Unregister the third page */ + rc = spdk_mem_unregister((void *)(2 * VALUE_2MB), VALUE_2MB); + CU_ASSERT(rc == -ERANGE); + + /* Unregister the entire address range */ + rc = spdk_mem_unregister((void *)0, 3 * VALUE_2MB); + CU_ASSERT(rc == 0); + + spdk_mem_map_free(&map); + CU_ASSERT(map == NULL); +} + +static void +test_mem_map_registration_adjacent(void) +{ + struct spdk_mem_map *map, *newmap; + uint64_t default_translation = 0xDEADBEEF0BADF00D; + uintptr_t vaddr; + unsigned i; + size_t notify_len[PAGE_ARRAY_SIZE] = {0}; + size_t chunk_len[] = { 2, 1, 3, 2, 1, 1 }; + + map = spdk_mem_map_alloc(default_translation, + &test_map_ops_notify_checklen, notify_len); + SPDK_CU_ASSERT_FATAL(map != NULL); + + vaddr = 0; + for (i = 0; i < SPDK_COUNTOF(chunk_len); i++) { + notify_len[vaddr / VALUE_2MB] = chunk_len[i] * VALUE_2MB; + spdk_mem_register((void *)vaddr, notify_len[vaddr / VALUE_2MB]); + vaddr += notify_len[vaddr / VALUE_2MB]; + } + + /* Verify the memory is translated in the same chunks it was registered */ + newmap = spdk_mem_map_alloc(default_translation, + &test_map_ops_notify_checklen, notify_len); + SPDK_CU_ASSERT_FATAL(newmap != NULL); + spdk_mem_map_free(&newmap); + CU_ASSERT(newmap == NULL); + + vaddr = 0; + for (i = 0; i < SPDK_COUNTOF(chunk_len); i++) { + notify_len[vaddr / VALUE_2MB] = chunk_len[i] * VALUE_2MB; + spdk_mem_unregister((void *)vaddr, notify_len[vaddr / VALUE_2MB]); + vaddr += notify_len[vaddr / VALUE_2MB]; + } + + /* Register all chunks again just to unregister them again, but this + * time with only a single unregister() call. + */ + vaddr = 0; + for (i = 0; i < SPDK_COUNTOF(chunk_len); i++) { + notify_len[vaddr / VALUE_2MB] = chunk_len[i] * VALUE_2MB; + spdk_mem_register((void *)vaddr, notify_len[vaddr / VALUE_2MB]); + vaddr += notify_len[vaddr / VALUE_2MB]; + } + spdk_mem_unregister(0, vaddr); + + spdk_mem_map_free(&map); + CU_ASSERT(map == NULL); +} + +int +main(int argc, char **argv) +{ + CU_pSuite suite = NULL; + unsigned int num_failures; + + /* + * These tests can use PAGE_ARRAY_SIZE 2MB pages of memory. + * Note that the tests just verify addresses - this memory + * is not actually allocated. + */ + g_page_array = spdk_bit_array_create(PAGE_ARRAY_SIZE); + + /* Initialize the memory map */ + if (spdk_mem_map_init() < 0) { + return CUE_NOMEMORY; + } + + if (CU_initialize_registry() != CUE_SUCCESS) { + return CU_get_error(); + } + + suite = CU_add_suite("memory", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "alloc and free memory map", test_mem_map_alloc_free) == NULL || + CU_add_test(suite, "mem map translation", test_mem_map_translation) == NULL || + CU_add_test(suite, "mem map registration", test_mem_map_registration) == NULL || + CU_add_test(suite, "mem map adjacent registrations", test_mem_map_registration_adjacent) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + spdk_bit_array_free(&g_page_array); + + return num_failures; +} diff --git a/src/spdk/test/env/pci/.gitignore b/src/spdk/test/env/pci/.gitignore new file mode 100644 index 00000000..11d1c65b --- /dev/null +++ b/src/spdk/test/env/pci/.gitignore @@ -0,0 +1 @@ +pci_ut diff --git a/src/spdk/test/env/pci/Makefile b/src/spdk/test/env/pci/Makefile new file mode 100644 index 00000000..34165584 --- /dev/null +++ b/src/spdk/test/env/pci/Makefile @@ -0,0 +1,43 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +CFLAGS += $(ENV_CFLAGS) +LIBS += $(ENV_LINKER_ARGS) +TEST_FILE = pci_ut.c + +ADDITIONAL_LIBS += $(ENV_LIBS) +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/env/pci/pci_ut.c b/src/spdk/test/env/pci/pci_ut.c new file mode 100644 index 00000000..bdb3a7cc --- /dev/null +++ b/src/spdk/test/env/pci/pci_ut.c @@ -0,0 +1,94 @@ +/*- + * 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 "CUnit/Basic.h" + +#include "env_dpdk/pci.c" + +static void +pci_test(void) +{ + int rc = 0; + pid_t childPid; + int status, ret; + struct spdk_pci_addr pci_addr; + + pci_addr.domain = 0x0; + pci_addr.bus = 0x5; + pci_addr.dev = 0x4; + pci_addr.func = 1; + + rc = spdk_pci_device_claim(&pci_addr); + CU_ASSERT(rc >= 0); + + childPid = fork(); + CU_ASSERT(childPid >= 0); + if (childPid == 0) { + ret = spdk_pci_device_claim(&pci_addr); + CU_ASSERT(ret == -1); + exit(0); + } else { + waitpid(childPid, &status, 0); + } +} + +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("pci", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "pci_ut1", pci_test) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/env/vtophys/.gitignore b/src/spdk/test/env/vtophys/.gitignore new file mode 100644 index 00000000..a03b46cc --- /dev/null +++ b/src/spdk/test/env/vtophys/.gitignore @@ -0,0 +1 @@ +vtophys diff --git a/src/spdk/test/env/vtophys/Makefile b/src/spdk/test/env/vtophys/Makefile new file mode 100644 index 00000000..7dc4d005 --- /dev/null +++ b/src/spdk/test/env/vtophys/Makefile @@ -0,0 +1,42 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +LIBS += $(ENV_LINKER_ARGS) +TEST_FILE = vtophys.c + +ADDITIONAL_LIBS += $(ENV_LIBS) +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/env/vtophys/vtophys.c b/src/spdk/test/env/vtophys/vtophys.c new file mode 100644 index 00000000..d672b1ae --- /dev/null +++ b/src/spdk/test/env/vtophys/vtophys.c @@ -0,0 +1,135 @@ +/*- + * 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/env.h" + +static int +vtophys_negative_test(void) +{ + void *p = NULL; + int i; + unsigned int size = 1; + int rc = 0; + + for (i = 0; i < 31; i++) { + p = malloc(size); + if (p == NULL) { + continue; + } + + if (spdk_vtophys(p) != SPDK_VTOPHYS_ERROR) { + rc = -1; + printf("Err: VA=%p is mapped to a huge_page,\n", p); + free(p); + break; + } + + free(p); + size = size << 1; + } + + /* Test addresses that are not in the valid x86-64 usermode range */ + + if (spdk_vtophys((void *)0x0000800000000000ULL) != SPDK_VTOPHYS_ERROR) { + rc = -1; + printf("Err: kernel-mode address incorrectly allowed\n"); + } + + if (!rc) { + printf("vtophys_negative_test passed\n"); + } else { + printf("vtophys_negative_test failed\n"); + } + + return rc; +} + +static int +vtophys_positive_test(void) +{ + void *p = NULL; + int i; + unsigned int size = 1; + int rc = 0; + + for (i = 0; i < 31; i++) { + p = spdk_dma_zmalloc(size, 512, NULL); + if (p == NULL) { + continue; + } + + if (spdk_vtophys(p) == SPDK_VTOPHYS_ERROR) { + rc = -1; + printf("Err: VA=%p is not mapped to a huge_page,\n", p); + spdk_dma_free(p); + break; + } + + spdk_dma_free(p); + size = size << 1; + } + + if (!rc) { + printf("vtophys_positive_test passed\n"); + } else { + printf("vtophys_positive_test failed\n"); + } + + return rc; +} + +int +main(int argc, char **argv) +{ + int rc; + struct spdk_env_opts opts; + + spdk_env_opts_init(&opts); + opts.name = "vtophys"; + opts.core_mask = "0x1"; + opts.mem_size = 256; + if (spdk_env_init(&opts) < 0) { + printf("Err: Unable to initialize SPDK env\n"); + return 1; + } + + rc = vtophys_negative_test(); + if (rc < 0) { + return rc; + } + + rc = vtophys_positive_test(); + return rc; +} diff --git a/src/spdk/test/event/Makefile b/src/spdk/test/event/Makefile new file mode 100644 index 00000000..b3e9cf1b --- /dev/null +++ b/src/spdk/test/event/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = event_perf reactor reactor_perf + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/event/event.sh b/src/spdk/test/event/event.sh new file mode 100755 index 00000000..e1d080b1 --- /dev/null +++ b/src/spdk/test/event/event.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) +source $rootdir/test/common/autotest_common.sh + +timing_enter event +$testdir/event_perf/event_perf -m 0xF -t 1 +$testdir/reactor/reactor -t 1 +$testdir/reactor_perf/reactor_perf -t 1 +report_test_completion "event" +timing_exit event diff --git a/src/spdk/test/event/event_perf/.gitignore b/src/spdk/test/event/event_perf/.gitignore new file mode 100644 index 00000000..2bdb558d --- /dev/null +++ b/src/spdk/test/event/event_perf/.gitignore @@ -0,0 +1 @@ +event_perf diff --git a/src/spdk/test/event/event_perf/Makefile b/src/spdk/test/event/event_perf/Makefile new file mode 100644 index 00000000..a3aef2ea --- /dev/null +++ b/src/spdk/test/event/event_perf/Makefile @@ -0,0 +1,54 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = event_perf +C_SRCS := event_perf.c + +SPDK_LIB_LIST = event trace conf thread util log rpc jsonrpc json + +LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) + +all : $(APP) + @: + +$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(ENV_LIBS) + $(LINK_C) + +clean : + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/event/event_perf/event_perf.c b/src/spdk/test/event/event_perf/event_perf.c new file mode 100644 index 00000000..917ea950 --- /dev/null +++ b/src/spdk/test/event/event_perf/event_perf.c @@ -0,0 +1,180 @@ +/*- + * 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/env.h" +#include "spdk/event.h" +#include "spdk_internal/event.h" +#include "spdk/log.h" + +static uint64_t g_tsc_rate; +static uint64_t g_tsc_us_rate; +static uint64_t g_tsc_end; + +static int g_time_in_sec; + +static uint64_t *call_count; + +static bool g_app_stopped = false; + +static void +submit_new_event(void *arg1, void *arg2) +{ + struct spdk_event *event; + static __thread uint32_t next_lcore = UINT32_MAX; + + if (spdk_get_ticks() > g_tsc_end) { + if (__sync_bool_compare_and_swap(&g_app_stopped, false, true)) { + spdk_app_stop(0); + } + return; + } + + if (next_lcore == UINT32_MAX) { + next_lcore = spdk_env_get_next_core(spdk_env_get_current_core()); + if (next_lcore == UINT32_MAX) { + next_lcore = spdk_env_get_first_core(); + } + } + + call_count[next_lcore]++; + event = spdk_event_allocate(next_lcore, submit_new_event, NULL, NULL); + spdk_event_call(event); +} + +static void +event_work_fn(void *arg1, void *arg2) +{ + + submit_new_event(NULL, NULL); + submit_new_event(NULL, NULL); + submit_new_event(NULL, NULL); + submit_new_event(NULL, NULL); +} + +static void +event_perf_start(void *arg1, void *arg2) +{ + uint32_t i; + + call_count = calloc(spdk_env_get_last_core() + 1, sizeof(*call_count)); + if (call_count == NULL) { + fprintf(stderr, "call_count allocation failed\n"); + spdk_app_stop(1); + return; + } + + g_tsc_rate = spdk_get_ticks_hz(); + g_tsc_us_rate = g_tsc_rate / (1000 * 1000); + g_tsc_end = spdk_get_ticks() + g_time_in_sec * g_tsc_rate; + + printf("Running I/O for %d seconds...", g_time_in_sec); + fflush(stdout); + + SPDK_ENV_FOREACH_CORE(i) { + spdk_event_call(spdk_event_allocate(i, event_work_fn, + NULL, NULL)); + } + +} + +static void +usage(char *program_name) +{ + printf("%s options\n", program_name); + printf("\t[-m core mask for distributing I/O submission/completion work\n"); + printf("\t\t(default: 0x1 - use core 0 only)]\n"); + printf("\t[-t time in seconds]\n"); +} + +static void +performance_dump(int io_time) +{ + uint32_t i; + + if (call_count == NULL) { + return; + } + + printf("\n"); + SPDK_ENV_FOREACH_CORE(i) { + printf("lcore %2d: %8ju\n", i, call_count[i] / g_time_in_sec); + } + + fflush(stdout); + free(call_count); +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int op; + int rc = 0; + + opts.name = "event_perf"; + opts.mem_size = 256; + + g_time_in_sec = 0; + + while ((op = getopt(argc, argv, "m:t:")) != -1) { + switch (op) { + case 'm': + opts.reactor_mask = optarg; + break; + case 't': + g_time_in_sec = atoi(optarg); + break; + default: + usage(argv[0]); + exit(1); + } + } + + if (!g_time_in_sec) { + usage(argv[0]); + exit(1); + } + + printf("Running I/O for %d seconds...", g_time_in_sec); + fflush(stdout); + + rc = spdk_app_start(&opts, event_perf_start, NULL, NULL); + + spdk_app_fini(); + performance_dump(g_time_in_sec); + + printf("done.\n"); + return rc; +} diff --git a/src/spdk/test/event/reactor/.gitignore b/src/spdk/test/event/reactor/.gitignore new file mode 100644 index 00000000..194b15d7 --- /dev/null +++ b/src/spdk/test/event/reactor/.gitignore @@ -0,0 +1 @@ +reactor diff --git a/src/spdk/test/event/reactor/Makefile b/src/spdk/test/event/reactor/Makefile new file mode 100644 index 00000000..c5a6168a --- /dev/null +++ b/src/spdk/test/event/reactor/Makefile @@ -0,0 +1,54 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = reactor +C_SRCS := reactor.c + +SPDK_LIB_LIST = event trace conf thread util log rpc jsonrpc json + +LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) + +all : $(APP) + @: + +$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(ENV_LIBS) + $(LINK_C) + +clean : + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/event/reactor/reactor.c b/src/spdk/test/event/reactor/reactor.c new file mode 100644 index 00000000..d79f94ba --- /dev/null +++ b/src/spdk/test/event/reactor/reactor.c @@ -0,0 +1,144 @@ +/*- + * 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/event.h" +#include "spdk/thread.h" + +static int g_time_in_sec; +static struct spdk_poller *test_end_poller; +static struct spdk_poller *poller_100ms; +static struct spdk_poller *poller_250ms; +static struct spdk_poller *poller_500ms; +static struct spdk_poller *poller_oneshot; +static struct spdk_poller *poller_unregister; + +static int +test_end(void *arg) +{ + printf("test_end\n"); + + spdk_poller_unregister(&test_end_poller); + spdk_poller_unregister(&poller_100ms); + spdk_poller_unregister(&poller_250ms); + spdk_poller_unregister(&poller_500ms); + + spdk_app_stop(0); + return -1; +} + +static int +tick(void *arg) +{ + uintptr_t period = (uintptr_t)arg; + + printf("tick %" PRIu64 "\n", (uint64_t)period); + + return -1; +} + +static int +oneshot(void *arg) +{ + printf("oneshot\n"); + spdk_poller_unregister(&poller_oneshot); + + return -1; +} + +static int +nop(void *arg) +{ + return -1; +} + +static void +test_start(void *arg1, void *arg2) +{ + printf("test_start\n"); + + /* Register a poller that will stop the test after the time has elapsed. */ + test_end_poller = spdk_poller_register(test_end, NULL, g_time_in_sec * 1000000ULL); + + poller_100ms = spdk_poller_register(tick, (void *)100, 100000); + poller_250ms = spdk_poller_register(tick, (void *)250, 250000); + poller_500ms = spdk_poller_register(tick, (void *)500, 500000); + poller_oneshot = spdk_poller_register(oneshot, NULL, 0); + + poller_unregister = spdk_poller_register(nop, NULL, 0); + spdk_poller_unregister(&poller_unregister); +} + +static void +usage(const char *program_name) +{ + printf("%s options\n", program_name); + printf("\t[-t time in seconds]\n"); +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts; + int op; + int rc = 0; + + spdk_app_opts_init(&opts); + opts.name = "reactor"; + opts.max_delay_us = 1000; + + g_time_in_sec = 0; + + while ((op = getopt(argc, argv, "t:")) != -1) { + switch (op) { + case 't': + g_time_in_sec = atoi(optarg); + break; + default: + usage(argv[0]); + exit(1); + } + } + + if (!g_time_in_sec) { + usage(argv[0]); + exit(1); + } + + rc = spdk_app_start(&opts, test_start, NULL, NULL); + + spdk_app_fini(); + + return rc; +} diff --git a/src/spdk/test/event/reactor_perf/.gitignore b/src/spdk/test/event/reactor_perf/.gitignore new file mode 100644 index 00000000..32160228 --- /dev/null +++ b/src/spdk/test/event/reactor_perf/.gitignore @@ -0,0 +1 @@ +reactor_perf diff --git a/src/spdk/test/event/reactor_perf/Makefile b/src/spdk/test/event/reactor_perf/Makefile new file mode 100644 index 00000000..820a6042 --- /dev/null +++ b/src/spdk/test/event/reactor_perf/Makefile @@ -0,0 +1,54 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = reactor_perf +C_SRCS := reactor_perf.c + +SPDK_LIB_LIST = event trace conf thread util log rpc jsonrpc json + +LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) + +all : $(APP) + @: + +$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(ENV_LIBS) + $(LINK_C) + +clean : + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/event/reactor_perf/reactor_perf.c b/src/spdk/test/event/reactor_perf/reactor_perf.c new file mode 100644 index 00000000..357f9038 --- /dev/null +++ b/src/spdk/test/event/reactor_perf/reactor_perf.c @@ -0,0 +1,144 @@ +/*- + * 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/env.h" +#include "spdk/event.h" +#include "spdk/thread.h" + +static int g_time_in_sec; +static int g_queue_depth; +static struct spdk_poller *test_end_poller; +static uint64_t g_call_count = 0; + +static int +__test_end(void *arg) +{ + printf("test_end\n"); + spdk_app_stop(0); + return -1; +} + +static void +__submit_next(void *arg1, void *arg2) +{ + struct spdk_event *event; + + g_call_count++; + + event = spdk_event_allocate(spdk_env_get_current_core(), + __submit_next, NULL, NULL); + spdk_event_call(event); +} + +static void +test_start(void *arg1, void *arg2) +{ + int i; + + printf("test_start\n"); + + /* Register a poller that will stop the test after the time has elapsed. */ + test_end_poller = spdk_poller_register(__test_end, NULL, + g_time_in_sec * 1000000ULL); + + for (i = 0; i < g_queue_depth; i++) { + __submit_next(NULL, NULL); + } +} + +static void +test_cleanup(void) +{ + printf("test_abort\n"); + + spdk_poller_unregister(&test_end_poller); + spdk_app_stop(0); +} + +static void +usage(const char *program_name) +{ + printf("%s options\n", program_name); + printf("\t[-d Allowed delay when passing messages between cores in microseconds]\n"); + printf("\t[-q Queue depth (default: 1)]\n"); + printf("\t[-t time in seconds]\n"); +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts; + int op; + int rc; + + spdk_app_opts_init(&opts); + opts.name = "reactor_perf"; + opts.max_delay_us = 1000; + + g_time_in_sec = 0; + g_queue_depth = 1; + + while ((op = getopt(argc, argv, "d:q:t:")) != -1) { + switch (op) { + case 'd': + opts.max_delay_us = atoi(optarg); + break; + case 'q': + g_queue_depth = atoi(optarg); + break; + case 't': + g_time_in_sec = atoi(optarg); + break; + default: + usage(argv[0]); + exit(1); + } + } + + if (!g_time_in_sec) { + usage(argv[0]); + exit(1); + } + + opts.shutdown_cb = test_cleanup; + + rc = spdk_app_start(&opts, test_start, NULL, NULL); + + spdk_app_fini(); + + printf("Performance: %8ju events per second\n", g_call_count / g_time_in_sec); + + return rc; +} diff --git a/src/spdk/test/ioat/ioat.sh b/src/spdk/test/ioat/ioat.sh new file mode 100755 index 00000000..b15f635e --- /dev/null +++ b/src/spdk/test/ioat/ioat.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -xe + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) +source $rootdir/test/common/autotest_common.sh + +timing_enter ioat + +timing_enter perf +$rootdir/examples/ioat/perf/perf -t 1 +timing_exit perf + +timing_enter verify +$rootdir/examples/ioat/verify/verify -t 1 +timing_exit verify + +report_test_completion "ioat" +timing_exit ioat diff --git a/src/spdk/test/iscsi_tgt/bdev_io_wait/bdev_io_wait.sh b/src/spdk/test/iscsi_tgt/bdev_io_wait/bdev_io_wait.sh new file mode 100755 index 00000000..94137507 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/bdev_io_wait/bdev_io_wait.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +timing_enter bdev_io_wait + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" + +timing_enter start_iscsi_tgt + +# Start the iSCSI target without using stub +# Reason: Two SPDK processes will be started +$ISCSI_APP -m 0x2 -p 1 -s 512 --wait-for-rpc & +pid=$! +echo "iSCSI target launched. pid: $pid" +trap "killprocess $pid;exit 1" SIGINT SIGTERM EXIT +waitforlisten $pid +$rpc_py set_iscsi_options -o 30 -a 4 +# Minimal number of bdev io pool (5) and cache (1) +$rpc_py set_bdev_options -p 5 -c 1 +$rpc_py start_subsystem_init +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK +$rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE +# "Malloc0:0" ==> use Malloc0 blockdev for LUN0 +# "1:2" ==> map PortalGroup1 to InitiatorGroup2 +# "64" ==> iSCSI queue depth 64 +# "-d" ==> disable CHAP authentication +$rpc_py construct_target_node disk1 disk1_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 256 -d +sleep 1 +trap "killprocess $pid; rm -f $testdir/bdev.conf; exit 1" SIGINT SIGTERM EXIT + +# Prepare config file for iSCSI initiator +echo "[iSCSI_Initiator]" > $testdir/bdev.conf +echo " URL iscsi://$TARGET_IP/iqn.2016-06.io.spdk:disk1/0 iSCSI0" >> $testdir/bdev.conf +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w write -t 1 +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w read -t 1 +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w flush -t 1 +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w unmap -t 1 +rm -f $testdir/bdev.conf + +trap - SIGINT SIGTERM EXIT + +killprocess $pid + +report_test_completion "bdev_io_wait" +timing_exit bdev_io_wait diff --git a/src/spdk/test/iscsi_tgt/calsoft/auth.conf b/src/spdk/test/iscsi_tgt/calsoft/auth.conf new file mode 100644 index 00000000..303bac31 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/calsoft/auth.conf @@ -0,0 +1,3 @@ +[AuthGroup1] + Comment "Auth Group1" + Auth "root" "tester" diff --git a/src/spdk/test/iscsi_tgt/calsoft/calsoft.py b/src/spdk/test/iscsi_tgt/calsoft/calsoft.py new file mode 100755 index 00000000..2970328e --- /dev/null +++ b/src/spdk/test/iscsi_tgt/calsoft/calsoft.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 + +import os +import time +import sys +import subprocess +import threading +import json + +CALSOFT_BIN_PATH = "/usr/local/calsoft/iscsi-pcts-v1.5/bin" + +''' +11/26/2015 disable tc_login_11_2 and tc_login_11_4 +RFC 7143 6.3 +Neither the initiator nor the target should attempt to declare or +negotiate a parameter more than once during login, except for +responses to specific keys that explicitly allow repeated key +declarations (e.g., TargetAddress) + +The spec didn't make it clear what other keys could be re-declare +Disscussed this with UNH and get the conclusion that TargetName/ +TargetAddress/MaxRecvDataSegmentLength could be re-declare. +''' +''' +12/1/2015 add tc_login_2_2 to known_failed_cases +RFC 7143 6.1 +A standard-label MUST begin with a capital letter and must not exceed +63 characters. +key name: A standard-label +''' +known_failed_cases = ['tc_ffp_15_2', 'tc_ffp_29_2', 'tc_ffp_29_3', 'tc_ffp_29_4', + 'tc_err_1_1', 'tc_err_1_2', 'tc_err_2_8', + 'tc_err_3_1', 'tc_err_3_2', 'tc_err_3_3', + 'tc_err_3_4', 'tc_err_5_1', 'tc_login_3_1', + 'tc_login_11_2', 'tc_login_11_4', 'tc_login_2_2'] + + +def run_case(case, result_list, log_dir_path): + try: + case_log = subprocess.check_output("{}/{}".format(CALSOFT_BIN_PATH, case), stderr=subprocess.STDOUT, shell=True) + except subprocess.CalledProcessError as e: + result_list.append({"Name": case, "Result": "FAIL"}) + case_log = e.output + else: + result_list.append({"Name": case, "Result": "PASS"}) + with open(log_dir_path + case + '.txt', 'w') as f: + f.write(case_log) + + +def main(): + if not os.path.exists(CALSOFT_BIN_PATH): + print("The Calsoft test suite is not available on this machine.") + sys.exit(1) + + output_dir = sys.argv[1] + if len(sys.argv) > 2: + output_file = sys.argv[2] + else: + output_file = "%s/calsoft.json" % (output_dir) + + log_dir = "%s/calsoft/" % output_dir + + all_cases = [x for x in os.listdir(CALSOFT_BIN_PATH) if x.startswith('tc')] + all_cases.sort() + + case_result_list = [] + + result = {"Calsoft iSCSI tests": case_result_list} + + if not os.path.exists(log_dir): + os.mkdir(log_dir) + for case in known_failed_cases: + print("Skipping %s. It is known to fail." % (case)) + case_result_list.append({"Name": case, "Result": "SKIP"}) + + thread_objs = [] + left_cases = list(set(all_cases) - set(known_failed_cases)) + index = 0 + max_thread_count = 32 + + while index < len(left_cases): + cur_thread_count = 0 + for thread_obj in thread_objs: + if thread_obj.is_alive(): + cur_thread_count += 1 + while cur_thread_count < max_thread_count and index < len(left_cases): + thread_obj = threading.Thread(target=run_case, args=(left_cases[index], case_result_list, log_dir, )) + thread_obj.start() + time.sleep(0.02) + thread_objs.append(thread_obj) + index += 1 + cur_thread_count += 1 + end_time = time.time() + 30 + while time.time() < end_time: + for thread_obj in thread_objs: + if thread_obj.is_alive(): + break + else: + break + else: + print("Thread timeout") + exit(1) + with open(output_file, 'w') as f: + json.dump(obj=result, fp=f, indent=2) + + failed = 0 + for x in case_result_list: + if x["Result"] == "FAIL": + print("Test case %s failed." % (x["Name"])) + failed = 1 + exit(failed) + + +if __name__ == '__main__': + main() diff --git a/src/spdk/test/iscsi_tgt/calsoft/calsoft.sh b/src/spdk/test/iscsi_tgt/calsoft/calsoft.sh new file mode 100755 index 00000000..1a5c3932 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/calsoft/calsoft.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +delete_tmp_conf_files() { + rm -f /usr/local/etc/its.conf + rm -f /usr/local/etc/auth.conf +} + +if [ ! -d /usr/local/calsoft ]; then + echo "skipping calsoft tests" + exit 0 +fi + +timing_enter calsoft + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" +calsoft_py="$testdir/calsoft.py" + +# Copy the calsoft config file to /usr/local/etc +mkdir -p /usr/local/etc +cp $testdir/its.conf /usr/local/etc/ +cp $testdir/auth.conf /usr/local/etc/ + +# Append target ip to calsoft config +echo "IP=$TARGET_IP" >> /usr/local/etc/its.conf + +timing_enter start_iscsi_tgt + +$ISCSI_APP -m 0x1 --wait-for-rpc & +pid=$! +echo "Process pid: $pid" + +trap "killprocess $pid; delete_tmp_conf_files; exit 1 " SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py load_subsystem_config < $testdir/iscsi.json +$rpc_py start_subsystem_init +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK +$rpc_py construct_malloc_bdev -b MyBdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE +# "MyBdev:0" ==> use MyBdev blockdev for LUN0 +# "1:2" ==> map PortalGroup1 to InitiatorGroup2 +# "64" ==> iSCSI queue depth 64 +# "0 0 0 1" ==> enable CHAP authentication using auth group 1 +$rpc_py construct_target_node Target3 Target3_alias 'MyBdev:0' $PORTAL_TAG:$INITIATOR_TAG 64 -g 1 +sleep 1 + +if [ "$1" ]; then + $calsoft_py "$output_dir" "$1" + failed=$? +else + $calsoft_py "$output_dir" + failed=$? +fi + +trap - SIGINT SIGTERM EXIT + +killprocess $pid +delete_tmp_conf_files +timing_exit calsoft +exit $failed diff --git a/src/spdk/test/iscsi_tgt/calsoft/iscsi.json b/src/spdk/test/iscsi_tgt/calsoft/iscsi.json new file mode 100644 index 00000000..34e44ca0 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/calsoft/iscsi.json @@ -0,0 +1,17 @@ +{ + "subsystem": "iscsi", + "config": [ + { + "params": { + "allow_duplicated_isid": true, + "nop_timeout": 30, + "nop_in_interval": 10, + "discovery_auth_group": 1, + "max_sessions": 256, + "error_recovery_level": 2, + "auth_file": "/usr/local/etc/auth.conf" + }, + "method": "set_iscsi_options" + } + ] +} diff --git a/src/spdk/test/iscsi_tgt/calsoft/its.conf b/src/spdk/test/iscsi_tgt/calsoft/its.conf new file mode 100644 index 00000000..6469dab6 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/calsoft/its.conf @@ -0,0 +1,7 @@ +InitiatorName=iqn.1994-05.com.redhat:b3283535dc3b +TargetName=iqn.2016-06.io.spdk:Target3 +DefaultTime2Retain=20 +DefaultTime2Wait=2 +AuthMethod=CHAP,None +UserName=root +PassWord=tester diff --git a/src/spdk/test/iscsi_tgt/common.sh b/src/spdk/test/iscsi_tgt/common.sh new file mode 100644 index 00000000..1928449b --- /dev/null +++ b/src/spdk/test/iscsi_tgt/common.sh @@ -0,0 +1,50 @@ +# Network configuration +TARGET_INTERFACE="spdk_tgt_int" +INITIATOR_INTERFACE="spdk_init_int" +TARGET_NAMESPACE="spdk_iscsi_ns" +TARGET_NS_CMD="ip netns exec $TARGET_NAMESPACE" + +# iSCSI target configuration +TARGET_IP=10.0.0.1 +INITIATOR_IP=10.0.0.2 +ISCSI_PORT=3260 +NETMASK=$INITIATOR_IP/32 +INITIATOR_TAG=2 +INITIATOR_NAME=ANY +PORTAL_TAG=1 +ISCSI_APP="$TARGET_NS_CMD ./app/iscsi_tgt/iscsi_tgt -i 0" +ISCSI_TEST_CORE_MASK=0xFF + +function create_veth_interfaces() { + # $1 = test type (posix/vpp) + ip netns del $TARGET_NAMESPACE || true + ip link delete $INITIATOR_INTERFACE || true + + trap "cleanup_veth_interfaces $1; exit 1" SIGINT SIGTERM EXIT + + # Create veth (Virtual ethernet) interface pair + ip link add $INITIATOR_INTERFACE type veth peer name $TARGET_INTERFACE + ip addr add $INITIATOR_IP/24 dev $INITIATOR_INTERFACE + ip link set $INITIATOR_INTERFACE up + + # Create and add interface for target to network namespace + ip netns add $TARGET_NAMESPACE + ip link set $TARGET_INTERFACE netns $TARGET_NAMESPACE + + $TARGET_NS_CMD ip link set lo up + $TARGET_NS_CMD ip addr add $TARGET_IP/24 dev $TARGET_INTERFACE + $TARGET_NS_CMD ip link set $TARGET_INTERFACE up + + # Verify connectivity + ping -c 1 $TARGET_IP + ip netns exec $TARGET_NAMESPACE ping -c 1 $INITIATOR_IP +} + +function cleanup_veth_interfaces() { + # $1 = test type (posix/vpp) + + # Cleanup veth interfaces and network namespace + # Note: removing one veth, removes the pair + ip link delete $INITIATOR_INTERFACE + ip netns del $TARGET_NAMESPACE +} diff --git a/src/spdk/test/iscsi_tgt/digests/digests.sh b/src/spdk/test/iscsi_tgt/digests/digests.sh new file mode 100755 index 00000000..675cf1c1 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/digests/digests.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +function node_login_fio_logout() { + for arg in "$@"; do + iscsiadm -m node -p $TARGET_IP:$ISCSI_PORT -o update -n node.conn[0].iscsi.$arg + done + iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT + sleep 1 + $fio_py 512 1 write 2 + $fio_py 512 1 read 2 + iscsiadm -m node --logout -p $TARGET_IP:$ISCSI_PORT + sleep 1 +} + +function iscsi_header_digest_test() { + # Enable HeaderDigest to CRC32C + timing_enter HeaderDigest_enabled + node_login_fio_logout "HeaderDigest -v CRC32C" + timing_exit HeaderDigest_enabled + + # Let iscsi target to decide its preference on + # HeaderDigest based on its capability. + timing_enter preferred + node_login_fio_logout "HeaderDigest -v CRC32C,None" + timing_exit preferred +} + +function iscsi_header_data_digest_test() { + # Only enable HeaderDigest to CRC32C + timing_enter HeaderDigest_enabled + node_login_fio_logout "HeaderDigest -v CRC32C" "DataDigest -v None" + timing_exit HeaderDigest_enabled + + # Only enable DataDigest to CRC32C + timing_enter DataDigest_enabled + node_login_fio_logout "HeaderDigest -v None" "DataDigest -v CRC32C" + timing_exit DataDigest_enabled + + # Let iscsi target to decide its preference on both + # HeaderDigest and DataDigest based on its capability. + timing_enter both_preferred + node_login_fio_logout "HeaderDigest -v CRC32C,None" "DataDigest -v CRC32C,None" + timing_exit both_preferred + + # Enable HeaderDigest and DataDigest both. + timing_enter both_enabled + node_login_fio_logout "HeaderDigest -v CRC32C" "DataDigest -v CRC32C" + timing_exit both_enabled +} + +timing_enter digests + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" +fio_py="$rootdir/scripts/fio.py" + +timing_enter start_iscsi_tgt + +$ISCSI_APP -m $ISCSI_TEST_CORE_MASK --wait-for-rpc & +pid=$! +echo "Process pid: $pid" + +trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py set_iscsi_options -o 30 -a 16 +$rpc_py start_subsystem_init +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK +$rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE +# "Malloc0:0" ==> use Malloc0 blockdev for LUN0 +# "1:2" ==> map PortalGroup1 to InitiatorGroup2 +# "64" ==> iSCSI queue depth 64 +# "-d" ==> disable CHAP authentication +$rpc_py construct_target_node Target3 Target3_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d +sleep 1 + +iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT + +# iscsiadm installed by some Fedora releases loses DataDigest parameter. +# Check and avoid setting DataDigest. +DataDigestAbility=$(iscsiadm -m node -p $TARGET_IP:$ISCSI_PORT | grep DataDigest || true) +if [ "$DataDigestAbility"x = x ]; then + iscsi_header_digest_test +else + iscsi_header_data_digest_test +fi + +trap - SIGINT SIGTERM EXIT + +iscsicleanup +killprocess $pid +timing_exit digests diff --git a/src/spdk/test/iscsi_tgt/ext4test/ext4test.sh b/src/spdk/test/iscsi_tgt/ext4test/ext4test.sh new file mode 100755 index 00000000..b022cfb7 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/ext4test/ext4test.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +if [ ! -z $1 ]; then + DPDK_DIR=$(readlink -f $1) +fi + +timing_enter ext4test + +rpc_py="$rootdir/scripts/rpc.py" + +timing_enter start_iscsi_tgt + +$ISCSI_APP --wait-for-rpc & +pid=$! +echo "Process pid: $pid" + +trap "$rpc_py destruct_split_vbdev Name0n1 || true; killprocess $pid; rm -f $testdir/iscsi.conf; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py set_iscsi_options -o 30 -a 4 -b "iqn.2013-06.com.intel.ch.spdk" +$rpc_py start_subsystem_init +$rootdir/scripts/gen_nvme.sh --json | $rpc_py load_subsystem_config +$rpc_py construct_malloc_bdev 512 4096 --name Malloc0 +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK +$rpc_py construct_error_bdev 'Malloc0' +# "1:2" ==> map PortalGroup1 to InitiatorGroup2 +# "64" ==> iSCSI queue depth 64 +# "-d" ==> disable CHAP authentication +$rpc_py construct_target_node Target0 Target0_alias EE_Malloc0:0 1:2 64 -d +sleep 1 + +iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT +iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT + +trap 'for new_dir in `dir -d /mnt/*dir`; do umount $new_dir; rm -rf $new_dir; done; \ + iscsicleanup; killprocess $pid; rm -f $testdir/iscsi.conf; exit 1' SIGINT SIGTERM EXIT + +sleep 1 + +echo "Test error injection" +$rpc_py bdev_inject_error EE_Malloc0 'all' 'failure' -n 1000 + +dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}') + +set +e +mkfs.ext4 -F /dev/$dev +if [ $? -eq 0 ]; then + echo "mkfs successful - expected failure" + iscsicleanup + killprocess $pid + exit 1 +else + echo "mkfs failed as expected" +fi +set -e + +$rpc_py bdev_inject_error EE_Malloc0 'clear' 'failure' +echo "Error injection test done" + +iscsicleanup + +if [ -z "$NO_NVME" ]; then + $rpc_py construct_split_vbdev Nvme0n1 2 -s 10000 + $rpc_py construct_target_node Target1 Target1_alias Nvme0n1p0:0 1:2 64 -d +fi + +iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT +iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT + +devs=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}') + +for dev in $devs; do + mkfs.ext4 -F /dev/$dev + mkdir -p /mnt/${dev}dir + mount -o sync /dev/$dev /mnt/${dev}dir + + rsync -qav --exclude=".git" --exclude="*.o" $rootdir/ /mnt/${dev}dir/spdk + + make -C /mnt/${dev}dir/spdk clean + (cd /mnt/${dev}dir/spdk && ./configure $config_params) + make -C /mnt/${dev}dir/spdk -j16 + + # Print out space consumed on target device to help decide + # if/when we need to increase the size of the malloc LUN + df -h /dev/$dev + + rm -rf /mnt/${dev}dir/spdk +done + +for dev in $devs; do + umount /mnt/${dev}dir + rm -rf /mnt/${dev}dir + + stats=($(cat /sys/block/$dev/stat)) + echo "" + echo "$dev stats" + printf "READ IO cnt: % 8u merges: % 8u sectors: % 8u ticks: % 8u\n" \ + ${stats[0]} ${stats[1]} ${stats[2]} ${stats[3]} + printf "WRITE IO cnt: % 8u merges: % 8u sectors: % 8u ticks: % 8u\n" \ + ${stats[4]} ${stats[5]} ${stats[6]} ${stats[7]} + printf "in flight: % 8u io ticks: % 8u time in queue: % 8u\n" \ + ${stats[8]} ${stats[9]} ${stats[10]} + echo "" +done + +trap - SIGINT SIGTERM EXIT + +iscsicleanup +$rpc_py destruct_split_vbdev Nvme0n1 +$rpc_py delete_error_bdev EE_Malloc0 + +if [ -z "$NO_NVME" ]; then + $rpc_py delete_nvme_controller Nvme0 +fi + +killprocess $pid +report_test_completion "nightly_iscsi_ext4test" +timing_exit ext4test diff --git a/src/spdk/test/iscsi_tgt/filesystem/filesystem.sh b/src/spdk/test/iscsi_tgt/filesystem/filesystem.sh new file mode 100755 index 00000000..0c530b3b --- /dev/null +++ b/src/spdk/test/iscsi_tgt/filesystem/filesystem.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh +source $rootdir/scripts/common.sh + +timing_enter filesystem + +rpc_py="$rootdir/scripts/rpc.py" +# Remove lvol bdevs and stores. +function remove_backends() { + echo "INFO: Removing lvol bdev" + $rpc_py destroy_lvol_bdev "lvs_0/lbd_0" + + echo "INFO: Removing lvol stores" + $rpc_py destroy_lvol_store -l lvs_0 + + echo "INFO: Removing NVMe" + $rpc_py delete_nvme_controller Nvme0 + + return 0 +} + +timing_enter start_iscsi_tgt + +$ISCSI_APP -m $ISCSI_TEST_CORE_MASK --wait-for-rpc & +pid=$! +echo "Process pid: $pid" + +trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py set_iscsi_options -o 30 -a 16 +$rpc_py start_subsystem_init +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +bdf=$(iter_pci_class_code 01 08 02 | head -1) +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK +$rpc_py construct_nvme_bdev -b "Nvme0" -t "pcie" -a $bdf + +ls_guid=$($rpc_py construct_lvol_store Nvme0n1 lvs_0) +free_mb=$(get_lvs_free_mb "$ls_guid") +# Using maximum 2048MiB to reduce the test time +if [ $free_mb -gt 2048 ]; then + $rpc_py construct_lvol_bdev -u $ls_guid lbd_0 2048 +else + $rpc_py construct_lvol_bdev -u $ls_guid lbd_0 $free_mb +fi +# "lvs_0/lbd_0:0" ==> use lvs_0/lbd_0 blockdev for LUN0 +# "1:2" ==> map PortalGroup1 to InitiatorGroup2 +# "256" ==> iSCSI queue depth 256 +# "-d" ==> disable CHAP authentication +$rpc_py construct_target_node Target1 Target1_alias 'lvs_0/lbd_0:0' $PORTAL_TAG:$INITIATOR_TAG 256 -d +sleep 1 + +iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT +iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT + +trap "remove_backends; umount /mnt/device; rm -rf /mnt/device; iscsicleanup; killprocess $pid; exit 1" SIGINT SIGTERM EXIT + +sleep 1 + +mkdir -p /mnt/device + +dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}') + +parted -s /dev/$dev mklabel msdos +parted -s /dev/$dev mkpart primary '0%' '100%' +sleep 1 + +for fstype in "ext4" "btrfs" "xfs"; do + + if [ "$fstype" == "ext4" ]; then + mkfs.${fstype} -F /dev/${dev}1 + else + mkfs.${fstype} -f /dev/${dev}1 + fi + mount /dev/${dev}1 /mnt/device + if [ $RUN_NIGHTLY -eq 1 ]; then + fio -filename=/mnt/device/test -direct=1 -iodepth 64 -thread=1 -invalidate=1 -rw=randwrite -ioengine=libaio -bs=4k \ + -size=1024M -name=job0 + umount /mnt/device + + iscsiadm -m node --logout + sleep 1 + iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT + sleep 1 + dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}') + mount -o rw /dev/${dev}1 /mnt/device + if [ -f "/mnt/device/test" ]; then + echo "File existed." + fio -filename=/mnt/device/test -direct=1 -iodepth 64 -thread=1 -invalidate=1 -rw=randread \ + -ioengine=libaio -bs=4k -runtime=20 -time_based=1 -name=job0 + else + echo "File doesn't exist." + exit 1 + fi + + rm -rf /mnt/device/test + umount /mnt/device + else + touch /mnt/device/aaa + umount /mnt/device + + iscsiadm -m node --logout + sleep 1 + iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT + sleep 1 + dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}') + mount -o rw /dev/${dev}1 /mnt/device + + if [ -f "/mnt/device/aaa" ]; then + echo "File existed." + else + echo "File doesn't exist." + exit 1 + fi + + rm -rf /mnt/device/aaa + umount /mnt/device + fi +done + +rm -rf /mnt/device + +trap - SIGINT SIGTERM EXIT + +iscsicleanup +remove_backends +killprocess $pid +timing_exit filesystem diff --git a/src/spdk/test/iscsi_tgt/fio/fio.sh b/src/spdk/test/iscsi_tgt/fio/fio.sh new file mode 100755 index 00000000..5fdeaed2 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/fio/fio.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +delete_tmp_files() { + rm -f $testdir/iscsi.conf + rm -f ./local-job0-0-verify.state +} + +function running_config() { + # generate a config file from the running iscsi_tgt + # running_config.sh will leave the file at /tmp/iscsi.conf + $testdir/running_config.sh $pid + sleep 1 + + # now start iscsi_tgt again using the generated config file + # keep the same iscsiadm configuration to confirm that the + # config file matched the running configuration + killprocess $pid + trap "iscsicleanup; delete_tmp_files; exit 1" SIGINT SIGTERM EXIT + + timing_enter start_iscsi_tgt2 + + $ISCSI_APP -c /tmp/iscsi.conf & + pid=$! + echo "Process pid: $pid" + trap "iscsicleanup; killprocess $pid; delete_tmp_files; exit 1" SIGINT SIGTERM EXIT + waitforlisten $pid + echo "iscsi_tgt is listening. Running tests..." + + timing_exit start_iscsi_tgt2 + + sleep 1 + $fio_py 4096 1 randrw 5 +} + +if [ -z "$TARGET_IP" ]; then + echo "TARGET_IP not defined in environment" + exit 1 +fi + +if [ -z "$INITIATOR_IP" ]; then + echo "INITIATOR_IP not defined in environment" + exit 1 +fi + +timing_enter fio + +cp $testdir/iscsi.conf.in $testdir/iscsi.conf + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=4096 + +rpc_py="$rootdir/scripts/rpc.py" +fio_py="$rootdir/scripts/fio.py" + +timing_enter start_iscsi_tgt + +$ISCSI_APP -c $testdir/iscsi.conf & +pid=$! +echo "Process pid: $pid" + +trap "killprocess $pid; rm -f $testdir/iscsi.conf; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK +# Create a RAID-0 bdev from two malloc bdevs +malloc_bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) " +malloc_bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" +$rpc_py construct_raid_bdev -n raid0 -s 64 -r 0 -b "$malloc_bdevs" +# "raid0:0" ==> use raid0 blockdev for LUN0 +# "1:2" ==> map PortalGroup1 to InitiatorGroup2 +# "64" ==> iSCSI queue depth 64 +# "-d" ==> disable CHAP authentication +$rpc_py construct_target_node Target3 Target3_alias 'raid0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d +sleep 1 + +iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT +iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT + +trap "iscsicleanup; killprocess $pid; delete_tmp_files; exit 1" SIGINT SIGTERM EXIT + +sleep 1 +$fio_py 4096 1 randrw 1 verify +$fio_py 131072 32 randrw 1 verify +$fio_py 524288 128 randrw 1 verify + +if [ $RUN_NIGHTLY -eq 1 ]; then + $fio_py 4096 1 write 300 verify + + # Run the running_config test which will generate a config file from the + # running iSCSI target, then kill and restart the iSCSI target using the + # generated config file + # Temporarily disabled + # running_config +fi + +# Start hotplug test case. +$fio_py 1048576 128 rw 10 & +fio_pid=$! + +sleep 3 +set +e +# Delete raid0, Malloc0, Malloc1 blockdevs +$rpc_py destroy_raid_bdev 'raid0' +$rpc_py delete_malloc_bdev 'Malloc0' +$rpc_py delete_malloc_bdev 'Malloc1' + +wait $fio_pid +fio_status=$? + +if [ $fio_status -eq 0 ]; then + echo "iscsi hotplug test: fio successful - expected failure" + set -e + exit 1 +else + echo "iscsi hotplug test: fio failed as expected" +fi + +set -e + +iscsicleanup +$rpc_py delete_target_node 'iqn.2016-06.io.spdk:Target3' + +delete_tmp_files + +trap - SIGINT SIGTERM EXIT + +killprocess $pid +#echo 1 > /sys/bus/pci/rescan +#sleep 2 +$rootdir/scripts/setup.sh + +timing_exit fio diff --git a/src/spdk/test/iscsi_tgt/fio/iscsi.conf.in b/src/spdk/test/iscsi_tgt/fio/iscsi.conf.in new file mode 100644 index 00000000..be06af58 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/fio/iscsi.conf.in @@ -0,0 +1,16 @@ +[Global] + +[iSCSI] + NodeBase "iqn.2016-06.io.spdk" + AuthFile /usr/local/etc/spdk/auth.conf + Timeout 30 + DiscoveryAuthMethod Auto + MaxSessions 16 + ImmediateData Yes + ErrorRecoveryLevel 0 + +[Nvme] + RetryCount 4 + ActionOnTimeout None + AdminPollRate 100000 + HotplugEnable Yes diff --git a/src/spdk/test/iscsi_tgt/fio/running_config.sh b/src/spdk/test/iscsi_tgt/fio/running_config.sh new file mode 100755 index 00000000..ea59eb5a --- /dev/null +++ b/src/spdk/test/iscsi_tgt/fio/running_config.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -xe + +pid="$1" + +if [[ -z "$pid" ]]; then + echo "usage: $0 pid" + exit 1 +fi + +# delete any existing temporary iscsi.conf files +rm -f /tmp/iscsi.conf* + +kill -USR1 "$pid" + +if [ ! -f $(ls /tmp/iscsi.conf.*) ]; then + echo "iscsi_tgt did not generate config file" + exit 1 +fi + +mv $(ls /tmp/iscsi.conf.*) /tmp/iscsi.conf diff --git a/src/spdk/test/iscsi_tgt/initiator/initiator.sh b/src/spdk/test/iscsi_tgt/initiator/initiator.sh new file mode 100755 index 00000000..8f3104a4 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/initiator/initiator.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +timing_enter initiator + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" + +timing_enter start_iscsi_tgt + +# Start the iSCSI target without using stub +# Reason: Two SPDK processes will be started +$ISCSI_APP -m 0x2 -p 1 -s 512 --wait-for-rpc & +pid=$! +echo "iSCSI target launched. pid: $pid" +trap "killprocess $pid;exit 1" SIGINT SIGTERM EXIT +waitforlisten $pid +$rpc_py set_iscsi_options -o 30 -a 4 +$rpc_py start_subsystem_init +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK +$rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE +# "Malloc0:0" ==> use Malloc0 blockdev for LUN0 +# "1:2" ==> map PortalGroup1 to InitiatorGroup2 +# "64" ==> iSCSI queue depth 64 +# "-d" ==> disable CHAP authentication +$rpc_py construct_target_node disk1 disk1_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 256 -d +sleep 1 +trap "killprocess $pid; rm -f $testdir/bdev.conf; exit 1" SIGINT SIGTERM EXIT + +# Prepare config file for iSCSI initiator +echo "[iSCSI_Initiator]" > $testdir/bdev.conf +echo " URL iscsi://$TARGET_IP/iqn.2016-06.io.spdk:disk1/0 iSCSI0" >> $testdir/bdev.conf +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w verify -t 5 -s 512 +if [ $RUN_NIGHTLY -eq 1 ]; then + $rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w unmap -t 5 -s 512 + $rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w flush -t 5 -s 512 + $rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w reset -t 10 -s 512 +fi +rm -f $testdir/bdev.conf + +trap - SIGINT SIGTERM EXIT + +killprocess $pid + +report_test_completion "iscsi_initiator" +timing_exit initiator diff --git a/src/spdk/test/iscsi_tgt/ip_migration/ip_migration.sh b/src/spdk/test/iscsi_tgt/ip_migration/ip_migration.sh new file mode 100755 index 00000000..25332ff8 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/ip_migration/ip_migration.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +rpc_py="$rootdir/scripts/rpc.py" +fio_py="$rootdir/scripts/fio.py" + +# Namespaces are NOT used here on purpose. This test requires changes to detect +# ifc_index for interface that was put into namespace. Needed for add_ip_address. +ISCSI_APP="$rootdir/app/iscsi_tgt/iscsi_tgt" +NETMASK=127.0.0.0/24 +MIGRATION_ADDRESS=127.0.0.2 + +function kill_all_iscsi_target() { + for ((i = 0; i < 2; i++)); do + rpc_addr="/var/tmp/spdk${i}.sock" + $rpc_py -s $rpc_addr kill_instance SIGTERM + done +} + +function rpc_config() { + # $1 = RPC server address + # $2 = Netmask + $rpc_py -s $1 add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $2 + $rpc_py -s $1 construct_malloc_bdev 64 512 +} + +function rpc_add_target_node() { + $rpc_py -s $1 add_ip_address 1 $MIGRATION_ADDRESS + $rpc_py -s $1 add_portal_group $PORTAL_TAG $MIGRATION_ADDRESS:$ISCSI_PORT + $rpc_py -s $1 construct_target_node target1 target1_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d +} + +timing_enter ip_migration + +echo "Running ip migration tests" +for ((i = 0; i < 2; i++)); do + timing_enter start_iscsi_tgt_$i + + rpc_addr="/var/tmp/spdk${i}.sock" + + # TODO: run the different iSCSI instances on non-overlapping CPU masks + $ISCSI_APP -r $rpc_addr -s 1000 -i $i -m $ISCSI_TEST_CORE_MASK --wait-for-rpc & + pid=$! + echo "Process pid: $pid" + + trap "kill_all_iscsi_target; exit 1" SIGINT SIGTERM EXIT + + waitforlisten $pid $rpc_addr + $rpc_py -s $rpc_addr set_iscsi_options -o 30 -a 64 + $rpc_py -s $rpc_addr start_subsystem_init + echo "iscsi_tgt is listening. Running tests..." + + timing_exit start_iscsi_tgt_$i + + rpc_config $rpc_addr $NETMASK + trap "kill_all_iscsi_target; exit 1" \ + SIGINT SIGTERM EXIT +done + +rpc_first_addr="/var/tmp/spdk0.sock" +rpc_add_target_node $rpc_first_addr + +sleep 1 +iscsiadm -m discovery -t sendtargets -p $MIGRATION_ADDRESS:$ISCSI_PORT +sleep 1 +iscsiadm -m node --login -p $MIGRATION_ADDRESS:$ISCSI_PORT + +# fio tests for multi-process +sleep 1 +$fio_py 4096 32 randrw 10 & +fiopid=$! +sleep 5 + +$rpc_py -s $rpc_first_addr kill_instance SIGTERM + +rpc_second_addr="/var/tmp/spdk1.sock" +rpc_add_target_node $rpc_second_addr + +wait $fiopid + +trap - SIGINT SIGTERM EXIT + +iscsicleanup + +$rpc_py -s $rpc_second_addr kill_instance SIGTERM +report_test_completion "nightly_iscsi_ip_migration" +timing_exit ip_migration diff --git a/src/spdk/test/iscsi_tgt/iscsi_tgt.sh b/src/spdk/test/iscsi_tgt/iscsi_tgt.sh new file mode 100755 index 00000000..fbf9f239 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/iscsi_tgt.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) +source $rootdir/test/common/autotest_common.sh + +if [ ! $(uname -s) = Linux ]; then + exit 0 +fi + +source $rootdir/test/iscsi_tgt/common.sh + +timing_enter iscsi_tgt + +# $1 = test type (posix/vpp) +if [ "$1" == "posix" ] || [ "$1" == "vpp" ]; then + TEST_TYPE=$1 +else + echo "No iSCSI test type specified" + exit 1 +fi + +# Network configuration +create_veth_interfaces $TEST_TYPE + +# ISCSI_TEST_CORE_MASK is the biggest core mask specified by +# any of the iscsi_tgt tests. Using this mask for the stub +# ensures that if this mask spans CPU sockets, that we will +# allocate memory from both sockets. The stub will *not* +# run anything on the extra cores (and will sleep on master +# core 0) so there is no impact to the iscsi_tgt tests by +# specifying the bigger core mask. +start_stub "-s 2048 -i 0 -m $ISCSI_TEST_CORE_MASK" +trap "kill_stub; cleanup_veth_interfaces $TEST_TYPE; exit 1" SIGINT SIGTERM EXIT + +run_test suite ./test/iscsi_tgt/calsoft/calsoft.sh +run_test suite ./test/iscsi_tgt/filesystem/filesystem.sh +run_test suite ./test/iscsi_tgt/reset/reset.sh +run_test suite ./test/iscsi_tgt/rpc_config/rpc_config.sh $TEST_TYPE +run_test suite ./test/iscsi_tgt/lvol/iscsi_lvol.sh +run_test suite ./test/iscsi_tgt/fio/fio.sh +run_test suite ./test/iscsi_tgt/qos/qos.sh + +if [ $RUN_NIGHTLY -eq 1 ]; then + if [ $SPDK_TEST_PMDK -eq 1 ]; then + run_test suite ./test/iscsi_tgt/pmem/iscsi_pmem.sh 4096 10 + fi + run_test suite ./test/iscsi_tgt/ip_migration/ip_migration.sh + run_test suite ./test/iscsi_tgt/ext4test/ext4test.sh + run_test suite ./test/iscsi_tgt/digests/digests.sh +fi +if [ $SPDK_TEST_RBD -eq 1 ]; then + run_test suite ./test/iscsi_tgt/rbd/rbd.sh +fi + +trap "cleanup_veth_interfaces $TEST_TYPE; exit 1" SIGINT SIGTERM EXIT +kill_stub + +if [ $SPDK_TEST_NVMF -eq 1 ]; then + # TODO: enable remote NVMe controllers with multi-process so that + # we can use the stub for this test + # Test configure remote NVMe device from rpc and conf file + run_test suite ./test/iscsi_tgt/nvme_remote/fio_remote_nvme.sh +fi + +if [ $RUN_NIGHTLY -eq 1 ]; then + run_test suite ./test/iscsi_tgt/multiconnection/multiconnection.sh +fi + +if [ $SPDK_TEST_ISCSI_INITIATOR -eq 1 ]; then + run_test suite ./test/iscsi_tgt/initiator/initiator.sh + run_test suite ./test/iscsi_tgt/bdev_io_wait/bdev_io_wait.sh +fi + +cleanup_veth_interfaces $TEST_TYPE +trap - SIGINT SIGTERM EXIT +timing_exit iscsi_tgt diff --git a/src/spdk/test/iscsi_tgt/iscsijson/json_config.sh b/src/spdk/test/iscsi_tgt/iscsijson/json_config.sh new file mode 100755 index 00000000..cec662b7 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/iscsijson/json_config.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -xe +ISCSI_JSON_DIR=$(readlink -f $(dirname $0)) +. $ISCSI_JSON_DIR/../../json_config/common.sh +. $JSON_DIR/../iscsi_tgt/common.sh +base_iscsi_config=$JSON_DIR/base_iscsi_config.json +last_iscsi_config=$JSON_DIR/last_iscsi_config.json +rpc_py="$spdk_rpc_py" +clear_config_py="$spdk_clear_config_py" +trap 'on_error_exit "${FUNCNAME}" "${LINENO}"; rm -f $base_iscsi_config $last_iscsi_config' ERR + +timing_enter iscsi_json_config +run_spdk_tgt +$rpc_py start_subsystem_init + +timing_enter iscsi_json_config_create_setup +$rpc_py add_portal_group $PORTAL_TAG 127.0.0.1:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK +$rpc_py construct_malloc_bdev 64 4096 --name Malloc0 +$rpc_py construct_target_node Target3 Target3_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d +$rpc_py save_config > $base_iscsi_config +timing_exit iscsi_json_config_create_setup + +timing_enter iscsi_json_config_test +test_json_config +timing_exit iscsi_json_config_test + +timing_enter iscsi_json_config_restart_spdk +$clear_config_py clear_config +kill_targets +run_spdk_tgt +$rpc_py load_config < $base_iscsi_config +$rpc_py save_config > $last_iscsi_config +timing_exit iscsi_json_config_restart_spdk + +json_diff $base_iscsi_config $last_iscsi_config + +$clear_config_py clear_config +kill_targets +rm -f $base_iscsi_config $last_iscsi_config + +timing_exit iscsi_json_config +report_test_completion iscsi_json_config diff --git a/src/spdk/test/iscsi_tgt/lvol/iscsi_lvol.sh b/src/spdk/test/iscsi_tgt/lvol/iscsi_lvol.sh new file mode 100755 index 00000000..c3df3af7 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/lvol/iscsi_lvol.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +timing_enter iscsi_lvol + +MALLOC_BDEV_SIZE=128 +MALLOC_BLOCK_SIZE=512 +if [ $RUN_NIGHTLY -eq 1 ]; then + NUM_LVS=10 + NUM_LVOL=10 +else + NUM_LVS=2 + NUM_LVOL=2 +fi + +rpc_py="$rootdir/scripts/rpc.py" +fio_py="$rootdir/scripts/fio.py" + +timing_enter start_iscsi_tgt + +$ISCSI_APP -m $ISCSI_TEST_CORE_MASK --wait-for-rpc & +pid=$! +echo "Process pid: $pid" + +trap "iscsicleanup; killprocess $pid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py set_iscsi_options -o 30 -a 16 +$rpc_py start_subsystem_init +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +timing_enter setup +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +# Create the first LVS from a Raid-0 bdev, which is created from two malloc bdevs +# Create remaining LVSs from a malloc bdev, respectively +for i in $(seq 1 $NUM_LVS); do + INITIATOR_TAG=$((i + 2)) + $rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK + if [ $i -eq 1 ]; then + # construct RAID bdev and put its name in $bdev + malloc_bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) " + malloc_bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + $rpc_py construct_raid_bdev -n raid0 -s 64 -r 0 -b "$malloc_bdevs" + bdev="raid0" + else + # construct malloc bdev and put its name in $bdev + bdev=$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) + fi + ls_guid=$($rpc_py construct_lvol_store $bdev lvs_$i -c 1048576) + LUNs="" + for j in $(seq 1 $NUM_LVOL); do + lb_name=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_$j 10) + LUNs+="$lb_name:$((j - 1)) " + done + $rpc_py construct_target_node Target$i Target${i}_alias "$LUNs" "1:$INITIATOR_TAG" 256 -d +done +timing_exit setup + +sleep 1 + +timing_enter discovery +iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT +iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT +timing_exit discovery + +timing_enter fio +$fio_py 131072 8 randwrite 10 verify +timing_exit fio + +rm -f ./local-job0-0-verify.state +trap - SIGINT SIGTERM EXIT + +rm -f ./local-job* +iscsicleanup +killprocess $pid +timing_exit iscsi_lvol diff --git a/src/spdk/test/iscsi_tgt/multiconnection/multiconnection.sh b/src/spdk/test/iscsi_tgt/multiconnection/multiconnection.sh new file mode 100755 index 00000000..b793d751 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/multiconnection/multiconnection.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +rpc_py="$rootdir/scripts/rpc.py" +fio_py="$rootdir/scripts/fio.py" + +CONNECTION_NUMBER=30 + +# Remove lvol bdevs and stores. +function remove_backends() { + echo "INFO: Removing lvol bdevs" + for i in $(seq 1 $CONNECTION_NUMBER); do + lun="lvs0/lbd_$i" + $rpc_py destroy_lvol_bdev $lun + echo -e "\tINFO: lvol bdev $lun removed" + done + sleep 1 + + echo "INFO: Removing lvol stores" + $rpc_py destroy_lvol_store -l lvs0 + echo "INFO: lvol store lvs0 removed" + + echo "INFO: Removing NVMe" + $rpc_py delete_nvme_controller Nvme0 + + return 0 +} + +set -e +timing_enter multiconnection + +timing_enter start_iscsi_tgt +# Start the iSCSI target without using stub. +$ISCSI_APP --wait-for-rpc & +iscsipid=$! +echo "iSCSI target launched. pid: $iscsipid" +trap "remove_backends; iscsicleanup; killprocess $iscsipid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $iscsipid +$rpc_py set_iscsi_options -o 30 -a 128 +$rpc_py start_subsystem_init +$rootdir/scripts/gen_nvme.sh --json | $rpc_py load_subsystem_config +timing_exit start_iscsi_tgt + +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK + +echo "Creating an iSCSI target node." +ls_guid=$($rpc_py construct_lvol_store "Nvme0n1" "lvs0" -c 1048576) + +# Assign even size for each lvol_bdev. +get_lvs_free_mb $ls_guid +lvol_bdev_size=$(($free_mb / $CONNECTION_NUMBER)) +for i in $(seq 1 $CONNECTION_NUMBER); do + $rpc_py construct_lvol_bdev -u $ls_guid lbd_$i $lvol_bdev_size +done + +for i in $(seq 1 $CONNECTION_NUMBER); do + lun="lvs0/lbd_$i:0" + $rpc_py construct_target_node Target$i Target${i}_alias "$lun" $PORTAL_TAG:$INITIATOR_TAG 256 -d +done +sleep 1 + +echo "Logging into iSCSI target." +iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT +iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT +sleep 1 + +echo "Running FIO" +$fio_py 131072 64 randrw 5 +$fio_py 262144 16 randwrite 10 +sync + +trap - SIGINT SIGTERM EXIT + +rm -f ./local-job* +iscsicleanup +remove_backends +killprocess $iscsipid +timing_exit multiconnection diff --git a/src/spdk/test/iscsi_tgt/nvme_remote/fio_remote_nvme.sh b/src/spdk/test/iscsi_tgt/nvme_remote/fio_remote_nvme.sh new file mode 100755 index 00000000..2f00b7a5 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/nvme_remote/fio_remote_nvme.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +set -e + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh +source $rootdir/test/iscsi_tgt/common.sh + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +rpc_py="$rootdir/scripts/rpc.py" +fio_py="$rootdir/scripts/fio.py" + +NVMF_PORT=4420 + +# Namespaces are NOT used here on purpose. Rxe_cfg utilility used for NVMf tests do not support namespaces. +TARGET_IP=127.0.0.1 +INITIATOR_IP=127.0.0.1 +NETMASK=$INITIATOR_IP/32 + +function run_nvme_remote() { + echo "now use $1 method to run iscsi tgt." + + # Start the iSCSI target without using stub + iscsi_rpc_addr="/var/tmp/spdk-iscsi.sock" + ISCSI_APP="$rootdir/app/iscsi_tgt/iscsi_tgt" + $ISCSI_APP -r "$iscsi_rpc_addr" -m 0x1 -p 0 -s 512 --wait-for-rpc & + iscsipid=$! + echo "iSCSI target launched. pid: $iscsipid" + trap "killprocess $iscsipid; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT + waitforlisten $iscsipid "$iscsi_rpc_addr" + $rpc_py -s "$iscsi_rpc_addr" set_iscsi_options -o 30 -a 16 + $rpc_py -s "$iscsi_rpc_addr" start_subsystem_init + if [ "$1" = "remote" ]; then + $rpc_py -s $iscsi_rpc_addr construct_nvme_bdev -b "Nvme0" -t "rdma" -f "ipv4" -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT -n nqn.2016-06.io.spdk:cnode1 + fi + + echo "iSCSI target has started." + + timing_exit start_iscsi_tgt + + echo "Creating an iSCSI target node." + $rpc_py -s "$iscsi_rpc_addr" add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT + $rpc_py -s "$iscsi_rpc_addr" add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK + if [ "$1" = "local" ]; then + $rpc_py -s "$iscsi_rpc_addr" construct_nvme_bdev -b "Nvme0" -t "rdma" -f "ipv4" -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT -n nqn.2016-06.io.spdk:cnode1 + fi + $rpc_py -s "$iscsi_rpc_addr" construct_target_node Target1 Target1_alias 'Nvme0n1:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d + sleep 1 + + echo "Logging in to iSCSI target." + iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT + iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT +} + +timing_enter nvme_remote + +# Start the NVMf target +NVMF_APP="$rootdir/app/nvmf_tgt/nvmf_tgt" +$NVMF_APP -m 0x2 -p 1 -s 512 --wait-for-rpc & +nvmfpid=$! +echo "NVMf target launched. pid: $nvmfpid" +trap "killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT +waitforlisten $nvmfpid +$rpc_py start_subsystem_init +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +echo "NVMf target has started." +bdevs=$($rpc_py construct_malloc_bdev 64 512) +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 +for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev +done +echo "NVMf subsystem created." + +timing_enter start_iscsi_tgt + +run_nvme_remote "local" + +trap "iscsicleanup; killprocess $iscsipid; killprocess $nvmfpid; \ + rm -f ./local-job0-0-verify.state; exit 1" SIGINT SIGTERM EXIT +sleep 1 + +echo "Running FIO" +$fio_py 4096 1 randrw 1 verify + +rm -f ./local-job0-0-verify.state +iscsicleanup +killprocess $iscsipid + +run_nvme_remote "remote" + +echo "Running FIO" +$fio_py 4096 1 randrw 1 verify + +rm -f ./local-job0-0-verify.state +trap - SIGINT SIGTERM EXIT + +iscsicleanup +killprocess $iscsipid +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 +killprocess $nvmfpid + +report_test_completion "iscsi_nvme_remote" +timing_exit nvme_remote diff --git a/src/spdk/test/iscsi_tgt/pmem/iscsi_pmem.sh b/src/spdk/test/iscsi_tgt/pmem/iscsi_pmem.sh new file mode 100755 index 00000000..063bb695 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/pmem/iscsi_pmem.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +BLOCKSIZE=$1 +RUNTIME=$2 +PMEM_BDEVS="" +PMEM_SIZE=128 +PMEM_BLOCK_SIZE=512 +TGT_NR=10 +PMEM_PER_TGT=1 +rpc_py="$rootdir/scripts/rpc.py" +fio_py="$rootdir/scripts/fio.py" + +timing_enter iscsi_pmem + +timing_enter start_iscsi_target +$ISCSI_APP -m $ISCSI_TEST_CORE_MASK --wait-for-rpc & +pid=$! +echo "Process pid: $pid" + +trap "iscsicleanup; killprocess $pid; rm -f /tmp/pool_file*; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py set_iscsi_options -o 30 -a 16 +$rpc_py start_subsystem_init +echo "iscsi_tgt is listening. Running tests..." +timing_exit start_iscsi_target + +timing_enter setup +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +for i in $(seq 1 $TGT_NR); do + INITIATOR_TAG=$((i + 1)) + $rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK + + luns="" + for j in $(seq 1 $PMEM_PER_TGT); do + $rpc_py create_pmem_pool /tmp/pool_file${i}_${j} $PMEM_SIZE $PMEM_BLOCK_SIZE + bdevs_name="$($rpc_py construct_pmem_bdev -n pmem${i}_${j} /tmp/pool_file${i}_${j})" + PMEM_BDEVS+="$bdevs_name " + luns+="$bdevs_name:$((j - 1)) " + done + $rpc_py construct_target_node Target$i Target${i}_alias "$luns" "1:$INITIATOR_TAG " 256 -d +done +timing_exit setup +sleep 1 + +timing_enter discovery +iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT +iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT +timing_exit discovery + +timing_enter fio_test +$fio_py $BLOCKSIZE 64 randwrite $RUNTIME verify +timing_exit fio_test + +iscsicleanup + +for pmem in $PMEM_BDEVS; do + $rpc_py delete_pmem_bdev $pmem +done + +for i in $(seq 1 $TGT_NR); do + for c in $(seq 1 $PMEM_PER_TGT); do + $rpc_py delete_pmem_pool /tmp/pool_file${i}_${c} + done +done + +trap - SIGINT SIGTERM EXIT + +rm -f ./local-job* +rm -f /tmp/pool_file* +killprocess $pid +report_test_completion "nightly_iscsi_pmem" +timing_exit iscsi_pmem diff --git a/src/spdk/test/iscsi_tgt/qos/qos.sh b/src/spdk/test/iscsi_tgt/qos/qos.sh new file mode 100755 index 00000000..da12f8f8 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/qos/qos.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +function check_qos_works_well() { + local enable_limit=$1 + local iops_limit=$2 + local retval=0 + + start_io_count=$($rpc_py get_bdevs_iostat -b $3 | jq -r '.[1].num_read_ops') + $fio_py 512 64 randread 5 + end_io_count=$($rpc_py get_bdevs_iostat -b $3 | jq -r '.[1].num_read_ops') + + read_iops=$(((end_io_count-start_io_count)/5)) + + if [ $enable_limit = true ]; then + retval=$(echo "$iops_limit*0.9 < $read_iops && $read_iops < $iops_limit*1.01" | bc) + if [ $retval -eq 0 ]; then + echo "Failed to limit the io read rate of malloc bdev by qos" + exit 1 + fi + else + retval=$(echo "$read_iops > $iops_limit" | bc) + if [ $retval -eq 0 ]; then + echo "$read_iops less than $iops_limit - expected greater than" + exit 1 + fi + fi +} + +if [ -z "$TARGET_IP" ]; then + echo "TARGET_IP not defined in environment" + exit 1 +fi + +if [ -z "$INITIATOR_IP" ]; then + echo "INITIATOR_IP not defined in environment" + exit 1 +fi + +timing_enter qos + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 +IOPS_LIMIT=20000 +rpc_py="$rootdir/scripts/rpc.py" +fio_py="$rootdir/scripts/fio.py" + +timing_enter start_iscsi_tgt + +$ISCSI_APP & +pid=$! +echo "Process pid: $pid" +trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT +waitforlisten $pid +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK +$rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE +# "Malloc0:0" ==> use Malloc0 blockdev for LUN0 +# "1:2" ==> map PortalGroup1 to InitiatorGroup2 +# "64" ==> iSCSI queue depth 64 +# "-d" ==> disable CHAP authentication +$rpc_py construct_target_node Target1 Target1_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d +sleep 1 + +iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT +iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT + +trap "iscsicleanup; killprocess $pid; exit 1" SIGINT SIGTERM EXIT + +# Limit the I/O rate by RPC, then confirm the observed rate matches. +$rpc_py set_bdev_qos_limit Malloc0 --rw_ios_per_sec $IOPS_LIMIT +check_qos_works_well true $IOPS_LIMIT Malloc0 + +# Now disable the rate limiting, and confirm the observed rate is not limited anymore. +$rpc_py set_bdev_qos_limit Malloc0 --rw_ios_per_sec 0 +check_qos_works_well false $IOPS_LIMIT Malloc0 + +# Limit the I/O rate again. +$rpc_py set_bdev_qos_limit Malloc0 --rw_ios_per_sec $IOPS_LIMIT +check_qos_works_well true $IOPS_LIMIT Malloc0 +echo "I/O rate limiting tests successful" + +iscsicleanup +$rpc_py delete_target_node 'iqn.2016-06.io.spdk:Target1' + +rm -f ./local-job0-0-verify.state +trap - SIGINT SIGTERM EXIT +killprocess $pid + +timing_exit qos diff --git a/src/spdk/test/iscsi_tgt/rbd/rbd.sh b/src/spdk/test/iscsi_tgt/rbd/rbd.sh new file mode 100755 index 00000000..27d86159 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/rbd/rbd.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +if ! hash ceph; then + echo "Ceph not detected on this system; skipping RBD tests" + exit 0 +fi + +timing_enter rbd_setup +rbd_setup $TARGET_IP $TARGET_NAMESPACE +trap "rbd_cleanup; exit 1" SIGINT SIGTERM EXIT +timing_exit rbd_setup + +timing_enter rbd + +rpc_py="$rootdir/scripts/rpc.py" +fio_py="$rootdir/scripts/fio.py" + +timing_enter start_iscsi_tgt + +$ISCSI_APP -m $ISCSI_TEST_CORE_MASK --wait-for-rpc & +pid=$! + +trap "killprocess $pid; rbd_cleanup; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py set_iscsi_options -o 30 -a 16 +$rpc_py start_subsystem_init +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK +rbd_bdev="$($rpc_py construct_rbd_bdev $RBD_POOL $RBD_NAME 4096)" +$rpc_py get_bdevs +# "Ceph0:0" ==> use Ceph0 blockdev for LUN0 +# "1:2" ==> map PortalGroup1 to InitiatorGroup2 +# "64" ==> iSCSI queue depth 64 +# "-d" ==> disable CHAP authentication +$rpc_py construct_target_node Target3 Target3_alias 'Ceph0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d +sleep 1 + +iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT +iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT + +trap "iscsicleanup; killprocess $pid; rbd_cleanup; exit 1" SIGINT SIGTERM EXIT + +sleep 1 +$fio_py 4096 1 randrw 1 verify +$fio_py 131072 32 randrw 1 verify + +rm -f ./local-job0-0-verify.state + +trap - SIGINT SIGTERM EXIT + +iscsicleanup +$rpc_py delete_rbd_bdev $rbd_bdev +killprocess $pid +rbd_cleanup + +report_test_completion "iscsi_rbd" +timing_exit rbd diff --git a/src/spdk/test/iscsi_tgt/reset/reset.sh b/src/spdk/test/iscsi_tgt/reset/reset.sh new file mode 100755 index 00000000..0e986ac5 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/reset/reset.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +set -xe + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +timing_enter reset + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" +fio_py="$rootdir/scripts/fio.py" + +if ! hash sg_reset; then + exit 1 +fi + +timing_enter start_iscsi_tgt + +$ISCSI_APP --wait-for-rpc & +pid=$! +echo "Process pid: $pid" + +trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py set_iscsi_options -o 30 -a 16 +$rpc_py start_subsystem_init +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT +$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK +$rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE +# "Malloc0:0" ==> use Malloc0 blockdev for LUN0 +# "1:2" ==> map PortalGroup1 to InitiatorGroup2 +# "64" ==> iSCSI queue depth 64 +# "-d" ==> disable CHAP authentication +$rpc_py construct_target_node Target3 Target3_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d +sleep 1 + +iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT +iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT +sleep 1 +dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}') + +sleep 1 +$fio_py 512 1 read 60 & +fiopid=$! +echo "FIO pid: $fiopid" + +trap "iscsicleanup; killprocess $pid; killprocess $fiopid; exit 1" SIGINT SIGTERM EXIT + +# Do 3 resets while making sure iscsi_tgt and fio are still running +for i in 1 2 3; do + sleep 1 + kill -s 0 $pid + kill -s 0 $fiopid + sg_reset -d /dev/$dev + sleep 1 + kill -s 0 $pid + kill -s 0 $fiopid +done + +kill $fiopid +wait $fiopid || true + +trap - SIGINT SIGTERM EXIT + +iscsicleanup +killprocess $pid +timing_exit reset diff --git a/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.py b/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.py new file mode 100755 index 00000000..03647c47 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.py @@ -0,0 +1,502 @@ +#!/usr/bin/env python3 + + +import os +import os.path +import re +import sys +import time +import json +import random +from subprocess import check_call, call, check_output, Popen, PIPE, CalledProcessError + +if (len(sys.argv) == 8): + target_ip = sys.argv[2] + initiator_ip = sys.argv[3] + port = sys.argv[4] + netmask = sys.argv[5] + namespace = sys.argv[6] + test_type = sys.argv[7] + +ns_cmd = 'ip netns exec ' + namespace +other_ip = '127.0.0.6' +initiator_name = 'ANY' +portal_tag = '1' +initiator_tag = '1' + +rpc_param = { + 'target_ip': target_ip, + 'initiator_ip': initiator_ip, + 'port': port, + 'initiator_name': initiator_name, + 'netmask': netmask, + 'lun_total': 3, + 'malloc_bdev_size': 64, + 'malloc_block_size': 512, + 'queue_depth': 64, + 'target_name': 'Target3', + 'alias_name': 'Target3_alias', + 'disable_chap': True, + 'mutual_chap': False, + 'require_chap': False, + 'chap_group': 0, + 'header_digest': False, + 'data_digest': False, + 'trace_flag': 'rpc', + 'cpumask': 0x1 +} + + +class RpcException(Exception): + + def __init__(self, retval, *args): + super(RpcException, self).__init__(*args) + self.retval = retval + + +class spdk_rpc(object): + + def __init__(self, rpc_py): + self.rpc_py = rpc_py + + def __getattr__(self, name): + def call(*args): + cmd = "{} {}".format(self.rpc_py, name) + for arg in args: + cmd += " {}".format(arg) + return check_output(cmd, shell=True) + return call + + +def verify(expr, retcode, msg): + if not expr: + raise RpcException(retcode, msg) + + +def verify_trace_flag_rpc_methods(rpc_py, rpc_param): + rpc = spdk_rpc(rpc_py) + output = rpc.get_trace_flags() + jsonvalue = json.loads(output) + verify(not jsonvalue[rpc_param['trace_flag']], 1, + "get_trace_flags returned {}, expected false".format(jsonvalue)) + rpc.set_trace_flag(rpc_param['trace_flag']) + output = rpc.get_trace_flags() + jsonvalue = json.loads(output) + verify(jsonvalue[rpc_param['trace_flag']], 1, + "get_trace_flags returned {}, expected true".format(jsonvalue)) + rpc.clear_trace_flag(rpc_param['trace_flag']) + output = rpc.get_trace_flags() + jsonvalue = json.loads(output) + verify(not jsonvalue[rpc_param['trace_flag']], 1, + "get_trace_flags returned {}, expected false".format(jsonvalue)) + + print("verify_trace_flag_rpc_methods passed") + + +def verify_iscsi_connection_rpc_methods(rpc_py): + rpc = spdk_rpc(rpc_py) + output = rpc.get_iscsi_connections() + jsonvalue = json.loads(output) + verify(not jsonvalue, 1, + "get_iscsi_connections returned {}, expected empty".format(jsonvalue)) + + rpc.construct_malloc_bdev(rpc_param['malloc_bdev_size'], rpc_param['malloc_block_size']) + rpc.add_portal_group(portal_tag, "{}:{}".format(rpc_param['target_ip'], str(rpc_param['port']))) + rpc.add_initiator_group(initiator_tag, rpc_param['initiator_name'], rpc_param['netmask']) + + lun_mapping = "Malloc" + str(rpc_param['lun_total']) + ":0" + net_mapping = portal_tag + ":" + initiator_tag + rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'], '-d') + check_output('iscsiadm -m discovery -t st -p {}'.format(rpc_param['target_ip']), shell=True) + check_output('iscsiadm -m node --login', shell=True) + name = json.loads(rpc.get_target_nodes())[0]['name'] + output = rpc.get_iscsi_connections() + jsonvalues = json.loads(output) + verify(jsonvalues[0]['target_node_name'] == rpc_param['target_name'], 1, + "target node name vaule is {}, expected {}".format(jsonvalues[0]['target_node_name'], rpc_param['target_name'])) + verify(jsonvalues[0]['id'] == 0, 1, + "device id value is {}, expected 0".format(jsonvalues[0]['id'])) + verify(jsonvalues[0]['initiator_addr'] == rpc_param['initiator_ip'], 1, + "initiator address values is {}, expected {}".format(jsonvalues[0]['initiator_addr'], rpc_param['initiator_ip'])) + verify(jsonvalues[0]['target_addr'] == rpc_param['target_ip'], 1, + "target address values is {}, expected {}".format(jsonvalues[0]['target_addr'], rpc_param['target_ip'])) + + check_output('iscsiadm -m node --logout', shell=True) + check_output('iscsiadm -m node -o delete', shell=True) + rpc.delete_initiator_group(initiator_tag) + rpc.delete_portal_group(portal_tag) + rpc.delete_target_node(name) + output = rpc.get_iscsi_connections() + jsonvalues = json.loads(output) + verify(not jsonvalues, 1, + "get_iscsi_connections returned {}, expected empty".format(jsonvalues)) + + print("verify_iscsi_connection_rpc_methods passed") + + +def verify_scsi_devices_rpc_methods(rpc_py): + rpc = spdk_rpc(rpc_py) + output = rpc.get_scsi_devices() + jsonvalue = json.loads(output) + verify(not jsonvalue, 1, + "get_scsi_devices returned {}, expected empty".format(jsonvalue)) + + rpc.construct_malloc_bdev(rpc_param['malloc_bdev_size'], rpc_param['malloc_block_size']) + rpc.add_portal_group(portal_tag, "{}:{}".format(rpc_param['target_ip'], str(rpc_param['port']))) + rpc.add_initiator_group(initiator_tag, rpc_param['initiator_name'], rpc_param['netmask']) + + lun_mapping = "Malloc" + str(rpc_param['lun_total']) + ":0" + net_mapping = portal_tag + ":" + initiator_tag + rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'], '-d') + check_output('iscsiadm -m discovery -t st -p {}'.format(rpc_param['target_ip']), shell=True) + check_output('iscsiadm -m node --login', shell=True) + name = json.loads(rpc.get_target_nodes())[0]['name'] + output = rpc.get_iscsi_global_params() + jsonvalues = json.loads(output) + nodebase = jsonvalues['node_base'] + output = rpc.get_scsi_devices() + jsonvalues = json.loads(output) + verify(jsonvalues[0]['device_name'] == nodebase + ":" + rpc_param['target_name'], 1, + "device name vaule is {}, expected {}".format(jsonvalues[0]['device_name'], rpc_param['target_name'])) + verify(jsonvalues[0]['id'] == 0, 1, + "device id value is {}, expected 0".format(jsonvalues[0]['id'])) + + check_output('iscsiadm -m node --logout', shell=True) + check_output('iscsiadm -m node -o delete', shell=True) + rpc.delete_initiator_group(initiator_tag) + rpc.delete_portal_group(portal_tag) + rpc.delete_target_node(name) + output = rpc.get_scsi_devices() + jsonvalues = json.loads(output) + verify(not jsonvalues, 1, + "get_scsi_devices returned {}, expected empty".format(jsonvalues)) + + print("verify_scsi_devices_rpc_methods passed") + + +def create_malloc_bdevs_rpc_methods(rpc_py, rpc_param): + rpc = spdk_rpc(rpc_py) + + for i in range(1, rpc_param['lun_total'] + 1): + rpc.construct_malloc_bdev(rpc_param['malloc_bdev_size'], rpc_param['malloc_block_size']) + + print("create_malloc_bdevs_rpc_methods passed") + + +def verify_portal_groups_rpc_methods(rpc_py, rpc_param): + rpc = spdk_rpc(rpc_py) + output = rpc.get_portal_groups() + jsonvalues = json.loads(output) + verify(not jsonvalues, 1, + "get_portal_groups returned {} groups, expected empty".format(jsonvalues)) + + lo_ip = (target_ip, other_ip) + nics = json.loads(rpc.get_interfaces()) + for x in nics: + if x["ifc_index"] == 'lo': + rpc.add_ip_address(x["ifc_index"], lo_ip[1]) + for idx, value in enumerate(lo_ip): + # The portal group tag must start at 1 + tag = idx + 1 + rpc.add_portal_group(tag, "{}:{}@{}".format(value, rpc_param['port'], rpc_param['cpumask'])) + output = rpc.get_portal_groups() + jsonvalues = json.loads(output) + verify(len(jsonvalues) == tag, 1, + "get_portal_groups returned {} groups, expected {}".format(len(jsonvalues), tag)) + + tag_list = [] + for idx, value in enumerate(jsonvalues): + verify(value['portals'][0]['host'] == lo_ip[idx], 1, + "host value is {}, expected {}".format(value['portals'][0]['host'], rpc_param['target_ip'])) + verify(value['portals'][0]['port'] == str(rpc_param['port']), 1, + "port value is {}, expected {}".format(value['portals'][0]['port'], str(rpc_param['port']))) + verify(value['portals'][0]['cpumask'] == format(rpc_param['cpumask'], '#x'), 1, + "cpumask value is {}, expected {}".format(value['portals'][0]['cpumask'], format(rpc_param['cpumask'], '#x'))) + tag_list.append(value['tag']) + verify(value['tag'] == idx + 1, 1, + "tag value is {}, expected {}".format(value['tag'], idx + 1)) + + for idx, value in enumerate(tag_list): + rpc.delete_portal_group(value) + output = rpc.get_portal_groups() + jsonvalues = json.loads(output) + verify(len(jsonvalues) == (len(tag_list) - (idx + 1)), 1, + "get_portal_group returned {} groups, expected {}".format(len(jsonvalues), (len(tag_list) - (idx + 1)))) + if not jsonvalues: + break + + for jidx, jvalue in enumerate(jsonvalues): + verify(jvalue['portals'][0]['host'] == lo_ip[idx + jidx + 1], 1, + "host value is {}, expected {}".format(jvalue['portals'][0]['host'], lo_ip[idx + jidx + 1])) + verify(jvalue['portals'][0]['port'] == str(rpc_param['port']), 1, + "port value is {}, expected {}".format(jvalue['portals'][0]['port'], str(rpc_param['port']))) + verify(jvalue['portals'][0]['cpumask'] == format(rpc_param['cpumask'], '#x'), 1, + "cpumask value is {}, expected {}".format(jvalue['portals'][0]['cpumask'], format(rpc_param['cpumask'], '#x'))) + verify(jvalue['tag'] != value or jvalue['tag'] == tag_list[idx + jidx + 1], 1, + "tag value is {}, expected {} and not {}".format(jvalue['tag'], tag_list[idx + jidx + 1], value)) + + for x in nics: + if x["ifc_index"] == 'lo': + rpc.delete_ip_address(x["ifc_index"], lo_ip[1]) + + print("verify_portal_groups_rpc_methods passed") + + +def verify_initiator_groups_rpc_methods(rpc_py, rpc_param): + rpc = spdk_rpc(rpc_py) + output = rpc.get_initiator_groups() + jsonvalues = json.loads(output) + verify(not jsonvalues, 1, + "get_initiator_groups returned {}, expected empty".format(jsonvalues)) + for idx, value in enumerate(rpc_param['netmask']): + # The initiator group tag must start at 1 + tag = idx + 1 + rpc.add_initiator_group(tag, rpc_param['initiator_name'], value) + output = rpc.get_initiator_groups() + jsonvalues = json.loads(output) + verify(len(jsonvalues) == tag, 1, + "get_initiator_groups returned {} groups, expected {}".format(len(jsonvalues), tag)) + + tag_list = [] + for idx, value in enumerate(jsonvalues): + verify(value['initiators'][0] == rpc_param['initiator_name'], 1, + "initiator value is {}, expected {}".format(value['initiators'][0], rpc_param['initiator_name'])) + tag_list.append(value['tag']) + verify(value['tag'] == idx + 1, 1, + "tag value is {}, expected {}".format(value['tag'], idx + 1)) + verify(value['netmasks'][0] == rpc_param['netmask'][idx], 1, + "netmasks value is {}, expected {}".format(value['netmasks'][0], rpc_param['netmask'][idx])) + + for idx, value in enumerate(rpc_param['netmask']): + tag = idx + 1 + rpc.delete_initiators_from_initiator_group(tag, '-n', rpc_param['initiator_name'], '-m', value) + + output = rpc.get_initiator_groups() + jsonvalues = json.loads(output) + verify(len(jsonvalues) == tag, 1, + "get_initiator_groups returned {} groups, expected {}".format(len(jsonvalues), tag)) + + for idx, value in enumerate(jsonvalues): + verify(value['tag'] == idx + 1, 1, + "tag value is {}, expected {}".format(value['tag'], idx + 1)) + initiators = value.get('initiators') + verify(len(initiators) == 0, 1, + "length of initiator list is {}, expected 0".format(len(initiators))) + netmasks = value.get('netmasks') + verify(len(netmasks) == 0, 1, + "length of netmask list is {}, expected 0".format(len(netmasks))) + + for idx, value in enumerate(rpc_param['netmask']): + tag = idx + 1 + rpc.add_initiators_to_initiator_group(tag, '-n', rpc_param['initiator_name'], '-m', value) + output = rpc.get_initiator_groups() + jsonvalues = json.loads(output) + verify(len(jsonvalues) == tag, 1, + "get_initiator_groups returned {} groups, expected {}".format(len(jsonvalues), tag)) + + tag_list = [] + for idx, value in enumerate(jsonvalues): + verify(value['initiators'][0] == rpc_param['initiator_name'], 1, + "initiator value is {}, expected {}".format(value['initiators'][0], rpc_param['initiator_name'])) + tag_list.append(value['tag']) + verify(value['tag'] == idx + 1, 1, + "tag value is {}, expected {}".format(value['tag'], idx + 1)) + verify(value['netmasks'][0] == rpc_param['netmask'][idx], 1, + "netmasks value is {}, expected {}".format(value['netmasks'][0], rpc_param['netmask'][idx])) + + for idx, value in enumerate(tag_list): + rpc.delete_initiator_group(value) + output = rpc.get_initiator_groups() + jsonvalues = json.loads(output) + verify(len(jsonvalues) == (len(tag_list) - (idx + 1)), 1, + "get_initiator_groups returned {} groups, expected {}".format(len(jsonvalues), (len(tag_list) - (idx + 1)))) + if not jsonvalues: + break + for jidx, jvalue in enumerate(jsonvalues): + verify(jvalue['initiators'][0] == rpc_param['initiator_name'], 1, + "initiator value is {}, expected {}".format(jvalue['initiators'][0], rpc_param['initiator_name'])) + verify(jvalue['tag'] != value or jvalue['tag'] == tag_list[idx + jidx + 1], 1, + "tag value is {}, expected {} and not {}".format(jvalue['tag'], tag_list[idx + jidx + 1], value)) + verify(jvalue['netmasks'][0] == rpc_param['netmask'][idx + jidx + 1], 1, + "netmasks value is {}, expected {}".format(jvalue['netmasks'][0], rpc_param['netmask'][idx + jidx + 1])) + + print("verify_initiator_groups_rpc_method passed.") + + +def verify_target_nodes_rpc_methods(rpc_py, rpc_param): + rpc = spdk_rpc(rpc_py) + output = rpc.get_iscsi_global_params() + jsonvalues = json.loads(output) + nodebase = jsonvalues['node_base'] + output = rpc.get_target_nodes() + jsonvalues = json.loads(output) + verify(not jsonvalues, 1, + "get_target_nodes returned {}, expected empty".format(jsonvalues)) + + rpc.construct_malloc_bdev(rpc_param['malloc_bdev_size'], rpc_param['malloc_block_size']) + rpc.add_portal_group(portal_tag, "{}:{}".format(rpc_param['target_ip'], str(rpc_param['port']))) + rpc.add_initiator_group(initiator_tag, rpc_param['initiator_name'], rpc_param['netmask']) + + lun_mapping = "Malloc" + str(rpc_param['lun_total']) + ":0" + net_mapping = portal_tag + ":" + initiator_tag + rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'], '-d') + output = rpc.get_target_nodes() + jsonvalues = json.loads(output) + verify(len(jsonvalues) == 1, 1, + "get_target_nodes returned {} nodes, expected 1".format(len(jsonvalues))) + bdev_name = jsonvalues[0]['luns'][0]['bdev_name'] + verify(bdev_name == "Malloc" + str(rpc_param['lun_total']), 1, + "bdev_name value is {}, expected Malloc{}".format(jsonvalues[0]['luns'][0]['bdev_name'], str(rpc_param['lun_total']))) + name = jsonvalues[0]['name'] + verify(name == nodebase + ":" + rpc_param['target_name'], 1, + "target name value is {}, expected {}".format(name, nodebase + ":" + rpc_param['target_name'])) + verify(jsonvalues[0]['alias_name'] == rpc_param['alias_name'], 1, + "target alias_name value is {}, expected {}".format(jsonvalues[0]['alias_name'], rpc_param['alias_name'])) + verify(jsonvalues[0]['luns'][0]['lun_id'] == 0, 1, + "lun id value is {}, expected 0".format(jsonvalues[0]['luns'][0]['lun_id'])) + verify(jsonvalues[0]['pg_ig_maps'][0]['ig_tag'] == int(initiator_tag), 1, + "initiator group tag value is {}, expected {}".format(jsonvalues[0]['pg_ig_maps'][0]['ig_tag'], initiator_tag)) + verify(jsonvalues[0]['queue_depth'] == rpc_param['queue_depth'], 1, + "queue depth value is {}, expected {}".format(jsonvalues[0]['queue_depth'], rpc_param['queue_depth'])) + verify(jsonvalues[0]['pg_ig_maps'][0]['pg_tag'] == int(portal_tag), 1, + "portal group tag value is {}, expected {}".format(jsonvalues[0]['pg_ig_maps'][0]['pg_tag'], portal_tag)) + verify(jsonvalues[0]['disable_chap'] == rpc_param['disable_chap'], 1, + "disable chap value is {}, expected {}".format(jsonvalues[0]['disable_chap'], rpc_param['disable_chap'])) + verify(jsonvalues[0]['mutual_chap'] == rpc_param['mutual_chap'], 1, + "chap mutual value is {}, expected {}".format(jsonvalues[0]['mutual_chap'], rpc_param['mutual_chap'])) + verify(jsonvalues[0]['require_chap'] == rpc_param['require_chap'], 1, + "chap required value is {}, expected {}".format(jsonvalues[0]['require_chap'], rpc_param['require_chap'])) + verify(jsonvalues[0]['chap_group'] == rpc_param['chap_group'], 1, + "chap auth group value is {}, expected {}".format(jsonvalues[0]['chap_group'], rpc_param['chap_group'])) + verify(jsonvalues[0]['header_digest'] == rpc_param['header_digest'], 1, + "header digest value is {}, expected {}".format(jsonvalues[0]['header_digest'], rpc_param['header_digest'])) + verify(jsonvalues[0]['data_digest'] == rpc_param['data_digest'], 1, + "data digest value is {}, expected {}".format(jsonvalues[0]['data_digest'], rpc_param['data_digest'])) + lun_id = '1' + rpc.target_node_add_lun(name, bdev_name, "-i", lun_id) + output = rpc.get_target_nodes() + jsonvalues = json.loads(output) + verify(jsonvalues[0]['luns'][1]['bdev_name'] == "Malloc" + str(rpc_param['lun_total']), 1, + "bdev_name value is {}, expected Malloc{}".format(jsonvalues[0]['luns'][0]['bdev_name'], str(rpc_param['lun_total']))) + verify(jsonvalues[0]['luns'][1]['lun_id'] == 1, 1, + "lun id value is {}, expected 1".format(jsonvalues[0]['luns'][1]['lun_id'])) + + rpc.delete_target_node(name) + output = rpc.get_target_nodes() + jsonvalues = json.loads(output) + verify(not jsonvalues, 1, + "get_target_nodes returned {}, expected empty".format(jsonvalues)) + + rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'], '-d') + + rpc.delete_portal_group(portal_tag) + rpc.delete_initiator_group(initiator_tag) + rpc.delete_target_node(name) + output = rpc.get_target_nodes() + jsonvalues = json.loads(output) + if not jsonvalues: + print("This issue will be fixed later.") + + print("verify_target_nodes_rpc_methods passed.") + + +def verify_get_interfaces(rpc_py): + rpc = spdk_rpc(rpc_py) + nics = json.loads(rpc.get_interfaces()) + nics_names = set(x["name"] for x in nics) + # parse ip link show to verify the get_interfaces result + ip_show = ns_cmd + " ip link show" + ifcfg_nics = set(re.findall("\S+:\s(\S+?)(?:@\S+){0,1}:\s<.*", check_output(ip_show.split()).decode())) + verify(nics_names == ifcfg_nics, 1, "get_interfaces returned {}".format(nics)) + print("verify_get_interfaces passed.") + + +def help_get_interface_ip_list(rpc_py, nic_name): + rpc = spdk_rpc(rpc_py) + nics = json.loads(rpc.get_interfaces()) + nic = list([x for x in nics if x["name"] == nic_name]) + verify(len(nic) != 0, 1, + "Nic name: {} is not found in {}".format(nic_name, [x["name"] for x in nics])) + return nic[0]["ip_addr"] + + +def verify_add_delete_ip_address(rpc_py): + rpc = spdk_rpc(rpc_py) + nics = json.loads(rpc.get_interfaces()) + # add ip on up to first 2 nics + for x in nics[:2]: + faked_ip = "123.123.{}.{}".format(random.randint(1, 254), random.randint(1, 254)) + ping_cmd = ns_cmd + " ping -c 1 -W 1 " + faked_ip + rpc.add_ip_address(x["ifc_index"], faked_ip) + verify(faked_ip in help_get_interface_ip_list(rpc_py, x["name"]), 1, + "add ip {} to nic {} failed.".format(faked_ip, x["name"])) + try: + check_call(ping_cmd.split()) + except BaseException: + verify(False, 1, + "ping ip {} for {} was failed(adding was successful)".format + (faked_ip, x["name"])) + rpc.delete_ip_address(x["ifc_index"], faked_ip) + verify(faked_ip not in help_get_interface_ip_list(rpc_py, x["name"]), 1, + "delete ip {} from nic {} failed.(adding and ping were successful)".format + (faked_ip, x["name"])) + # ping should be failed and throw an CalledProcessError exception + try: + check_call(ping_cmd.split()) + except CalledProcessError as _: + pass + except Exception as e: + verify(False, 1, + "Unexpected exception was caught {}(adding/ping/delete were successful)".format + (str(e))) + else: + verify(False, 1, + "ip {} for {} could be pinged after delete ip(adding/ping/delete were successful)".format + (faked_ip, x["name"])) + print("verify_add_delete_ip_address passed.") + + +def verify_add_nvme_bdev_rpc_methods(rpc_py): + rpc = spdk_rpc(rpc_py) + test_pass = 0 + output = check_output(["lspci", "-mm", "-nn"]) + addrs = re.findall('^([0-9]{2}:[0-9]{2}.[0-9]) "Non-Volatile memory controller \[0108\]".*-p02', output.decode(), re.MULTILINE) + for addr in addrs: + ctrlr_address = "-b Nvme{} -t pcie -a 0000:{}".format(addrs.index(addr), addr) + rpc.construct_nvme_bdev(ctrlr_address) + print("add nvme device passed first time") + test_pass = 0 + try: + rpc.construct_nvme_bdev(ctrlr_address) + except Exception as e: + print("add nvme device passed second time") + test_pass = 1 + pass + else: + pass + verify(test_pass == 1, 1, "add nvme device passed second time") + print("verify_add_nvme_bdev_rpc_methods passed.") + + +if __name__ == "__main__": + + rpc_py = sys.argv[1] + + try: + verify_trace_flag_rpc_methods(rpc_py, rpc_param) + verify_get_interfaces(rpc_py) + verify_add_delete_ip_address(rpc_py) + create_malloc_bdevs_rpc_methods(rpc_py, rpc_param) + verify_portal_groups_rpc_methods(rpc_py, rpc_param) + verify_initiator_groups_rpc_methods(rpc_py, rpc_param) + verify_target_nodes_rpc_methods(rpc_py, rpc_param) + verify_scsi_devices_rpc_methods(rpc_py) + verify_iscsi_connection_rpc_methods(rpc_py) + verify_add_nvme_bdev_rpc_methods(rpc_py) + except RpcException as e: + print("{}. Exiting with status {}".format(e.message, e.retval)) + raise e + except Exception as e: + raise e + + sys.exit(0) diff --git a/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.sh b/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.sh new file mode 100755 index 00000000..ac5c4647 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/iscsi_tgt/common.sh + +timing_enter rpc_config + +# $1 = test type (posix/vpp) +if [ "$1" == "posix" ] || [ "$1" == "vpp" ]; then + TEST_TYPE=$1 +else + echo "No iSCSI test type specified" + exit 1 +fi + +MALLOC_BDEV_SIZE=64 + +rpc_py=$rootdir/scripts/rpc.py +rpc_config_py="$testdir/rpc_config.py" + +timing_enter start_iscsi_tgt + +$ISCSI_APP --wait-for-rpc & +pid=$! +echo "Process pid: $pid" + +trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py set_iscsi_options -o 30 -a 16 +$rpc_py start_subsystem_init +echo "iscsi_tgt is listening. Running tests..." + +timing_exit start_iscsi_tgt + +$rpc_config_py $rpc_py $TARGET_IP $INITIATOR_IP $ISCSI_PORT $NETMASK $TARGET_NAMESPACE $TEST_TYPE + +$rpc_py get_bdevs + +trap - SIGINT SIGTERM EXIT + +iscsicleanup +killprocess $pid +timing_exit rpc_config diff --git a/src/spdk/test/iscsi_tgt/test_plan.md b/src/spdk/test/iscsi_tgt/test_plan.md new file mode 100644 index 00000000..4afad162 --- /dev/null +++ b/src/spdk/test/iscsi_tgt/test_plan.md @@ -0,0 +1,41 @@ +# SPDK iscsi_tgt test plan + +## Objective +The purpose of these tests is to verify correct behavior of SPDK iSCSI target +feature. +These tests are run either per-commit or as nightly tests. + +## Configuration +All tests share the same basic configuration file for SPDK iscsi_tgt to run. +Static configuration from config file consists of setting number of per session +queues and enabling RPC for further configuration via RPC calls. +RPC calls used for dynamic configuration consist: +- creating Malloc backend devices +- creating Null Block backend devices +- creating Pmem backend devices +- constructing iSCSI subsystems +- deleting iSCSI subsystems + +### Tests + +#### Test 1: iSCSI namespace on a Pmem device +This test configures a SPDK iSCSI subsystem backed by pmem +devices and uses FIO to generate I/Os that target those subsystems. +Test steps: +- Step 1: Start SPDK iscsi_tgt application. +- Step 2: Create 10 pmem pools. +- Step 3: Create pmem bdevs on pmem pools. +- Step 4: Create iSCSI subsystems with 10 pmem bdevs namespaces. +- Step 5: Connect to iSCSI susbsystems with kernel initiator. +- Step 6: Run FIO with workload parameters: blocksize=4096, iodepth=64, + workload=randwrite; varify flag is enabled so that + FIO reads and verifies the data written to the pmem device. + The run time is 10 seconds for a quick test an 10 minutes + for longer nightly test. +- Step 7: Run FIO with workload parameters: blocksize=128kB, iodepth=4, + workload=randwrite; varify flag is enabled so that + FIO reads and verifies the data written to the pmem device. + The run time is 10 seconds for a quick test an 10 minutes + for longer nightly test. +- Step 8: Disconnect kernel initiator from iSCSI subsystems. +- Step 9: Delete iSCSI subsystems from configuration. diff --git a/src/spdk/test/json_config/clear_config.py b/src/spdk/test/json_config/clear_config.py new file mode 100755 index 00000000..e6d8dd71 --- /dev/null +++ b/src/spdk/test/json_config/clear_config.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse +sys.path.append(os.path.join(os.path.dirname(__file__), "../../scripts")) +import rpc # noqa +from rpc.client import print_dict, JSONRPCException # noqa + + +def get_bdev_name_key(bdev): + bdev_name_key = 'name' + if 'method' in bdev and bdev['method'] == 'construct_split_vbdev': + bdev_name_key = "base_bdev" + return bdev_name_key + + +def get_bdev_name(bdev): + bdev_name = None + if 'params' in bdev: + if 'name' in bdev['params']: + bdev_name = bdev['params']['name'] + elif 'base_name' in bdev['params']: + bdev_name = bdev['params']['base_name'] + elif 'base_bdev' in bdev['params']: + bdev_name = bdev['params']['base_bdev'] + if 'method' in bdev and bdev['method'] == 'construct_error_bdev': + bdev_name = "EE_%s" % bdev_name + return bdev_name + + +def delete_subbdevs(args, bdev, rpc_bdevs): + ret_value = False + bdev_name = get_bdev_name(bdev) + if bdev_name and 'method' in bdev: + construct_method = bdev['method'] + if construct_method == 'construct_nvme_bdev': + for rpc_bdev in rpc_bdevs: + if bdev_name in rpc_bdev['name'] and rpc_bdev['product_name'] == "NVMe disk": + args.client.call('delete_nvme_controller', {'name': "%s" % rpc_bdev['name'].split('n')[0]}) + ret_value = True + + return ret_value + + +def get_bdev_destroy_method(bdev): + destroy_method_map = {'construct_nvme_bdev': "delete_nvme_controller", + 'construct_malloc_bdev': "delete_malloc_bdev", + 'construct_null_bdev': "delete_null_bdev", + 'construct_rbd_bdev': "delete_rbd_bdev", + 'construct_pmem_bdev': "delete_pmem_bdev", + 'construct_aio_bdev': "delete_aio_bdev", + 'construct_error_bdev': "delete_error_bdev", + 'construct_split_vbdev': "destruct_split_vbdev", + 'construct_virtio_dev': "remove_virtio_bdev", + 'construct_crypto_bdev': "delete_crypto_bdev" + } + destroy_method = None + if 'method' in bdev: + construct_method = bdev['method'] + if construct_method in list(destroy_method_map.keys()): + destroy_method = destroy_method_map[construct_method] + + return destroy_method + + +def clear_bdev_subsystem(args, bdev_config): + rpc_bdevs = args.client.call("get_bdevs") + for bdev in bdev_config: + if delete_subbdevs(args, bdev, rpc_bdevs): + continue + bdev_name_key = get_bdev_name_key(bdev) + bdev_name = get_bdev_name(bdev) + destroy_method = get_bdev_destroy_method(bdev) + if destroy_method: + args.client.call(destroy_method, {bdev_name_key: bdev_name}) + + ''' Disable and reset hotplug ''' + rpc.bdev.set_bdev_nvme_hotplug(args.client, False) + + +def get_nvmf_destroy_method(nvmf): + destroy_method_map = {'nvmf_subsystem_create': "delete_nvmf_subsystem"} + try: + return destroy_method_map[nvmf['method']] + except KeyError: + return None + + +def clear_nvmf_subsystem(args, nvmf_config): + for nvmf in nvmf_config: + destroy_method = get_nvmf_destroy_method(nvmf) + if destroy_method: + args.client.call(destroy_method, {'nqn': nvmf['params']['nqn']}) + + +def get_iscsi_destroy_method(iscsi): + destroy_method_map = {'add_portal_group': "delete_portal_group", + 'add_initiator_group': "delete_initiator_group", + 'construct_target_node': "delete_target_node", + 'set_iscsi_options': None + } + return destroy_method_map[iscsi['method']] + + +def get_iscsi_name(iscsi): + if 'name' in iscsi['params']: + return iscsi['params']['name'] + else: + return iscsi['params']['tag'] + + +def get_iscsi_name_key(iscsi): + if iscsi['method'] == 'construct_target_node': + return "name" + else: + return 'tag' + + +def clear_iscsi_subsystem(args, iscsi_config): + for iscsi in iscsi_config: + destroy_method = get_iscsi_destroy_method(iscsi) + if destroy_method: + args.client.call(destroy_method, {get_iscsi_name_key(iscsi): get_iscsi_name(iscsi)}) + + +def get_nbd_destroy_method(nbd): + destroy_method_map = {'start_nbd_disk': "stop_nbd_disk" + } + return destroy_method_map[nbd['method']] + + +def clear_nbd_subsystem(args, nbd_config): + for nbd in nbd_config: + destroy_method = get_nbd_destroy_method(nbd) + if destroy_method: + args.client.call(destroy_method, {'nbd_device': nbd['params']['nbd_device']}) + + +def clear_net_framework_subsystem(args, net_framework_config): + pass + + +def clear_copy_subsystem(args, copy_config): + pass + + +def clear_interface_subsystem(args, interface_config): + pass + + +def clear_vhost_subsystem(args, vhost_config): + for vhost in reversed(vhost_config): + if 'method' in vhost: + method = vhost['method'] + if method in ['add_vhost_scsi_lun']: + args.client.call("remove_vhost_scsi_target", + {"ctrlr": vhost['params']['ctrlr'], + "scsi_target_num": vhost['params']['scsi_target_num']}) + elif method in ['construct_vhost_scsi_controller', 'construct_vhost_blk_controller', + 'construct_vhost_nvme_controller']: + args.client.call("remove_vhost_controller", {'ctrlr': vhost['params']['ctrlr']}) + + +def call_test_cmd(func): + def rpc_test_cmd(*args, **kwargs): + try: + func(*args, **kwargs) + except JSONRPCException as ex: + print((ex.message)) + exit(1) + return rpc_test_cmd + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Clear config command') + parser.add_argument('-s', dest='server_addr', default='/var/tmp/spdk.sock') + parser.add_argument('-p', dest='port', default=5260, type=int) + parser.add_argument('-t', dest='timeout', default=60.0, type=float) + parser.add_argument('-v', dest='verbose', action='store_true') + subparsers = parser.add_subparsers(help='RPC methods') + + @call_test_cmd + def clear_config(args): + for subsystem_item in reversed(args.client.call('get_subsystems')): + args.subsystem = subsystem_item['subsystem'] + clear_subsystem(args) + + p = subparsers.add_parser('clear_config', help="""Clear configuration of all SPDK subsystems and targets using JSON RPC""") + p.set_defaults(func=clear_config) + + @call_test_cmd + def clear_subsystem(args): + config = args.client.call('get_subsystem_config', {"name": args.subsystem}) + if config is None: + return + if args.verbose: + print("Calling clear_%s_subsystem" % args.subsystem) + globals()["clear_%s_subsystem" % args.subsystem](args, config) + + p = subparsers.add_parser('clear_subsystem', help="""Clear configuration of SPDK subsystem using JSON RPC""") + p.add_argument('--subsystem', help="""Subsystem name""") + p.set_defaults(func=clear_subsystem) + + args = parser.parse_args() + + try: + args.client = rpc.client.JSONRPCClient(args.server_addr, args.port, args.verbose, args.timeout) + except JSONRPCException as ex: + print((ex.message)) + exit(1) + args.func(args) diff --git a/src/spdk/test/json_config/common.sh b/src/spdk/test/json_config/common.sh new file mode 100644 index 00000000..a0a56951 --- /dev/null +++ b/src/spdk/test/json_config/common.sh @@ -0,0 +1,249 @@ +JSON_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})) +SPDK_BUILD_DIR=$JSON_DIR/../../ +source $JSON_DIR/../common/autotest_common.sh +source $JSON_DIR/../nvmf/common.sh + +spdk_rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s /var/tmp/spdk.sock" +spdk_clear_config_py="$JSON_DIR/clear_config.py -s /var/tmp/spdk.sock" +initiator_rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s /var/tmp/virtio.sock" +initiator_clear_config_py="$JSON_DIR/clear_config.py -s /var/tmp/virtio.sock" +base_json_config=$JSON_DIR/base_config.json +last_json_config=$JSON_DIR/last_config.json +full_config=$JSON_DIR/full_config.json +base_bdevs=$JSON_DIR/bdevs_base.txt +last_bdevs=$JSON_DIR/bdevs_last.txt +null_json_config=$JSON_DIR/null_json_config.json + +function run_spdk_tgt() { + echo "Running spdk target" + $SPDK_BUILD_DIR/app/spdk_tgt/spdk_tgt -m 0x1 -p 0 -s 4096 --wait-for-rpc & + spdk_tgt_pid=$! + + echo "Waiting for app to run..." + waitforlisten $spdk_tgt_pid + echo "spdk_tgt started - pid=$spdk_tgt_pid but waits for subsystem initialization" + + echo "" +} + +function load_nvme() { + echo '{"subsystems": [' > nvme_config.json + $SPDK_BUILD_DIR/scripts/gen_nvme.sh --json >> nvme_config.json + echo ']}' >> nvme_config.json + $rpc_py load_config < nvme_config.json + rm nvme_config.json +} + +function run_initiator() { + $SPDK_BUILD_DIR/app/spdk_tgt/spdk_tgt -m 0x2 -p 0 -g -u -s 1024 -r /var/tmp/virtio.sock --wait-for-rpc & + virtio_pid=$! + waitforlisten $virtio_pid /var/tmp/virtio.sock +} + +function upload_vhost() { + $rpc_py construct_split_vbdev Nvme0n1 8 + $rpc_py construct_vhost_scsi_controller sample1 + $rpc_py add_vhost_scsi_lun sample1 0 Nvme0n1p3 + $rpc_py add_vhost_scsi_lun sample1 1 Nvme0n1p4 + $rpc_py set_vhost_controller_coalescing sample1 1 100 + $rpc_py construct_vhost_blk_controller sample2 Nvme0n1p5 + $rpc_py construct_vhost_nvme_controller sample3 16 + $rpc_py add_vhost_nvme_ns sample3 Nvme0n1p6 +} + +function kill_targets() { + if [ ! -z $virtio_pid ]; then + killprocess $virtio_pid + fi + if [ ! -z $spdk_tgt_pid ]; then + killprocess $spdk_tgt_pid + fi +} + +# Compare two JSON files. +# +# NOTE: Order of objects in JSON can change by just doing loads -> dumps so all JSON objects (not arrays) are sorted by +# config_filter.py script. Sorted output is used to compare JSON output. +# +function json_diff() +{ + local tmp_file_1=$(mktemp ${1}.XXX) + local tmp_file_2=$(mktemp ${2}.XXX) + local ret=0 + + cat $1 | $JSON_DIR/config_filter.py -method "sort" > $tmp_file_1 + cat $2 | $JSON_DIR/config_filter.py -method "sort" > $tmp_file_2 + + if ! diff -u $tmp_file_1 $tmp_file_2; then + ret=1 + fi + + rm $tmp_file_1 $tmp_file_2 + return $ret +} + +# This function test if json config was properly saved and loaded. +# 1. Get a list of bdevs and save it to the file "base_bdevs". +# 2. Save only configuration of the running spdk_tgt to the file "base_json_config" +# (global parameters are not saved). +# 3. Clear configuration of the running spdk_tgt. +# 4. Save only configuration of the running spdk_tgt to the file "null_json_config" +# (global parameters are not saved). +# 5. Check if configuration of the running spdk_tgt is cleared by checking +# if the file "null_json_config" doesn't have any configuration. +# 6. Load the file "base_json_config" to the running spdk_tgt. +# 7. Get a list of bdevs and save it to the file "last_bdevs". +# 8. Save only configuration of the running spdk_tgt to the file "last_json_config". +# 9. Check if the file "base_json_config" matches the file "last_json_config". +# 10. Check if the file "base_bdevs" matches the file "last_bdevs". +# 11. Remove all files. +function test_json_config() { + $rpc_py get_bdevs | jq '.|sort_by(.name)' > $base_bdevs + $rpc_py save_config > $full_config + $JSON_DIR/config_filter.py -method "delete_global_parameters" < $full_config > $base_json_config + $clear_config_py clear_config + $rpc_py save_config | $JSON_DIR/config_filter.py -method "delete_global_parameters" > $null_json_config + if [ "[]" != "$(jq '.subsystems | map(select(.config != null)) | map(select(.config != []))' $null_json_config)" ]; then + echo "Config has not been cleared" + return 1 + fi + $rpc_py load_config < $base_json_config + $rpc_py get_bdevs | jq '.|sort_by(.name)' > $last_bdevs + $rpc_py save_config | $JSON_DIR/config_filter.py -method "delete_global_parameters" > $last_json_config + + json_diff $base_json_config $last_json_config + json_diff $base_bdevs $last_bdevs + remove_config_files_after_test_json_config +} + +function remove_config_files_after_test_json_config() { + rm -f $last_bdevs $base_bdevs + rm -f $last_json_config $base_json_config + rm -f $full_config $null_json_config +} + +function create_pmem_bdev_subsytem_config() { + $rpc_py create_pmem_pool /tmp/pool_file1 128 512 + $rpc_py construct_pmem_bdev -n pmem1 /tmp/pool_file1 +} + +function clear_pmem_bdev_subsystem_config() { + $clear_config_py clear_config + $rpc_py delete_pmem_pool /tmp/pool_file1 +} + +function create_rbd_bdev_subsystem_config() { + rbd_setup 127.0.0.1 + $rpc_py construct_rbd_bdev $RBD_POOL $RBD_NAME 4096 +} + +function clear_rbd_bdev_subsystem_config() { + $clear_config_py clear_config + rbd_cleanup +} + +function create_bdev_subsystem_config() { + $rpc_py construct_split_vbdev Nvme0n1 2 + $rpc_py construct_null_bdev Null0 32 512 + $rpc_py construct_malloc_bdev 128 512 --name Malloc0 + $rpc_py construct_malloc_bdev 64 4096 --name Malloc1 + $rpc_py construct_malloc_bdev 8 1024 --name Malloc2 + if [ $SPDK_TEST_CRYPTO -eq 1 ]; then + $rpc_py construct_malloc_bdev 8 1024 --name Malloc3 + if [ $(lspci -d:37c8 | wc -l) -eq 0 ]; then + $rpc_py construct_crypto_bdev -b Malloc3 -c CryMalloc3 -d crypto_aesni_mb -k 0123456789123456 + else + $rpc_py construct_crypto_bdev -b Malloc3 -c CryMalloc3 -d crypto_qat -k 0123456789123456 + fi + fi + $rpc_py construct_error_bdev Malloc2 + if [ $(uname -s) = Linux ]; then + dd if=/dev/zero of=/tmp/sample_aio bs=2048 count=5000 + $rpc_py construct_aio_bdev /tmp/sample_aio aio_disk 1024 + fi + $rpc_py construct_lvol_store -c 1048576 Nvme0n1p0 lvs_test + $rpc_py construct_lvol_bdev -l lvs_test lvol0 32 + $rpc_py construct_lvol_bdev -l lvs_test -t lvol1 32 + $rpc_py snapshot_lvol_bdev lvs_test/lvol0 snapshot0 + $rpc_py clone_lvol_bdev lvs_test/snapshot0 clone0 +} + +function create_nvmf_subsystem_config() { + rdma_device_init + RDMA_IP_LIST=$(get_available_rdma_ips) + NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) + if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "Error: no NIC for nvmf test" + return 1 + fi + + bdevs="$($rpc_py construct_malloc_bdev 64 512) " + bdevs+="$($rpc_py construct_malloc_bdev 64 512)" + $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 + for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev + done + $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_FIRST_TARGET_IP -s "$NVMF_PORT" +} + +function clear_nvmf_subsystem_config() { + $clear_config_py clear_config +} + +function clear_bdev_subsystem_config() { + $rpc_py destroy_lvol_bdev lvs_test/clone0 + $rpc_py destroy_lvol_bdev lvs_test/lvol0 + $rpc_py destroy_lvol_bdev lvs_test/snapshot0 + $rpc_py destroy_lvol_store -l lvs_test + $clear_config_py clear_config + if [ $(uname -s) = Linux ]; then + rm -f /tmp/sample_aio + fi +} + +# In this test, target is spdk_tgt or virtio_initiator. +# 1. Save current spdk config to full_config +# and save only global parameters to the file "base_json_config". +# 2. Exit the running spdk target. +# 3. Start the spdk target and wait for loading config. +# 4. Load global parameters and configuration to the spdk target from the file full_config. +# 5. Save json config to the file "full_config". +# 6. Save only global parameters to the file "last_json_config". +# 7. Check if the file "base_json_config" matches the file "last_json_config". +# 8. Delete all files. +function test_global_params() { + target=$1 + $rpc_py save_config > $full_config + $JSON_DIR/config_filter.py -method "delete_configs" < $full_config > $base_json_config + if [ $target == "spdk_tgt" ]; then + killprocess $spdk_tgt_pid + run_spdk_tgt + elif [ $target == "virtio_initiator" ]; then + killprocess $virtio_pid + run_initiator + else + echo "Target is not specified for test_global_params" + return 1 + fi + $rpc_py load_config < $full_config + $rpc_py save_config > $full_config + $JSON_DIR/config_filter.py -method "delete_configs" < $full_config > $last_json_config + + json_diff $base_json_config $last_json_config + rm $base_json_config $last_json_config + rm $full_config +} + +function on_error_exit() { + set +e + echo "Error on $1 - $2" + remove_config_files_after_test_json_config + rpc_py="$spdk_rpc_py" + clear_config_py="$spdk_clear_config_py" + clear_bdev_subsystem_config + + kill_targets + + print_backtrace + exit 1 +} diff --git a/src/spdk/test/json_config/config_filter.py b/src/spdk/test/json_config/config_filter.py new file mode 100755 index 00000000..59e96f94 --- /dev/null +++ b/src/spdk/test/json_config/config_filter.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + +import sys +import json +import argparse +from collections import OrderedDict + + +def sort_json_object(o): + if isinstance(o, dict): + sorted_o = OrderedDict() + """ Order of keys in JSON object is irrelevant but we need to pick one + to be able to compare JSONS. """ + for key in sorted(o.keys()): + sorted_o[key] = sort_json_object(o[key]) + return sorted_o + if isinstance(o, list): + """ Keep list in the same orded but sort each item """ + return [sort_json_object(item) for item in o] + else: + return o + + +def filter_methods(do_remove_global_rpcs): + global_rpcs = [ + 'set_iscsi_options', + 'set_nvmf_target_config', + 'set_nvmf_target_options', + 'nvmf_create_transport', + 'set_bdev_options', + 'set_bdev_nvme_options', + 'set_bdev_nvme_hotplug', + ] + + data = json.loads(sys.stdin.read()) + out = {'subsystems': []} + for s in data['subsystems']: + if s['config']: + s_config = [] + for config in s['config']: + m_name = config['method'] + is_global_rpc = m_name in global_rpcs + if do_remove_global_rpcs != is_global_rpc: + s_config.append(config) + else: + s_config = None + out['subsystems'].append({ + 'subsystem': s['subsystem'], + 'config': s_config, + }) + + print(json.dumps(out, indent=2)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('-method', dest='method') + + args = parser.parse_args() + if args.method == "delete_global_parameters": + filter_methods(True) + elif args.method == "delete_configs": + filter_methods(False) + elif args.method == "sort": + """ Wrap input into JSON object so any input is possible here + like output from get_bdevs RPC method""" + o = json.loads('{ "the_object": ' + sys.stdin.read() + ' }') + print(json.dumps(sort_json_object(o)['the_object'], indent=2)) + else: + raise ValueError("Invalid method '{}'".format(args.method)) diff --git a/src/spdk/test/lvol/lvol.sh b/src/spdk/test/lvol/lvol.sh new file mode 100755 index 00000000..a5883765 --- /dev/null +++ b/src/spdk/test/lvol/lvol.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +set -e +BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../ && pwd)" + +total_size=256 +block_size=512 +test_cases=all +x="" + +rpc_py="$TEST_DIR/scripts/rpc.py " + +function usage() { + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for doing automated lvol tests" + echo "Usage: $(basename $1) [OPTIONS]" + echo + echo "-h, --help print help and exit" + echo " --total-size Size of malloc bdev in MB (int > 0)" + echo " --block-size Block size for this bdev" + echo "-x set -x for script debug" + echo " --test-cases= List test cases which will be run: + 1: 'construct_lvs_positive', + 50: 'construct_logical_volume_positive', + 51: 'construct_multi_logical_volumes_positive', + 52: 'construct_lvol_bdev_using_name_positive', + 53: 'construct_lvol_bdev_duplicate_names_positive', + 100: 'construct_logical_volume_nonexistent_lvs_uuid', + 101: 'construct_lvol_bdev_on_full_lvol_store', + 102: 'construct_lvol_bdev_name_twice', + 150: 'resize_lvol_bdev_positive', + 200: 'resize_logical_volume_nonexistent_logical_volume', + 201: 'resize_logical_volume_with_size_out_of_range', + 250: 'destroy_lvol_store_positive', + 251: 'destroy_lvol_store_use_name_positive', + 252: 'destroy_lvol_store_with_lvol_bdev_positive', + 253: 'destroy_multi_logical_volumes_positive', + 254: 'destroy_after_resize_lvol_bdev_positive', + 255: 'delete_lvol_store_persistent_positive', + 300: 'destroy_lvol_store_nonexistent_lvs_uuid', + 301: 'delete_lvol_store_underlying_bdev', + 350: 'nested_destroy_logical_volume_negative', + 400: 'nested_construct_logical_volume_positive', + 450: 'construct_lvs_nonexistent_bdev', + 451: 'construct_lvs_on_bdev_twice', + 452: 'construct_lvs_name_twice', + 500: 'nested_construct_lvol_bdev_on_full_lvol_store', + 550: 'delete_bdev_positive', + 551: 'delete_lvol_bdev', + 552: 'destroy_lvol_store_with_clones', + 553: 'unregister_lvol_bdev', + 600: 'construct_lvol_store_with_cluster_size_max', + 601: 'construct_lvol_store_with_cluster_size_min', + 650: 'thin_provisioning_check_space', + 651: 'thin_provisioning_read_empty_bdev', + 652: 'thin_provisionind_data_integrity_test', + 653: 'thin_provisioning_resize', + 654: 'thin_overprovisioning', + 655: 'thin_provisioning_filling_disks_less_than_lvs_size', + 700: 'tasting_positive', + 701: 'tasting_lvol_store_positive', + 750: 'snapshot_readonly', + 751: 'snapshot_compare_with_lvol_bdev', + 752: 'snapshot_during_io_traffic', + 753: 'snapshot_of_snapshot', + 754: 'clone_bdev_only', + 755: 'clone_writing_clone', + 756: 'clone_and_snapshot_consistency', + 757: 'clone_inflate', + 758: 'clone_decouple_parent', + 759: 'clone_decouple_parent_rw', + 800: 'rename_positive', + 801: 'rename_lvs_nonexistent', + 802: 'rename_lvs_EEXIST', + 803: 'rename_lvol_bdev_nonexistent', + 804: 'rename_lvol_bdev_EEXIST', + 10000: 'SIGTERM' + or + all: This parameter runs all tests + Ex: \"1,2,19,20\", default: all" + echo + echo +} + +while getopts 'xh-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 && exit 0;; + total-size=*) total_size="${OPTARG#*=}" ;; + block-size=*) block_size="${OPTARG#*=}" ;; + test-cases=*) test_cases="${OPTARG#*=}" ;; + *) usage $0 "Invalid argument '$OPTARG'" && exit 1 ;; + esac + ;; + h) usage $0 && exit 0 ;; + x) set -x + x="-x" ;; + *) usage $0 "Invalid argument '$OPTARG'" && exit 1 ;; + esac +done +shift $(( OPTIND - 1 )) + +source $TEST_DIR/test/common/autotest_common.sh + +### Function starts vhost app +function vhost_start() +{ + modprobe nbd + $TEST_DIR/app/vhost/vhost & + vhost_pid=$! + echo $vhost_pid > $BASE_DIR/vhost.pid + waitforlisten $vhost_pid +} + +### Function stops vhost app +function vhost_kill() +{ + ### Kill with SIGKILL param + if pkill -F $BASE_DIR/vhost.pid; then + sleep 1 + fi + rm $BASE_DIR/vhost.pid || true + rmmod nbd || true +} + +trap "vhost_kill; rm -f $BASE_DIR/aio_bdev_0 $BASE_DIR/aio_bdev_1; exit 1" SIGINT SIGTERM EXIT + +truncate -s 400M $BASE_DIR/aio_bdev_0 $BASE_DIR/aio_bdev_1 +vhost_start +$BASE_DIR/lvol_test.py $rpc_py $total_size $block_size $BASE_DIR $TEST_DIR/app/vhost "${test_cases[@]}" + +vhost_kill +rm -rf $BASE_DIR/aio_bdev_0 $BASE_DIR/aio_bdev_1 +trap - SIGINT SIGTERM EXIT diff --git a/src/spdk/test/lvol/lvol_test.py b/src/spdk/test/lvol/lvol_test.py new file mode 100755 index 00000000..50255f1f --- /dev/null +++ b/src/spdk/test/lvol/lvol_test.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import sys +from test_cases import * + + +if __name__ == "__main__": + rpc_py = None + total_size = None + block_size = None + num_test = None + fail_count = 0 + tc_failed = [] + tc_list = [] + + if len(sys.argv) == 7 and len(sys.argv[6].split(',')) <= test_counter(): + rpc_py = sys.argv[1] + total_size = int(sys.argv[2]) + block_size = int(sys.argv[3]) + base_dir_path = sys.argv[4] + app_path = sys.argv[5] + tc_list = sys.argv[6].split(',') + else: + print("Invalid argument") + try: + tc = TestCases(rpc_py, total_size, block_size, base_dir_path, app_path) + if "all" in tc_list: + tc_list = sorted([i.split("test_case")[1] for i in dir(TestCases) if "test_case" in i], key=int) + + for num_test in tc_list: + fail_count = 0 + exec("fail_count += tc.test_case{num_test}" + "()".format(num_test=num_test)) + if fail_count: + tc_failed.append(num_test) + + if not tc_failed: + print("RESULT: All test cases - PASS") + elif tc_failed: + print("RESULT: Some test cases FAIL") + print(tc_failed) + sys.exit(1) + except BaseException: + print("Test: {num_test} - FAIL".format(num_test=num_test)) + sys.exit(1) diff --git a/src/spdk/test/lvol/rpc_commands_lib.py b/src/spdk/test/lvol/rpc_commands_lib.py new file mode 100644 index 00000000..0857c73d --- /dev/null +++ b/src/spdk/test/lvol/rpc_commands_lib.py @@ -0,0 +1,241 @@ +import json +import sys +from uuid import UUID +from subprocess import check_output, CalledProcessError + + +class Spdk_Rpc(object): + def __init__(self, rpc_py): + self.rpc_py = rpc_py + + def __getattr__(self, name): + def call(*args): + cmd = "{} {} {}".format(sys.executable, self.rpc_py, name) + for arg in args: + cmd += " {}".format(arg) + try: + output = check_output(cmd, shell=True) + return output.decode('ascii').rstrip('\n'), 0 + except CalledProcessError as e: + print("ERROR: RPC Command {cmd} " + "execution failed:". format(cmd=cmd)) + print("Failed command output:") + print(e.output) + return e.output.decode('ascii'), e.returncode + return call + + +class Commands_Rpc(object): + def __init__(self, rpc_py): + self.rpc = Spdk_Rpc(rpc_py) + + def check_get_bdevs_methods(self, uuid_bdev, bdev_size_mb, bdev_alias=""): + print("INFO: Check RPC COMMAND get_bdevs") + output = self.rpc.get_bdevs()[0] + json_value = json.loads(output) + for i in range(len(json_value)): + uuid_json = json_value[i]['name'] + aliases = json_value[i]['aliases'] + + if uuid_bdev in [uuid_json]: + print("Info: UUID:{uuid} is found in RPC Command: " + "gets_bdevs response".format(uuid=uuid_bdev)) + # Check if human-friendly alias is as expected + if bdev_alias and aliases: + if bdev_alias not in aliases: + print("ERROR: Expected bdev alias not found") + print("Expected: {name}".format(name=bdev_alias)) + print("Actual: {aliases}".format(aliases=aliases)) + return 1 + # num_block and block_size have values in bytes + num_blocks = json_value[i]['num_blocks'] + block_size = json_value[i]['block_size'] + if num_blocks * block_size == bdev_size_mb * 1024 * 1024: + print("Info: Response get_bdevs command is " + "correct. Params: uuid_bdevs: {uuid}, bdev_size " + "{size}".format(uuid=uuid_bdev, + size=bdev_size_mb)) + return 0 + print("INFO: UUID:{uuid} or bdev_size:{bdev_size_mb} not found in " + "RPC COMMAND get_bdevs: " + "{json_value}".format(uuid=uuid_bdev, bdev_size_mb=bdev_size_mb, + json_value=json_value)) + return 1 + + def check_get_lvol_stores(self, base_name, uuid, cluster_size=None, lvs_name=""): + print("INFO: RPC COMMAND get_lvol_stores") + json_value = self.get_lvol_stores() + if json_value: + for i in range(len(json_value)): + json_uuid = json_value[i]['uuid'] + json_cluster = json_value[i]['cluster_size'] + json_base_name = json_value[i]['base_bdev'] + json_name = json_value[i]['name'] + + if base_name in json_base_name \ + and uuid in json_uuid: + print("INFO: base_name:{base_name} is found in RPC " + "Command: get_lvol_stores " + "response".format(base_name=base_name)) + print("INFO: UUID:{uuid} is found in RPC Command: " + "get_lvol_stores response".format(uuid=uuid)) + if cluster_size: + if str(cluster_size) in str(json_cluster): + print("Info: Cluster size :{cluster_size} is found in RPC " + "Command: get_lvol_stores " + "response".format(cluster_size=cluster_size)) + else: + print("ERROR: Wrong cluster size in lvol store") + print("Expected:".format(cluster_size)) + print("Actual:".format(json_cluster)) + return 1 + + # Also check name if param is provided: + if lvs_name: + if lvs_name not in json_name: + print("ERROR: Lvol store human-friendly name does not match") + print("Expected: {lvs_name}".format(lvs_name=lvs_name)) + print("Actual: {name}".format(name=json_name)) + return 1 + return 0 + print("FAILED: UUID: lvol store {uuid} on base_bdev: " + "{base_name} not found in get_lvol_stores()".format(uuid=uuid, + base_name=base_name)) + return 1 + else: + print("INFO: Lvol store not exist") + return 2 + return 0 + + def construct_malloc_bdev(self, total_size, block_size): + print("INFO: RPC COMMAND construct_malloc_bdev") + output = self.rpc.construct_malloc_bdev(total_size, block_size)[0] + return output.rstrip('\n') + + def construct_lvol_store(self, base_name, lvs_name, cluster_size=None): + print("INFO: RPC COMMAND construct_lvol_store") + if cluster_size: + output = self.rpc.construct_lvol_store(base_name, + lvs_name, + "-c {cluster_sz}".format(cluster_sz=cluster_size))[0] + else: + output = self.rpc.construct_lvol_store(base_name, lvs_name)[0] + return output.rstrip('\n') + + def construct_lvol_bdev(self, uuid, lbd_name, size, thin=False): + print("INFO: RPC COMMAND construct_lvol_bdev") + try: + uuid_obj = UUID(uuid) + name_opt = "-u" + except ValueError: + name_opt = "-l" + thin_provisioned = "" + if thin: + thin_provisioned = "-t" + output = self.rpc.construct_lvol_bdev(name_opt, uuid, lbd_name, size, thin_provisioned)[0] + return output.rstrip('\n') + + def destroy_lvol_store(self, uuid): + print("INFO: RPC COMMAND destroy_lvol_store") + try: + uuid_obj = UUID(uuid) + name_opt = "-u" + except ValueError: + name_opt = "-l" + output, rc = self.rpc.destroy_lvol_store(name_opt, uuid) + return rc + + def delete_bdev(self, base_name): + print("INFO: RPC COMMAND delete_bdev") + output, rc = self.rpc.delete_bdev(base_name) + return rc + + def delete_malloc_bdev(self, base_name): + print("INFO: RPC COMMAND delete_malloc_bdev") + output, rc = self.rpc.delete_malloc_bdev(base_name) + return rc + + def destroy_lvol_bdev(self, bdev_name): + print("INFO: RPC COMMAND destroy_lvol_bdev") + output, rc = self.rpc.destroy_lvol_bdev(bdev_name) + return rc + + def resize_lvol_bdev(self, uuid, new_size): + print("INFO: RPC COMMAND resize_lvol_bdev") + output, rc = self.rpc.resize_lvol_bdev(uuid, new_size) + return rc + + def start_nbd_disk(self, bdev_name, nbd_name): + print("INFO: RPC COMMAND start_nbd_disk") + output, rc = self.rpc.start_nbd_disk(bdev_name, nbd_name) + return rc + + def stop_nbd_disk(self, nbd_name): + print("INFO: RPC COMMAND stop_nbd_disk") + output, rc = self.rpc.stop_nbd_disk(nbd_name) + return rc + + def get_lvol_stores(self, name=None): + print("INFO: RPC COMMAND get_lvol_stores") + if name: + output = json.loads(self.rpc.get_lvol_stores("-l", name)[0]) + else: + output = json.loads(self.rpc.get_lvol_stores()[0]) + return output + + def get_lvol_bdevs(self): + print("INFO: RPC COMMAND get_bdevs; lvol bdevs only") + output = [] + rpc_output = json.loads(self.rpc.get_bdevs()[0]) + for bdev in rpc_output: + if bdev["product_name"] == "Logical Volume": + output.append(bdev) + return output + + def get_lvol_bdev_with_name(self, name): + print("INFO: RPC COMMAND get_bdevs; lvol bdevs only") + rpc_output = json.loads(self.rpc.get_bdevs("-b", name)[0]) + if len(rpc_output) > 0: + return rpc_output[0] + + return None + + def rename_lvol_store(self, old_name, new_name): + print("INFO: Renaming lvol store from {old} to {new}".format(old=old_name, new=new_name)) + output, rc = self.rpc.rename_lvol_store(old_name, new_name) + return rc + + def rename_lvol_bdev(self, old_name, new_name): + print("INFO: Renaming lvol bdev from {old} to {new}".format(old=old_name, new=new_name)) + output, rc = self.rpc.rename_lvol_bdev(old_name, new_name) + return rc + + def snapshot_lvol_bdev(self, bdev_name, snapshot_name): + print("INFO: RPC COMMAND snapshot_lvol_bdev") + output, rc = self.rpc.snapshot_lvol_bdev(bdev_name, snapshot_name) + return rc + + def clone_lvol_bdev(self, snapshot_name, clone_name): + print("INFO: RPC COMMAND clone_lvol_bdev") + output, rc = self.rpc.clone_lvol_bdev(snapshot_name, clone_name) + return rc + + def inflate_lvol_bdev(self, clone_name): + print("INFO: RPC COMMAND inflate_lvol_bdev") + output, rc = self.rpc.inflate_lvol_bdev(clone_name) + return rc + + def decouple_parent_lvol_bdev(self, clone_name): + print("INFO: RPC COMMAND decouple_parent_lvol_bdev") + output, rc = self.rpc.decouple_parent_lvol_bdev(clone_name) + return rc + + def construct_aio_bdev(self, aio_path, aio_name, aio_bs=""): + print("INFO: RPC COMMAND construct_aio_bdev") + output, rc = self.rpc.construct_aio_bdev(aio_path, aio_name, aio_bs) + return rc + + def delete_aio_bdev(self, aio_name): + print("INFO: RPC COMMAND delete_aio_bdev") + output, rc = self.rpc.delete_aio_bdev(aio_name) + return rc diff --git a/src/spdk/test/lvol/test_cases.py b/src/spdk/test/lvol/test_cases.py new file mode 100644 index 00000000..ad85f529 --- /dev/null +++ b/src/spdk/test/lvol/test_cases.py @@ -0,0 +1,2591 @@ +import io +import time +import sys +import random +import signal +import subprocess +import pprint +import socket +import threading +import os + +from errno import ESRCH +from os import kill, path, unlink, path, listdir, remove +from rpc_commands_lib import Commands_Rpc +from time import sleep +from uuid import uuid4 + + +MEGABYTE = 1024 * 1024 + + +current_fio_pid = -1 + + +def is_process_alive(pid): + try: + os.kill(pid, 0) + except Exception as e: + return 1 + + return 0 + + +def get_fio_cmd(nbd_disk, offset, size, rw, pattern, extra_params=""): + fio_template = "fio --name=fio_test --filename=%(file)s --offset=%(offset)s --size=%(size)s"\ + " --rw=%(rw)s --direct=1 %(extra_params)s %(pattern)s" + pattern_template = "" + if pattern: + pattern_template = "--do_verify=1 --verify=pattern --verify_pattern=%s"\ + " --verify_state_save=0" % pattern + fio_cmd = fio_template % {"file": nbd_disk, "offset": offset, "size": size, + "rw": rw, "pattern": pattern_template, + "extra_params": extra_params} + + return fio_cmd + + +def run_fio(fio_cmd, expected_ret_value): + global current_fio_pid + try: + proc = subprocess.Popen([fio_cmd], shell=True) + current_fio_pid = proc.pid + proc.wait() + rv = proc.returncode + except Exception as e: + print("ERROR: Fio test ended with unexpected exception.") + rv = 1 + if expected_ret_value == rv: + return 0 + + if rv == 0: + print("ERROR: Fio test ended with unexpected success") + else: + print("ERROR: Fio test ended with unexpected failure") + return 1 + + +class FioThread(threading.Thread): + def __init__(self, nbd_disk, offset, size, rw, pattern, expected_ret_value, + extra_params=""): + super(FioThread, self).__init__() + self.fio_cmd = get_fio_cmd(nbd_disk, offset, size, rw, pattern, + extra_params=extra_params) + self.rv = 1 + self.expected_ret_value = expected_ret_value + + def run(self): + print("INFO: Starting fio") + self.rv = run_fio(self.fio_cmd, self.expected_ret_value) + print("INFO: Fio test finished") + + +def test_counter(): + ''' + :return: the number of tests + ''' + return ['test_case' in i for i in dir(TestCases)].count(True) + + +def case_message(func): + def inner(*args, **kwargs): + test_name = { + 1: 'construct_lvs_positive', + 50: 'construct_logical_volume_positive', + 51: 'construct_multi_logical_volumes_positive', + 52: 'construct_lvol_bdev_using_name_positive', + 53: 'construct_lvol_bdev_duplicate_names_positive', + 100: 'construct_logical_volume_nonexistent_lvs_uuid', + 101: 'construct_lvol_bdev_on_full_lvol_store', + 102: 'construct_lvol_bdev_name_twice', + 150: 'resize_lvol_bdev_positive', + 200: 'resize_logical_volume_nonexistent_logical_volume', + 201: 'resize_logical_volume_with_size_out_of_range', + 250: 'destroy_lvol_store_positive', + 251: 'destroy_lvol_store_use_name_positive', + 252: 'destroy_lvol_store_with_lvol_bdev_positive', + 253: 'destroy_multi_logical_volumes_positive', + 254: 'destroy_after_resize_lvol_bdev_positive', + 255: 'delete_lvol_store_persistent_positive', + 300: 'destroy_lvol_store_nonexistent_lvs_uuid', + 301: 'delete_lvol_store_underlying_bdev', + 350: 'nested_destroy_logical_volume_negative', + 400: 'nested_construct_logical_volume_positive', + 450: 'construct_lvs_nonexistent_bdev', + 451: 'construct_lvs_on_bdev_twice', + 452: 'construct_lvs_name_twice', + 500: 'nested_construct_lvol_bdev_on_full_lvol_store', + 550: 'delete_bdev_positive', + 551: 'delete_lvol_bdev', + 552: 'destroy_lvol_store_with_clones', + 553: 'unregister_lvol_bdev', + 600: 'construct_lvol_store_with_cluster_size_max', + 601: 'construct_lvol_store_with_cluster_size_min', + 650: 'thin_provisioning_check_space', + 651: 'thin_provisioning_read_empty_bdev', + 652: 'thin_provisionind_data_integrity_test', + 653: 'thin_provisioning_resize', + 654: 'thin_overprovisioning', + 655: 'thin_provisioning_filling_disks_less_than_lvs_size', + 700: 'tasting_positive', + 701: 'tasting_lvol_store_positive', + 702: 'tasting_positive_with_different_lvol_store_cluster_size', + 750: 'snapshot_readonly', + 751: 'snapshot_compare_with_lvol_bdev', + 752: 'snapshot_during_io_traffic', + 753: 'snapshot_of_snapshot', + 754: 'clone_bdev_only', + 755: 'clone_writing_clone', + 756: 'clone_and_snapshot_consistency', + 757: 'clone_inflate', + 758: 'decouple_parent', + 759: 'decouple_parent_rw', + 800: 'rename_positive', + 801: 'rename_lvs_nonexistent', + 802: 'rename_lvs_EEXIST', + 803: 'rename_lvol_bdev_nonexistent', + 804: 'rename_lvol_bdev_EEXIST', + 10000: 'SIGTERM', + } + num = int(func.__name__.strip('test_case')[:]) + print("************************************") + print("START TEST CASE {name}".format(name=test_name[num])) + print("************************************") + fail_count = func(*args, **kwargs) + print("************************************") + if not fail_count: + print("END TEST CASE {name} PASS".format(name=test_name[num])) + else: + print("END TEST CASE {name} FAIL".format(name=test_name[num])) + print("************************************") + return fail_count + return inner + + +class TestCases(object): + + def __init__(self, rpc_py, total_size, block_size, base_dir_path, app_path): + self.c = Commands_Rpc(rpc_py) + self.total_size = total_size + self.block_size = block_size + self.cluster_size = None + self.path = base_dir_path + self.app_path = app_path + self.lvs_name = "lvs_test" + self.lbd_name = "lbd_test" + self.vhost_config_path = path.join(path.dirname(sys.argv[0]), 'vhost.conf') + + def _gen_lvs_uuid(self): + return str(uuid4()) + + def _gen_lvb_uuid(self): + return "_".join([str(uuid4()), str(random.randrange(9999999999))]) + + def compare_two_disks(self, disk1, disk2, expected_ret_value): + cmp_cmd = "cmp %s %s" % (disk1, disk2) + try: + process = subprocess.check_output(cmp_cmd, stderr=subprocess.STDOUT, shell=True) + rv = 0 + except subprocess.CalledProcessError as ex: + rv = 1 + except Exception as e: + print("ERROR: Cmp ended with unexpected exception.") + rv = 1 + + if expected_ret_value == rv: + return 0 + elif rv == 0: + print("ERROR: Cmp ended with unexpected success") + else: + print("ERROR: Cmp ended with unexpected failure") + + return 1 + + def run_fio_test(self, nbd_disk, offset, size, rw, pattern, expected_ret_value=0): + fio_cmd = get_fio_cmd(nbd_disk, offset, size, rw, pattern) + return run_fio(fio_cmd, expected_ret_value) + + def _stop_vhost(self, pid_path): + with io.open(pid_path, 'r') as vhost_pid: + pid = int(vhost_pid.readline()) + if pid: + try: + kill(pid, signal.SIGTERM) + for count in range(30): + sleep(1) + kill(pid, 0) + except OSError as err: + if err.errno == ESRCH: + pass + else: + return 1 + else: + return 1 + else: + return 1 + return 0 + + def _start_vhost(self, vhost_path, pid_path): + subprocess.call("{app} -f " + "{pid} &".format(app=vhost_path, + pid=pid_path), shell=True) + for timeo in range(10): + if timeo == 9: + print("ERROR: Timeout on waiting for app start") + return 1 + if not path.exists(pid_path): + print("Info: Waiting for PID file...") + sleep(1) + continue + else: + break + + # Wait for RPC to open + sock = socket.socket(socket.AF_UNIX) + for timeo in range(30): + if timeo == 29: + print("ERROR: Timeout on waiting for RPC start") + return 1 + try: + sock.connect("/var/tmp/spdk.sock") + break + except socket.error as e: + print("Info: Waiting for RPC Unix socket...") + sleep(1) + continue + else: + sock.close() + break + + with io.open(pid_path, 'r') as vhost_pid: + pid = int(vhost_pid.readline()) + if not pid: + return 1 + return 0 + + def get_lvs_size(self, lvs_name="lvs_test"): + lvs = self.c.get_lvol_stores(lvs_name)[0] + return int(int(lvs['free_clusters'] * lvs['cluster_size']) / MEGABYTE) + + def get_lvs_divided_size(self, split_num, lvs_name="lvs_test"): + # Actual size of lvol bdevs on creation is rounded up to multiple of cluster size. + # In order to avoid over provisioning, this function returns + # lvol store size in MB divided by split_num - rounded down to multiple of cluster size." + lvs = self.c.get_lvol_stores(lvs_name)[0] + return int(int(lvs['free_clusters'] / split_num) * lvs['cluster_size'] / MEGABYTE) + + def get_lvs_cluster_size(self, lvs_name="lvs_test"): + lvs = self.c.get_lvol_stores(lvs_name)[0] + return int(int(lvs['cluster_size']) / MEGABYTE) + + # positive tests + @case_message + def test_case1(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + self.c.destroy_lvol_store(uuid_store) + self.c.delete_malloc_bdev(base_name) + if self.c.check_get_lvol_stores("", "", "") == 1: + fail_count += 1 + return fail_count + + @case_message + def test_case50(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + + lvs_size = self.get_lvs_size() + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name, + lvs_size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, + lvs_size) + self.c.destroy_lvol_bdev(uuid_bdev) + self.c.destroy_lvol_store(uuid_store) + self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case51(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + size = self.get_lvs_divided_size(4) + + for j in range(2): + uuid_bdevs = [] + for i in range(4): + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name + str(i), + size) + uuid_bdevs.append(uuid_bdev) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size) + + for uuid_bdev in uuid_bdevs: + self.c.destroy_lvol_bdev(uuid_bdev) + + self.c.destroy_lvol_store(uuid_store) + self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case52(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs_size = self.get_lvs_size() + uuid_bdev = self.c.construct_lvol_bdev(self.lvs_name, + self.lbd_name, + lvs_size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, + lvs_size) + + fail_count += self.c.destroy_lvol_bdev(uuid_bdev) + fail_count += self.c.destroy_lvol_store(uuid_store) + fail_count += self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case53(self): + base_name_1 = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + base_name_2 = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + + uuid_store_1 = self.c.construct_lvol_store(base_name_1, + self.lvs_name + "1") + uuid_store_2 = self.c.construct_lvol_store(base_name_2, + self.lvs_name + "2") + fail_count = self.c.check_get_lvol_stores(base_name_1, uuid_store_1, + self.cluster_size) + fail_count = self.c.check_get_lvol_stores(base_name_2, uuid_store_2, + self.cluster_size) + + lvs_size = self.get_lvs_size(self.lvs_name + "1") + uuid_bdev_1 = self.c.construct_lvol_bdev(uuid_store_1, + self.lbd_name, + lvs_size) + uuid_bdev_2 = self.c.construct_lvol_bdev(uuid_store_2, + self.lbd_name, + lvs_size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev_1, lvs_size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev_2, lvs_size) + + fail_count += self.c.destroy_lvol_bdev(uuid_bdev_1) + fail_count += self.c.destroy_lvol_bdev(uuid_bdev_2) + fail_count += self.c.destroy_lvol_store(uuid_store_1) + fail_count += self.c.destroy_lvol_store(uuid_store_2) + fail_count += self.c.delete_malloc_bdev(base_name_1) + fail_count += self.c.delete_malloc_bdev(base_name_2) + return fail_count + + @case_message + def test_case100(self): + fail_count = 0 + if self.c.construct_lvol_bdev(self._gen_lvs_uuid(), + self.lbd_name, + 32) == 0: + fail_count += 1 + return fail_count + + @case_message + def test_case101(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs_size = self.get_lvs_size() + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name, + lvs_size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, + lvs_size) + if self.c.construct_lvol_bdev(uuid_store, + self.lbd_name + "_1", + lvs_size) == 0: + fail_count += 1 + + self.c.destroy_lvol_bdev(uuid_bdev) + self.c.destroy_lvol_store(uuid_store) + self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case102(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + size = self.get_lvs_size() + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name, + size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, + size) + if self.c.construct_lvol_bdev(uuid_store, + self.lbd_name, + size) == 0: + fail_count += 1 + + self.c.destroy_lvol_bdev(uuid_bdev) + self.c.destroy_lvol_store(uuid_store) + self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case150(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + # size is equal to one quarter of size malloc bdev + + size = self.get_lvs_divided_size(4) + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size) + + # size is equal to half of size malloc bdev + size = self.get_lvs_divided_size(2) + self.c.resize_lvol_bdev(uuid_bdev, size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size) + + # size is smaller by 1 cluster + size = (self.get_lvs_size() - self.get_lvs_cluster_size()) + self.c.resize_lvol_bdev(uuid_bdev, size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size) + + # size is equal 0 MiB + size = 0 + self.c.resize_lvol_bdev(uuid_bdev, size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size) + + self.c.destroy_lvol_bdev(uuid_bdev) + self.c.destroy_lvol_store(uuid_store) + self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case200(self): + fail_count = 0 + if self.c.resize_lvol_bdev(self._gen_lvb_uuid(), 16) == 0: + fail_count += 1 + return fail_count + + @case_message + def test_case201(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs_size = self.get_lvs_size() + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name, + lvs_size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, + lvs_size) + if self.c.resize_lvol_bdev(uuid_bdev, self.total_size + 1) == 0: + fail_count += 1 + + self.c.destroy_lvol_bdev(uuid_bdev) + self.c.destroy_lvol_store(uuid_store) + self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case250(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + self.c.destroy_lvol_store(uuid_store) + if self.c.check_get_lvol_stores("", "", "") == 1: + fail_count += 1 + self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case251(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + fail_count += self.c.destroy_lvol_store(self.lvs_name) + if self.c.check_get_lvol_stores("", "", "") == 1: + fail_count += 1 + fail_count += self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case252(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs_size = self.get_lvs_size() + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name, + lvs_size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, + lvs_size) + if self.c.destroy_lvol_store(uuid_store) != 0: + fail_count += 1 + + if self.c.check_get_lvol_stores("", "", "") == 1: + fail_count += 1 + self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case253(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + size = self.get_lvs_divided_size(4) + + for i in range(4): + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name + str(i), + size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size) + + self.c.destroy_lvol_store(uuid_store) + if self.c.check_get_lvol_stores("", "", "") == 1: + fail_count += 1 + self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case254(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + size = self.get_lvs_divided_size(4) + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name, + size) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size) + + sz = size + 4 + self.c.resize_lvol_bdev(uuid_bdev, sz) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, sz) + sz = size * 2 + self.c.resize_lvol_bdev(uuid_bdev, sz) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, sz) + sz = size * 3 + self.c.resize_lvol_bdev(uuid_bdev, sz) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, sz) + sz = (size * 4) - 4 + self.c.resize_lvol_bdev(uuid_bdev, sz) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, sz) + sz = 0 + self.c.resize_lvol_bdev(uuid_bdev, sz) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, sz) + + self.c.destroy_lvol_store(uuid_store) + if self.c.check_get_lvol_stores("", "", "") == 1: + fail_count += 1 + self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case255(self): + base_path = path.dirname(sys.argv[0]) + base_name = "aio_bdev0" + aio_bdev0 = path.join(base_path, "aio_bdev_0") + self.c.construct_aio_bdev(aio_bdev0, base_name, 4096) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + if self.c.destroy_lvol_store(self.lvs_name) != 0: + fail_count += 1 + + self.c.delete_aio_bdev(base_name) + self.c.construct_aio_bdev(aio_bdev0, base_name, 4096) + # wait 1 second to allow time for lvolstore tasting + sleep(1) + + ret_value = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + if ret_value == 0: + fail_count += 1 + self.c.delete_aio_bdev(base_name) + return fail_count + + @case_message + def test_case300(self): + fail_count = 0 + if self.c.destroy_lvol_store(self._gen_lvs_uuid()) == 0: + fail_count += 1 + return fail_count + + @case_message + def test_case301(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + + if self.c.delete_malloc_bdev(base_name) != 0: + fail_count += 1 + + if self.c.destroy_lvol_store(uuid_store) == 0: + fail_count += 1 + + return fail_count + + def test_case350(self): + print("Test of this feature not yet implemented.") + pass + return 0 + + def test_case400(self): + print("Test of this feature not yet implemented.") + pass + return 0 + + # negative tests + @case_message + def test_case450(self): + fail_count = 0 + bad_bdev_id = random.randrange(999999999) + if self.c.construct_lvol_store(bad_bdev_id, + self.lvs_name, + self.cluster_size) == 0: + fail_count += 1 + return fail_count + + @case_message + def test_case451(self): + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + if self.c.construct_lvol_store(base_name, + self.lvs_name) == 0: + fail_count += 1 + self.c.destroy_lvol_store(uuid_store) + self.c.delete_malloc_bdev(base_name) + return fail_count + + @case_message + def test_case452(self): + fail_count = 0 + base_name_1 = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + base_name_2 = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store_1 = self.c.construct_lvol_store(base_name_1, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name_1, + uuid_store_1, + self.cluster_size) + if self.c.construct_lvol_store(base_name_2, + self.lvs_name) == 0: + fail_count += 1 + + fail_count += self.c.destroy_lvol_store(uuid_store_1) + fail_count += self.c.delete_malloc_bdev(base_name_1) + fail_count += self.c.delete_malloc_bdev(base_name_2) + + return fail_count + + def test_case500(self): + """ + nested_construct_lvol_bdev_on_full_lvol_store + + Negative test for constructing a new nested lvol bdev. + Call construct_lvol_bdev on a full lvol store. + """ + # Steps: + # - create a malloc bdev + # - construct_lvol_store on created malloc bdev + # - check correct uuid values in response get_lvol_stores command + # - construct_lvol_bdev on correct lvs_uuid and size is + # equal to size malloc bdev + # - construct nested lvol store on previously created lvol_bdev + # - check correct uuid values in response get_lvol_stores command + # - construct nested lvol bdev on previously created nested lvol store + # and size is equal to size lvol store + # - try construct another lvol bdev as in previous step; this call should fail + # as nested lvol store space is already claimed by lvol bdev + # - delete nested lvol bdev + # - destroy nested lvol_store + # - delete base lvol bdev + # - delete base lvol store + # - delete malloc bdev + # + # Expected result: + # - second construct_lvol_bdev call on nested lvol store return code != 0 + # - EEXIST response printed to stdout + # - no other operation fails + print("Test of this feature not yet implemented.") + pass + return 0 + + @case_message + def test_case550(self): + """ + delete_bdev_positive + + Positive test for deleting malloc bdev. + Call construct_lvol_store with correct base bdev name. + """ + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Construct_lvol_store on correct, exisitng malloc bdev + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + # Check correct uuid values in response get_lvol_stores command + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + # Delete malloc bdev + self.c.delete_malloc_bdev(base_name) + # Check response get_lvol_stores command + if self.c.check_get_lvol_stores("", "", "") == 1: + fail_count += 1 + + # Expected result: + # - get_lvol_stores: response should be of no value after destroyed lvol store + # - no other operation fails + return fail_count + + @case_message + def test_case551(self): + """ + destroy_lvol_bdev_ordering + + Test for destroying lvol bdevs in particular order. + Check destroying wrong one is not possible and returns error. + """ + + fail_count = 0 + snapshot_name = "snapshot" + clone_name = "clone" + + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Construct_lvol_store on correct, exisitng malloc bdev + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name, + self.cluster_size) + # Check correct uuid values in response get_lvol_stores command + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs = self.c.get_lvol_stores() + size = int(int(lvs[0]['free_clusters'] * lvs[0]['cluster_size']) / 4 / MEGABYTE) + + # Construct thin provisioned lvol bdev + uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name, size, thin=True) + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0) + + # Create snapshot of thin provisioned lvol bdev + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name) + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + + # Create clone of snapshot and check if it ends with success + fail_count += self.c.clone_lvol_bdev(self.lvs_name + "/" + snapshot_name, clone_name) + clone_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name) + + # Try to destroy snapshot with clones and check if it fails + ret_value = self.c.destroy_lvol_bdev(snapshot_bdev['name']) + if ret_value == 0: + print("ERROR: Delete snapshot should fail but didn't") + fail_count += 1 + + # Destroy clone and then snapshot + fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name']) + fail_count += self.c.destroy_lvol_bdev(clone_bdev['name']) + fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name']) + + # Destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + + # Check response get_lvol_stores command + if self.c.check_get_lvol_stores("", "", "") == 1: + fail_count += 1 + + # Delete malloc bdev + self.c.delete_malloc_bdev(base_name) + # Expected result: + # - get_lvol_stores: response should be of no value after destroyed lvol store + # - no other operation fails + return fail_count + + @case_message + def test_case552(self): + """ + destroy_lvol_store_with_clones + + Test for destroying lvol store with clones present, + without removing them first. + """ + + fail_count = 0 + snapshot_name = "snapshot" + snapshot_name2 = "snapshot2" + clone_name = "clone" + + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Construct_lvol_store on correct, exisitng malloc bdev + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name, + self.cluster_size) + # Check correct uuid values in response get_lvol_stores command + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs = self.c.get_lvol_stores() + size = int(int(lvs[0]['free_clusters'] * lvs[0]['cluster_size']) / 4 / MEGABYTE) + + # Create lvol bdev, snapshot it, then clone it and then snapshot the clone + uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, size, thin=True) + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0) + + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name) + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + + fail_count += self.c.clone_lvol_bdev(self.lvs_name + "/" + snapshot_name, clone_name) + clone_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name) + + fail_count += self.c.snapshot_lvol_bdev(clone_bdev['name'], snapshot_name2) + snapshot_bdev2 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name2) + + # Try to destroy snapshots with clones and check if it fails + ret_value = self.c.destroy_lvol_bdev(snapshot_bdev['name']) + if ret_value == 0: + print("ERROR: Delete snapshot should fail but didn't") + fail_count += 1 + ret_value = self.c.destroy_lvol_bdev(snapshot_bdev2['name']) + if ret_value == 0: + print("ERROR: Delete snapshot should fail but didn't") + fail_count += 1 + + # Destroy lvol store without deleting lvol bdevs + fail_count += self.c.destroy_lvol_store(uuid_store) + + # Check response get_lvol_stores command + if self.c.check_get_lvol_stores("", "", "") == 1: + fail_count += 1 + + # Delete malloc bdev + self.c.delete_malloc_bdev(base_name) + # Expected result: + # - get_lvol_stores: response should be of no value after destroyed lvol store + # - no other operation fails + return fail_count + + @case_message + def test_case553(self): + """ + unregister_lvol_bdev + + Test for unregistering the lvol bdevs. + Removing malloc bdev under an lvol store triggers unregister of + all lvol bdevs. Verify it with clones present. + """ + + fail_count = 0 + snapshot_name = "snapshot" + snapshot_name2 = "snapshot2" + clone_name = "clone" + + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Construct_lvol_store on correct, exisitng malloc bdev + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name, + self.cluster_size) + # Check correct uuid values in response get_lvol_stores command + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs = self.c.get_lvol_stores() + size = int(int(lvs[0]['free_clusters'] * lvs[0]['cluster_size']) / 4 / MEGABYTE) + + # Create lvol bdev, snapshot it, then clone it and then snapshot the clone + uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, size, thin=True) + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0) + + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name) + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + + fail_count += self.c.clone_lvol_bdev(self.lvs_name + "/" + snapshot_name, clone_name) + clone_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name) + + fail_count += self.c.snapshot_lvol_bdev(clone_bdev['name'], snapshot_name2) + snapshot_bdev2 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name2) + + # Delete malloc bdev + self.c.delete_malloc_bdev(base_name) + + # Check response get_lvol_stores command + if self.c.check_get_lvol_stores("", "", "") == 1: + fail_count += 1 + + # Expected result: + # - get_lvol_stores: response should be of no value after destroyed lvol store + # - no other operation fails + return fail_count + + @case_message + def test_case600(self): + """ + construct_lvol_store_with_cluster_size_max + + Negative test for constructing a new lvol store. + Call construct_lvol_store with cluster size is equal malloc bdev size + 1B. + """ + fail_count = 0 + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Construct_lvol_store on correct, exisitng malloc bdev and cluster size equal + # malloc bdev size in bytes + 1B + lvol_uuid = self.c.construct_lvol_store(base_name, + self.lvs_name, + (self.total_size * 1024 * 1024) + 1) == 0 + if self.c.check_get_lvol_stores(base_name, lvol_uuid) == 0: + fail_count += 1 + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - return code != 0 + # - Error code response printed to stdout + return fail_count + + @case_message + def test_case601(self): + """ + construct_lvol_store_with_cluster_size_min + + Negative test for constructing a new lvol store. + Call construct_lvol_store with cluster size smaller than minimal value of 8192. + """ + fail_count = 0 + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Try construct lvol store on malloc bdev with cluster size 8191 + lvol_uuid = self.c.construct_lvol_store(base_name, self.lvs_name, 8191) + # Verify that lvol store was not created + if self.c.check_get_lvol_stores(base_name, lvol_uuid) == 0: + fail_count += 1 + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - construct lvol store return code != 0 + # - Error code response printed to stdout + return fail_count + + @case_message + def test_case650(self): + """ + thin_provisioning_check_space + + Check if free clusters number on lvol store decreases + if we write to created thin provisioned lvol bdev + """ + # create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # create lvol store on mamloc bdev + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs = self.c.get_lvol_stores(self.lvs_name)[0] + free_clusters_start = int(lvs['free_clusters']) + bdev_size = self.get_lvs_size() + # create thin provisioned lvol bdev with size equals to lvol store free space + bdev_name = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, + bdev_size, thin=True) + lvs = self.c.get_lvol_stores(self.lvs_name)[0] + free_clusters_create_lvol = int(lvs['free_clusters']) + # check and save number of free clusters for lvol store + if free_clusters_start != free_clusters_create_lvol: + fail_count += 1 + lvol_bdev = self.c.get_lvol_bdev_with_name(bdev_name) + nbd_name = "/dev/nbd0" + fail_count += self.c.start_nbd_disk(bdev_name, nbd_name) + + size = int(lvs['cluster_size']) + # write data (lvs cluster size) to created lvol bdev starting from offset 0. + fail_count += self.run_fio_test("/dev/nbd0", 0, size, "write", "0xcc") + lvs = self.c.get_lvol_stores(self.lvs_name)[0] + free_clusters_first_fio = int(lvs['free_clusters']) + # check that free clusters on lvol store was decremented by 1 + if free_clusters_start != free_clusters_first_fio + 1: + fail_count += 1 + + size = int(lvs['cluster_size']) + # calculate size of one and half cluster + offset = int((int(lvol_bdev['num_blocks']) * int(lvol_bdev['block_size']) / + free_clusters_create_lvol) * 1.5) + # write data (lvs cluster size) to lvol bdev with offset set to one and half of cluster size + fail_count += self.run_fio_test(nbd_name, offset, size, "write", "0xcc") + lvs = self.c.get_lvol_stores(self.lvs_name)[0] + free_clusters_second_fio = int(lvs['free_clusters']) + # check that free clusters on lvol store was decremented by 2 + if free_clusters_start != free_clusters_second_fio + 3: + fail_count += 1 + + size = (free_clusters_create_lvol - 3) * int(lvs['cluster_size']) + offset = int(int(lvol_bdev['num_blocks']) * int(lvol_bdev['block_size']) / + free_clusters_create_lvol * 3) + # write data to lvol bdev to the end of its size + fail_count += self.run_fio_test(nbd_name, offset, size, "write", "0xcc") + lvs = self.c.get_lvol_stores(self.lvs_name)[0] + free_clusters_third_fio = int(lvs['free_clusters']) + # check that lvol store free clusters number equals to 0 + if free_clusters_third_fio != 0: + fail_count += 1 + + fail_count += self.c.stop_nbd_disk(nbd_name) + # destroy thin provisioned lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name']) + lvs = self.c.get_lvol_stores(self.lvs_name)[0] + free_clusters_end = int(lvs['free_clusters']) + # check that saved number of free clusters equals to current free clusters + if free_clusters_start != free_clusters_end: + fail_count += 1 + # destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + # destroy malloc bdev + fail_count += self.c.delete_malloc_bdev(base_name) + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case651(self): + """ + thin_provisioning_read_empty_bdev + + Check if we can create thin provisioned bdev on empty lvol store + and check if we can read from this device and it returns zeroes. + """ + # create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # construct lvol store on malloc bdev + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs = self.c.get_lvol_stores(self.lvs_name)[0] + free_clusters_start = int(lvs['free_clusters']) + lbd_name0 = self.lbd_name + str("0") + lbd_name1 = self.lbd_name + str("1") + # calculate bdev size in megabytes + bdev_size = self.get_lvs_size() + # create thick provisioned lvol bvdev with size equal to lvol store + bdev_name0 = self.c.construct_lvol_bdev(uuid_store, lbd_name0, + bdev_size, thin=False) + # create thin provisioned lvol bdev with the same size + bdev_name1 = self.c.construct_lvol_bdev(uuid_store, lbd_name1, + bdev_size, thin=True) + lvol_bdev0 = self.c.get_lvol_bdev_with_name(bdev_name0) + lvol_bdev1 = self.c.get_lvol_bdev_with_name(bdev_name1) + nbd_name0 = "/dev/nbd0" + fail_count += self.c.start_nbd_disk(lvol_bdev0['name'], nbd_name0) + nbd_name1 = "/dev/nbd1" + fail_count += self.c.start_nbd_disk(lvol_bdev1['name'], nbd_name1) + + size = bdev_size * MEGABYTE + # fill the whole thick provisioned lvol bdev + fail_count += self.run_fio_test(nbd_name0, 0, size, "write", False) + + size = bdev_size * MEGABYTE + # perform read operations on thin provisioned lvol bdev + # and check if they return zeroes + fail_count += self.run_fio_test(nbd_name1, 0, size, "read", "0x00") + + fail_count += self.c.stop_nbd_disk(nbd_name0) + fail_count += self.c.stop_nbd_disk(nbd_name1) + # destroy thin provisioned lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev0['name']) + fail_count += self.c.destroy_lvol_bdev(lvol_bdev1['name']) + # destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + # destroy malloc bdev + fail_count += self.c.delete_malloc_bdev(base_name) + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case652(self): + """ + thin_provisioning_data_integrity_test + + Check if data written to thin provisioned lvol bdev + were properly written (fio test with verification). + """ + # create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # construct lvol store on malloc bdev + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs = self.c.get_lvol_stores(self.lvs_name)[0] + free_clusters_start = int(lvs['free_clusters']) + bdev_size = self.get_lvs_size() + # construct thin provisioned lvol bdev with size equal to lvol store + bdev_name = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, + bdev_size, thin=True) + + lvol_bdev = self.c.get_lvol_bdev_with_name(bdev_name) + nbd_name = "/dev/nbd0" + fail_count += self.c.start_nbd_disk(lvol_bdev['name'], nbd_name) + size = bdev_size * MEGABYTE + # on the whole lvol bdev perform write operation with verification + fail_count += self.run_fio_test(nbd_name, 0, size, "write", "0xcc") + + fail_count += self.c.stop_nbd_disk(nbd_name) + # destroy thin provisioned lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name']) + # destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + # destroy malloc bdev + fail_count += self.c.delete_malloc_bdev(base_name) + # Expected result: + # - calls successful, return code = 0 + # - verification ends with success + # - no other operation fails + return fail_count + + @case_message + def test_case653(self): + """ + thin_provisioning_resize + + Check thin provisioned bdev resize. To be implemented. + """ + # TODO + # create malloc bdev + # construct lvol store on malloc bdev + # construct thin provisioned lvol bdevs on created lvol store + # with size equal to 50% of lvol store + # fill all free space of lvol bdev with data + # save number of free clusters for lvs + # resize bdev to full size of lvs + # check if bdev size changed (total_data_clusters*cluster_size + # equal to num_blocks*block_size) + # check if free_clusters on lvs remain unaffected + # perform write operation with verification + # to newly created free space of lvol bdev + # resize bdev to 30M and check if it ended with success + # check if free clusters on lvs equals to saved counter + # destroy thin provisioned lvol bdev + # destroy lvol store + # destroy malloc bdev + fail_count = 0 + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case654(self): + """ + thin_overprovisioning + + Create two thin provisioned lvol bdevs with max size + and check if writting more than total size of lvol store + will cause failures. + """ + # create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # construct lvol store on malloc bdev + uuid_store = self.c.construct_lvol_store(base_name, self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs = self.c.get_lvol_stores(self.lvs_name)[0] + free_clusters_start = int(lvs['free_clusters']) + lbd_name0 = self.lbd_name + str("0") + lbd_name1 = self.lbd_name + str("1") + bdev_size = self.get_lvs_size() + # construct two thin provisioned lvol bdevs on created lvol store + # with size equals to free lvs size + bdev_name0 = self.c.construct_lvol_bdev(uuid_store, lbd_name0, + bdev_size, thin=True) + bdev_name1 = self.c.construct_lvol_bdev(uuid_store, lbd_name1, + bdev_size, thin=True) + + lvs = self.c.get_lvol_stores(self.lvs_name)[0] + free_clusters_create_lvol = int(lvs['free_clusters']) + if free_clusters_start != free_clusters_create_lvol: + fail_count += 1 + lvol_bdev0 = self.c.get_lvol_bdev_with_name(bdev_name0) + lvol_bdev1 = self.c.get_lvol_bdev_with_name(bdev_name1) + + nbd_name0 = "/dev/nbd0" + nbd_name1 = "/dev/nbd1" + fail_count += self.c.start_nbd_disk(lvol_bdev0['name'], nbd_name0) + fail_count += self.c.start_nbd_disk(lvol_bdev1['name'], nbd_name1) + + size = "75%" + # fill first bdev to 75% of its space with specific pattern + fail_count += self.run_fio_test(nbd_name0, 0, size, "write", "0xcc") + + size = "75%" + # fill second bdev up to 75% of its space + # check that error message occured while filling second bdev with data + fail_count += self.run_fio_test(nbd_name1, 0, size, "write", "0xee", + expected_ret_value=1) + + size = "75%" + # check if data on first disk stayed unchanged + fail_count += self.run_fio_test(nbd_name0, 0, size, "read", "0xcc") + + size = "25%" + offset = "75%" + fail_count += self.run_fio_test(nbd_name0, offset, size, "read", "0x00") + + fail_count += self.c.stop_nbd_disk(nbd_name0) + fail_count += self.c.stop_nbd_disk(nbd_name1) + # destroy thin provisioned lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev0['name']) + fail_count += self.c.destroy_lvol_bdev(lvol_bdev1['name']) + # destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + # destroy malloc bdev + fail_count += self.c.delete_malloc_bdev(base_name) + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case655(self): + """ + thin_provisioning_filling_disks_less_than_lvs_size + + Check if writing to two thin provisioned lvol bdevs + less than total size of lvol store will end with success + """ + # create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # construct lvol store on malloc bdev + uuid_store = self.c.construct_lvol_store(base_name, self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs = self.c.get_lvol_stores(self.lvs_name)[0] + free_clusters_start = int(lvs['free_clusters']) + lbd_name0 = self.lbd_name + str("0") + lbd_name1 = self.lbd_name + str("1") + lvs_size = self.get_lvs_size() + bdev_size = int(lvs_size * 0.7) + # construct two thin provisioned lvol bdevs on created lvol store + # with size equal to 70% of lvs size + bdev_name0 = self.c.construct_lvol_bdev(uuid_store, lbd_name0, + bdev_size, thin=True) + bdev_name1 = self.c.construct_lvol_bdev(uuid_store, lbd_name1, + bdev_size, thin=True) + + lvol_bdev0 = self.c.get_lvol_bdev_with_name(bdev_name0) + lvol_bdev1 = self.c.get_lvol_bdev_with_name(bdev_name1) + # check if bdevs are available and size of every disk is equal to 70% of lvs size + nbd_name0 = "/dev/nbd0" + nbd_name1 = "/dev/nbd1" + fail_count += self.c.start_nbd_disk(lvol_bdev0['name'], nbd_name0) + fail_count += self.c.start_nbd_disk(lvol_bdev1['name'], nbd_name1) + size = int(int(lvol_bdev0['num_blocks']) * int(lvol_bdev0['block_size']) * 0.7) + # fill first disk with 70% of its size + # check if operation didn't fail + fail_count += self.run_fio_test(nbd_name0, 0, size, "write", "0xcc") + size = int(int(lvol_bdev1['num_blocks']) * int(lvol_bdev1['block_size']) * 0.7) + # fill second disk also with 70% of its size + # check if operation didn't fail + fail_count += self.run_fio_test(nbd_name1, 0, size, "write", "0xee") + + fail_count += self.c.stop_nbd_disk(nbd_name0) + fail_count += self.c.stop_nbd_disk(nbd_name1) + # destroy thin provisioned lvol bdevs + fail_count += self.c.destroy_lvol_bdev(lvol_bdev0['name']) + fail_count += self.c.destroy_lvol_bdev(lvol_bdev1['name']) + # destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + # destroy malloc bdev + fail_count += self.c.delete_malloc_bdev(base_name) + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case700(self): + """ + tasting_positive + + Positive test for tasting a multi lvol bdev configuration. + Create a lvol store with some lvol bdevs on aio bdev and restart vhost app. + After restarting configuration should be automatically loaded and should be exactly + the same as before restarting. + Check that running configuration can be modified after restarting and tasting. + """ + fail_count = 0 + uuid_bdevs = [] + base_name = "aio_bdev0" + + base_path = path.dirname(sys.argv[0]) + vhost_path = path.join(self.app_path, 'vhost') + pid_path = path.join(base_path, 'vhost.pid') + aio_bdev0 = path.join(base_path, 'aio_bdev_0') + + self.c.construct_aio_bdev(aio_bdev0, base_name, 4096) + # Create initial configuration on running vhost instance + # create lvol store, create 5 bdevs + # save info of all lvs and lvol bdevs + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, + uuid_store, + self.cluster_size) + + size = self.get_lvs_divided_size(10) + + for i in range(5): + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name + str(i), + size) + uuid_bdevs.append(uuid_bdev) + # Using get_bdevs command verify lvol bdevs were correctly created + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size) + + old_bdevs = sorted(self.c.get_lvol_bdevs(), key=lambda x: x["name"]) + old_stores = self.c.get_lvol_stores() + + # Shut down vhost instance and restart with new instance + fail_count += self._stop_vhost(pid_path) + remove(pid_path) + if self._start_vhost(vhost_path, pid_path) != 0: + fail_count += 1 + return fail_count + + self.c.construct_aio_bdev(aio_bdev0, base_name, 4096) + # Check if configuration was properly loaded after tasting + # get all info all lvs and lvol bdevs, compare with previous info + new_bdevs = sorted(self.c.get_lvol_bdevs(), key=lambda x: x["name"]) + new_stores = self.c.get_lvol_stores() + + if old_stores != new_stores: + fail_count += 1 + print("ERROR: old and loaded lvol store is not the same") + print("DIFF:") + print(old_stores) + print(new_stores) + + if len(old_bdevs) != len(new_bdevs): + fail_count += 1 + print("ERROR: old and loaded lvol bdev list count is not equal") + + for o, n in zip(old_bdevs, new_bdevs): + if o != n: + fail_count += 1 + print("ERROR: old and loaded lvol bdev is not the same") + print("DIFF:") + pprint.pprint([o, n]) + + if fail_count != 0: + self.c.delete_aio_bdev(aio_bdev0) + return fail_count + + # Try modifying loaded configuration + # Add some lvol bdevs to existing lvol store then + # remove all lvol configuration and re-create it again + for i in range(5, 10): + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name + str(i), + size) + uuid_bdevs.append(uuid_bdev) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size) + + for uuid_bdev in uuid_bdevs: + self.c.destroy_lvol_bdev(uuid_bdev) + + if self.c.destroy_lvol_store(uuid_store) != 0: + fail_count += 1 + + uuid_bdevs = [] + + # Create lvol store on aio bdev, create ten lvol bdevs on lvol store and + # verify all configuration call results + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, + uuid_store, + self.cluster_size) + + for i in range(10): + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name + str(i), + size) + uuid_bdevs.append(uuid_bdev) + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size) + + # Destroy lvol store + if self.c.destroy_lvol_store(uuid_store) != 0: + fail_count += 1 + + self.c.delete_aio_bdev(base_name) + + return fail_count + + @case_message + def test_case701(self): + """ + tasting_lvol_store_positive + + Positive test for tasting lvol store. + """ + base_path = path.dirname(sys.argv[0]) + aio_bdev0 = path.join(base_path, 'aio_bdev_0') + base_name = "aio_bdev0" + + self.c.construct_aio_bdev(aio_bdev0, base_name, 4096) + # construct lvol store on aio bdev + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + + self.c.delete_aio_bdev(base_name) + self.c.construct_aio_bdev(aio_bdev0, base_name, 4096) + # wait 1 second to allow time for lvolstore tasting + sleep(1) + # check if lvol store still exists in vhost configuration + if self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) != 0: + fail_count += 1 + # destroy lvol store from aio bdev + if self.c.destroy_lvol_store(uuid_store) != 0: + fail_count += 1 + + self.c.delete_aio_bdev(base_name) + return fail_count + + @case_message + def test_case702(self): + """ + tasting_positive_with_different_lvol_store_cluster_size + + Positive test for tasting a multi lvol bdev configuration. + Create two lvol stores with different cluster sizes with some lvol bdevs on aio + drive and restart vhost app. + After restarting configuration should be automatically loaded and should be exactly + the same as before restarting. + """ + fail_count = 0 + uuid_bdevs = [] + cluster_size_1M = MEGABYTE + cluster_size_32M = 32 * MEGABYTE + base_name_1M = "aio_bdev0" + base_name_32M = "aio_bdev1" + + base_path = path.dirname(sys.argv[0]) + vhost_path = path.join(self.app_path, 'vhost') + pid_path = path.join(base_path, 'vhost.pid') + aio_bdev0 = path.join(base_path, 'aio_bdev_0') + aio_bdev1 = path.join(base_path, 'aio_bdev_1') + + self.c.construct_aio_bdev(aio_bdev0, base_name_1M, 4096) + self.c.construct_aio_bdev(aio_bdev1, base_name_32M, 4096) + + # Create initial configuration on running vhost instance + # create lvol store, create 5 bdevs + # save info of all lvs and lvol bdevs + uuid_store_1M = self.c.construct_lvol_store(base_name_1M, + self.lvs_name + "_1M", + cluster_size_1M) + + fail_count += self.c.check_get_lvol_stores(base_name_1M, + uuid_store_1M, + cluster_size_1M) + + uuid_store_32M = self.c.construct_lvol_store(base_name_32M, + self.lvs_name + "_32M", + cluster_size_32M) + + fail_count += self.c.check_get_lvol_stores(base_name_32M, + uuid_store_32M, + cluster_size_32M) + + # size = approx 20% of total aio bdev size + size_1M = self.get_lvs_divided_size(5, self.lvs_name + "_1M") + size_32M = self.get_lvs_divided_size(5, self.lvs_name + "_32M") + + for i in range(5): + uuid_bdev = self.c.construct_lvol_bdev(uuid_store_1M, + self.lbd_name + str(i) + "_1M", + size_1M) + uuid_bdevs.append(uuid_bdev) + # Using get_bdevs command verify lvol bdevs were correctly created + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size_1M) + + for i in range(5): + uuid_bdev = self.c.construct_lvol_bdev(uuid_store_32M, + self.lbd_name + str(i) + "_32M", + size_32M) + uuid_bdevs.append(uuid_bdev) + # Using get_bdevs command verify lvol bdevs were correctly created + fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size_32M) + + old_bdevs = sorted(self.c.get_lvol_bdevs(), key=lambda x: x["name"]) + old_stores = sorted(self.c.get_lvol_stores(), key=lambda x: x["name"]) + + # Shut down vhost instance and restart with new instance + fail_count += self._stop_vhost(pid_path) + remove(pid_path) + if self._start_vhost(vhost_path, pid_path) != 0: + fail_count += 1 + return fail_count + + self.c.construct_aio_bdev(aio_bdev0, base_name_1M, 4096) + self.c.construct_aio_bdev(aio_bdev1, base_name_32M, 4096) + + # Check if configuration was properly loaded after tasting + # get all info all lvs and lvol bdevs, compare with previous info + new_bdevs = sorted(self.c.get_lvol_bdevs(), key=lambda x: x["name"]) + new_stores = sorted(self.c.get_lvol_stores(), key=lambda x: x["name"]) + + if old_stores != new_stores: + fail_count += 1 + print("ERROR: old and loaded lvol store is not the same") + print("DIFF:") + print(old_stores) + print(new_stores) + + if len(old_bdevs) != len(new_bdevs): + fail_count += 1 + print("ERROR: old and loaded lvol bdev list count is not equal") + + for o, n in zip(old_bdevs, new_bdevs): + if o != n: + fail_count += 1 + print("ERROR: old and loaded lvol bdev is not the same") + print("DIFF:") + pprint.pprint([o, n]) + + if fail_count != 0: + self.c.delete_aio_bdev(base_name_1M) + self.c.delete_aio_bdev(base_name_32M) + return fail_count + + for uuid_bdev in uuid_bdevs: + self.c.destroy_lvol_bdev(uuid_bdev) + + if self.c.destroy_lvol_store(uuid_store_1M) != 0: + fail_count += 1 + + if self.c.destroy_lvol_store(uuid_store_32M) != 0: + fail_count += 1 + + self.c.delete_aio_bdev(base_name_1M) + self.c.delete_aio_bdev(base_name_32M) + + return fail_count + + @case_message + def test_case750(self): + """ + snapshot readonly + + Create snaphot of lvol bdev and check if it is readonly. + """ + fail_count = 0 + nbd_name0 = "/dev/nbd0" + snapshot_name = "snapshot0" + # Construct malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Construct lvol store on malloc bdev + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + + lvs = self.c.get_lvol_stores()[0] + free_clusters_start = int(lvs['free_clusters']) + bdev_size = self.get_lvs_divided_size(3) + # Create lvol bdev with 33% of lvol store space + bdev_name = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, + bdev_size) + lvol_bdev = self.c.get_lvol_bdev_with_name(bdev_name) + # Create snapshot of lvol bdev + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name) + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + + fail_count += self.c.start_nbd_disk(snapshot_bdev['name'], nbd_name0) + size = bdev_size * MEGABYTE + # Try to perform write operation on created snapshot + # Check if filling snapshot of lvol bdev fails + fail_count += self.run_fio_test(nbd_name0, 0, size, "write", "0xcc", 1) + + fail_count += self.c.stop_nbd_disk(nbd_name0) + # Destroy lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name']) + # Destroy snapshot + fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name']) + # Destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + # Delete malloc bdev + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case751(self): + """ + snapshot_compare_with_lvol_bdev + + Check if lvol bdevs and snapshots contain the same data. + Check if lvol bdev and snapshot differ when writing to lvol bdev + after creating snapshot. + """ + fail_count = 0 + nbd_name = ["/dev/nbd0", "/dev/nbd1", "/dev/nbd2", "/dev/nbd3"] + snapshot_name0 = "snapshot0" + snapshot_name1 = "snapshot1" + # Construct mallov bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Construct lvol store + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + size = self.get_lvs_divided_size(6) + lbd_name0 = self.lbd_name + str(0) + lbd_name1 = self.lbd_name + str(1) + # Create thin provisioned lvol bdev with size less than 25% of lvs + uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store, + lbd_name0, size, thin=True) + # Create thick provisioned lvol bdev with size less than 25% of lvs + uuid_bdev1 = self.c.construct_lvol_bdev(uuid_store, + lbd_name1, size, thin=False) + lvol_bdev0 = self.c.get_lvol_bdev_with_name(uuid_bdev0) + fail_count += self.c.start_nbd_disk(lvol_bdev0['name'], nbd_name[0]) + fill_size = int(size * MEGABYTE / 2) + # Fill thin provisoned lvol bdev with 50% of its space + fail_count += self.run_fio_test(nbd_name[0], 0, fill_size, "write", "0xcc", 0) + lvol_bdev1 = self.c.get_lvol_bdev_with_name(uuid_bdev1) + fail_count += self.c.start_nbd_disk(lvol_bdev1['name'], nbd_name[1]) + fill_size = int(size * MEGABYTE) + # Fill whole thic provisioned lvol bdev + fail_count += self.run_fio_test(nbd_name[1], 0, fill_size, "write", "0xcc", 0) + + # Create snapshots of lvol bdevs + fail_count += self.c.snapshot_lvol_bdev(uuid_bdev0, snapshot_name0) + fail_count += self.c.snapshot_lvol_bdev(uuid_bdev1, snapshot_name1) + fail_count += self.c.start_nbd_disk(self.lvs_name + "/" + snapshot_name0, nbd_name[2]) + fail_count += self.c.start_nbd_disk(self.lvs_name + "/" + snapshot_name1, nbd_name[3]) + # Compare every lvol bdev with corresponding snapshot + # and check that data are the same + fail_count += self.compare_two_disks(nbd_name[0], nbd_name[2], 0) + fail_count += self.compare_two_disks(nbd_name[1], nbd_name[3], 0) + + fill_size = int(size * MEGABYTE / 2) + offset = fill_size + # Fill second half of thin provisioned lvol bdev + fail_count += self.run_fio_test(nbd_name[0], offset, fill_size, "write", "0xaa", 0) + # Compare thin provisioned lvol bdev with its snapshot and check if it fails + fail_count += self.compare_two_disks(nbd_name[0], nbd_name[2], 1) + for nbd in nbd_name: + fail_count += self.c.stop_nbd_disk(nbd) + # Delete lvol bdevs + fail_count += self.c.destroy_lvol_bdev(lvol_bdev0['name']) + fail_count += self.c.destroy_lvol_bdev(lvol_bdev1['name']) + # Delete snapshots + fail_count += self.c.destroy_lvol_bdev(self.lvs_name + "/" + snapshot_name0) + fail_count += self.c.destroy_lvol_bdev(self.lvs_name + "/" + snapshot_name1) + # Destroy snapshot + fail_count += self.c.destroy_lvol_store(uuid_store) + # Delete malloc bdev + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - calls successful, return code = 0 + # - removing snapshot should always end with success + # - no other operation fails + return fail_count + + @case_message + def test_case752(self): + """ + snapshot_during_io_traffic + + Check that when writing to lvol bdev + creating snapshot ends with success + """ + global current_fio_pid + fail_count = 0 + nbd_name = "/dev/nbd0" + snapshot_name = "snapshot" + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Construct lvol store + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + # Create thin provisioned lvol bdev with size equal to 50% of lvs space + size = self.get_lvs_divided_size(2) + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, + size, thin=True) + + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev) + fail_count += self.c.start_nbd_disk(lvol_bdev['name'], nbd_name) + fill_size = int(size * MEGABYTE) + # Create thread that will run fio in background + thread = FioThread(nbd_name, 0, fill_size, "write", "0xcc", 0, + extra_params="--time_based --runtime=8") + # Perform write operation with verification to created lvol bdev + thread.start() + time.sleep(4) + fail_count += is_process_alive(current_fio_pid) + # During write operation create snapshot of created lvol bdev + # and check that snapshot has been created successfully + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name) + fail_count += is_process_alive(current_fio_pid) + thread.join() + # Check that write operation ended with success + fail_count += thread.rv + fail_count += self.c.stop_nbd_disk(nbd_name) + # Destroy lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name']) + # Delete snapshot + fail_count += self.c.destroy_lvol_bdev(self.lvs_name + "/" + snapshot_name) + # Destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + # Delete malloc bdevs + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case753(self): + """ + snapshot_of_snapshot + + Check that creating snapshot of snapshot will fail + """ + fail_count = 0 + snapshot_name0 = "snapshot0" + snapshot_name1 = "snapshot1" + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Construct lvol store + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + # Create thick provisioned lvol bdev + size = self.get_lvs_divided_size(2) + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, + size, thin=False) + + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev) + # Create snapshot of created lvol bdev + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name0) + # Create snapshot of previously created snapshot + # and check if operation will fail + if self.c.snapshot_lvol_bdev(snapshot_name0, snapshot_name1) == 0: + print("ERROR: Creating snapshot of snapshot should fail") + fail_count += 1 + # Delete lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name']) + # Destroy snapshot + fail_count += self.c.destroy_lvol_bdev(self.lvs_name + "/" + snapshot_name0) + # Destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + # Delete malloc bdev + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - calls successful, return code = 0 + # - creating snapshot of snapshot should fail + # - no other operation fails + return fail_count + + @case_message + def test_case754(self): + """ + clone_bdev_only + + Check that only clone of snapshot can be created. + Creating clone of lvol bdev should fail. + """ + fail_count = 0 + clone_name = "clone" + snapshot_name = "snapshot" + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Construct lvol store + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs = self.c.get_lvol_stores() + # Create thick provisioned lvol bdev with size equal to 50% of lvs space + size = self.get_lvs_divided_size(2) + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, + size, thin=False) + + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev) + # Create clone of lvol bdev and check if it fails + rv = self.c.clone_lvol_bdev(lvol_bdev['name'], clone_name) + if rv == 0: + print("ERROR: Creating clone of lvol bdev ended with unexpected success") + fail_count += 1 + # Create snapshot of lvol bdev + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name) + # Create again clone of lvol bdev and check if it fails + rv = self.c.clone_lvol_bdev(lvol_bdev['name'], clone_name) + if rv == 0: + print("ERROR: Creating clone of lvol bdev ended with unexpected success") + fail_count += 1 + # Create clone of snapshot and check if it ends with success + rv = self.c.clone_lvol_bdev(self.lvs_name + "/" + snapshot_name, clone_name) + if rv != 0: + print("ERROR: Creating clone of snapshot ended with unexpected failure") + fail_count += 1 + clone_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name) + + # Delete lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name']) + # Destroy clone + fail_count += self.c.destroy_lvol_bdev(clone_bdev['name']) + # Delete snapshot + fail_count += self.c.destroy_lvol_bdev(self.lvs_name + "/" + snapshot_name) + # Delete lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + # Destroy malloc bdev + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - calls successful, return code = 0 + # - cloning thick provisioned lvol bdev should fail + # - no other operation fails + return fail_count + + @case_message + def test_case755(self): + """ + clone_writing_to_clone + + + """ + fail_count = 0 + nbd_name = ["/dev/nbd0", "/dev/nbd1", "/dev/nbd2", "/dev/nbd3"] + snapshot_name = "snapshot" + clone_name0 = "clone0" + clone_name1 = "clone1" + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Create lvol store + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + size = self.get_lvs_divided_size(6) + lbd_name0 = self.lbd_name + str(0) + # Construct thick provisioned lvol bdev + uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store, + lbd_name0, size, thin=False) + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0) + # Install lvol bdev on /dev/nbd0 + fail_count += self.c.start_nbd_disk(lvol_bdev['name'], nbd_name[0]) + fill_size = size * MEGABYTE + # Fill lvol bdev with 100% of its space + fail_count += self.run_fio_test(nbd_name[0], 0, fill_size, "write", "0xcc", 0) + + # Create snapshot of thick provisioned lvol bdev + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name) + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + # Create two clones of created snapshot + fail_count += self.c.clone_lvol_bdev(snapshot_bdev['name'], clone_name0) + fail_count += self.c.clone_lvol_bdev(snapshot_bdev['name'], clone_name1) + + lvol_clone0 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name0) + fail_count += self.c.start_nbd_disk(lvol_clone0['name'], nbd_name[1]) + fill_size = int(size * MEGABYTE / 2) + # Perform write operation to first clone + # Change first half of its space + fail_count += self.run_fio_test(nbd_name[1], 0, fill_size, "write", "0xaa", 0) + fail_count += self.c.start_nbd_disk(self.lvs_name + "/" + snapshot_name, nbd_name[2]) + lvol_clone1 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name1) + fail_count += self.c.start_nbd_disk(lvol_clone1['name'], nbd_name[3]) + # Compare snapshot with second clone. Data on both bdevs should be the same + time.sleep(1) + fail_count += self.compare_two_disks(nbd_name[2], nbd_name[3], 0) + + for nbd in nbd_name: + fail_count += self.c.stop_nbd_disk(nbd) + # Destroy lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name']) + # Destroy two clones + fail_count += self.c.destroy_lvol_bdev(lvol_clone0['name']) + fail_count += self.c.destroy_lvol_bdev(lvol_clone1['name']) + # Delete snapshot + fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name']) + # Destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + # Delete malloc + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case756(self): + """ + clone_and_snapshot_relations + + Check if relations between clones and snapshots + are properly set in configuration + """ + fail_count = 0 + snapshot_name = 'snapshot' + clone_name0 = 'clone1' + clone_name1 = 'clone2' + lbd_name = clone_name1 + + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + # Create lvol store + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + size = self.get_lvs_divided_size(6) + + # Construct thick provisioned lvol bdev + uuid_bdev = self.c.construct_lvol_bdev(uuid_store, + lbd_name, size, thin=False) + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev) + + # Create snapshot of thick provisioned lvol bdev + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name) + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + + # Create clone of created snapshot + fail_count += self.c.clone_lvol_bdev(snapshot_bdev['name'], clone_name0) + + # Get current bdevs configuration + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + lvol_clone0 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name0) + lvol_clone1 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name1) + + # Check snapshot consistency + snapshot_lvol = snapshot_bdev['driver_specific']['lvol'] + if snapshot_lvol['snapshot'] is not True: + fail_count += 1 + if snapshot_lvol['clone'] is not False: + fail_count += 1 + if sorted([clone_name0, clone_name1]) != sorted(snapshot_lvol['clones']): + fail_count += 1 + + # Check first clone consistency + lvol_clone0_lvol = lvol_clone0['driver_specific']['lvol'] + if lvol_clone0_lvol['snapshot'] is not False: + fail_count += 1 + if lvol_clone0_lvol['clone'] is not True: + fail_count += 1 + if lvol_clone0_lvol['base_snapshot'] != 'snapshot': + fail_count += 1 + + # Check second clone consistency + lvol_clone1_lvol = lvol_clone1['driver_specific']['lvol'] + if lvol_clone1_lvol['snapshot'] is not False: + fail_count += 1 + if lvol_clone1_lvol['clone'] is not True: + fail_count += 1 + if lvol_clone1_lvol['base_snapshot'] != 'snapshot': + fail_count += 1 + + # Destroy first clone and check if it is deleted from snapshot + fail_count += self.c.destroy_lvol_bdev(lvol_clone0['name']) + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + if [clone_name1] != snapshot_bdev['driver_specific']['lvol']['clones']: + fail_count += 1 + + # Destroy second clone + fail_count += self.c.destroy_lvol_bdev(lvol_clone1['name']) + + # Delete snapshot + fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name']) + + # Destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + + # Delete malloc + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case757(self): + """ + clone_inflate + + + Test inflate rpc method + """ + fail_count = 0 + snapshot_name = "snapshot" + nbd_name = "/dev/nbd0" + + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + + # Create lvol store + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + size = self.get_lvs_divided_size(4) + + # Construct thick provisioned lvol bdev + uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name, size, thin=False) + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0) + + # Fill bdev with data of knonw pattern + fail_count += self.c.start_nbd_disk(lvol_bdev['name'], nbd_name) + fill_size = size * MEGABYTE + fail_count += self.run_fio_test(nbd_name, 0, fill_size, "write", "0xcc", 0) + self.c.stop_nbd_disk(nbd_name) + + # Create snapshot of thick provisioned lvol bdev + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name) + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + + # Create two clones of created snapshot + lvol_clone = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + self.lbd_name) + if lvol_clone['driver_specific']['lvol']['thin_provision'] is not True: + fail_count += 1 + + # Fill part of clone with data of known pattern + fail_count += self.c.start_nbd_disk(lvol_clone['name'], nbd_name) + first_fill = 0 + second_fill = int(size * 3 / 4) + fail_count += self.run_fio_test(nbd_name, first_fill * MEGABYTE, + MEGABYTE, "write", "0xdd", 0) + fail_count += self.run_fio_test(nbd_name, second_fill * MEGABYTE, + MEGABYTE, "write", "0xdd", 0) + self.c.stop_nbd_disk(nbd_name) + + # Do inflate + fail_count += self.c.inflate_lvol_bdev(lvol_clone['name']) + lvol_clone = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + self.lbd_name) + if lvol_clone['driver_specific']['lvol']['thin_provision'] is not False: + fail_count += 1 + + # Delete snapshot + fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name']) + + # Check data consistency + fail_count += self.c.start_nbd_disk(lvol_clone['name'], nbd_name) + fail_count += self.run_fio_test(nbd_name, first_fill * MEGABYTE, + MEGABYTE, "read", "0xdd") + fail_count += self.run_fio_test(nbd_name, (first_fill + 1) * MEGABYTE, + (second_fill - first_fill - 1) * MEGABYTE, + "read", "0xcc") + fail_count += self.run_fio_test(nbd_name, (second_fill) * MEGABYTE, + MEGABYTE, "read", "0xdd") + fail_count += self.run_fio_test(nbd_name, (second_fill + 1) * MEGABYTE, + (size - second_fill - 1) * MEGABYTE, + "read", "0xcc") + self.c.stop_nbd_disk(nbd_name) + + # Destroy lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name']) + + # Destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + + # Delete malloc + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case758(self): + """ + clone_decouple_parent + + Detach parent from clone and check if parent can be safely removed. + Check data consistency. + """ + + fail_count = 0 + snapshot_name = "snapshot" + nbd_name = "/dev/nbd0" + + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + + # Create lvol store + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + size = self.get_lvs_divided_size(4) + + # Construct thin provisioned lvol bdev + uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name, size, thin=True) + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0) + + # Decouple parent lvol bdev and check if it fails + ret_value = self.c.decouple_parent_lvol_bdev(lvol_bdev['name']) + if ret_value == 0: + print("ERROR: Decouple parent on bdev without parent should " + "fail but didn't") + fail_count += 1 + + # Create snapshot of thin provisioned lvol bdev + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name) + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + + # Try to destroy snapshot and check if it fails + ret_value = self.c.destroy_lvol_bdev(snapshot_bdev['name']) + if ret_value == 0: + print("ERROR: Delete snapshot should fail but didn't") + fail_count += 1 + + # Decouple parent lvol bdev + fail_count += self.c.decouple_parent_lvol_bdev(lvol_bdev['name']) + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0) + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + if lvol_bdev['driver_specific']['lvol']['thin_provision'] is not True: + fail_count += 1 + if lvol_bdev['driver_specific']['lvol']['clone'] is not False: + fail_count += 1 + if lvol_bdev['driver_specific']['lvol']['snapshot'] is not False: + fail_count += 1 + if snapshot_bdev['driver_specific']['lvol']['clone'] is not False: + fail_count += 1 + + # Destroy snapshot + fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name']) + + # Destroy lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name']) + + # Destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + + # Delete malloc + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case759(self): + """ + clone_decouple_parent_rw + + Create tree level snaphot-snapshot2-clone structure. + Detach snapshot2 from clone. Check if snapshot2 can be safely removed. + Each time check consistency of snapshot-clone relations and written data. + """ + fail_count = 0 + snapshot_name = "snapshot" + snapshot_name2 = "snapshot2" + nbd_name = "/dev/nbd0" + + # Create malloc bdev + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + + # Create lvol store + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + lvs = self.c.get_lvol_stores() + size = int(5 * lvs[0]['cluster_size'] / MEGABYTE) + + # Construct thin provisioned lvol bdev + uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store, + self.lbd_name, size, thin=True) + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0) + + # Fill first four out of 5 culsters of clone with data of known pattern + fail_count += self.c.start_nbd_disk(lvol_bdev['name'], nbd_name) + begin_fill = 0 + end_fill = int(size * 4 / 5) + fail_count += self.run_fio_test(nbd_name, begin_fill * MEGABYTE, + end_fill * MEGABYTE, "write", "0xdd", 0) + + # Create snapshot of thin provisioned lvol bdev + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name) + snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name) + + # Fill second and fourth cluster of clone with data of known pattern + start_fill = int(size / 5) + fill_range = int(size / 5) + fail_count += self.run_fio_test(nbd_name, start_fill * MEGABYTE, + fill_range * MEGABYTE, "write", "0xcc", 0) + start_fill = int(size * 3 / 5) + fail_count += self.run_fio_test(nbd_name, start_fill * MEGABYTE, + fill_range * MEGABYTE, "write", "0xcc", 0) + + # Create second snapshot of thin provisioned lvol bdev + fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name2) + snapshot_bdev2 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name2) + + # Fill second cluster of clone with data of known pattern + start_fill = int(size / 5) + fail_count += self.run_fio_test(nbd_name, start_fill * MEGABYTE, + fill_range * MEGABYTE, "write", "0xee", 0) + + # Check data consistency + pattern = ["0xdd", "0xee", "0xdd", "0xcc", "0x00"] + for i in range(0, 5): + begin_fill = int(size * i / 5) + fail_count += self.run_fio_test(nbd_name, begin_fill * MEGABYTE, + fill_range * MEGABYTE, "read", pattern[i]) + + # Delete snapshot and check if it fails + ret_value = self.c.destroy_lvol_bdev(snapshot_bdev2['name']) + if ret_value == 0: + print("ERROR: Delete snapshot should fail but didn't") + fail_count += 1 + + # Decouple parent + fail_count += self.c.decouple_parent_lvol_bdev(lvol_bdev['name']) + lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0) + + # Check data consistency + for i in range(0, 5): + begin_fill = int(size * i / 5) + fail_count += self.run_fio_test(nbd_name, begin_fill * MEGABYTE, + fill_range * MEGABYTE, "read", pattern[i]) + + # Delete second snapshot + ret_value = self.c.destroy_lvol_bdev(snapshot_bdev2['name']) + + # Check data consistency + for i in range(0, 5): + begin_fill = int(size * i / 5) + fail_count += self.run_fio_test(nbd_name, begin_fill * MEGABYTE, + fill_range * MEGABYTE, "read", pattern[i]) + + # Destroy lvol bdev + fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name']) + + # Destroy snapshot + fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name']) + + # Destroy lvol store + fail_count += self.c.destroy_lvol_store(uuid_store) + + # Delete malloc + fail_count += self.c.delete_malloc_bdev(base_name) + + # Expected result: + # - calls successful, return code = 0 + # - no other operation fails + return fail_count + + @case_message + def test_case800(self): + fail_count = 0 + + bdev_uuids = [] + bdev_names = [self.lbd_name + str(i) for i in range(4)] + bdev_aliases = ["/".join([self.lvs_name, name]) for name in bdev_names] + + # Create a lvol store with 4 lvol bdevs + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + lvs_uuid = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_name, + lvs_uuid, + self.cluster_size, + self.lvs_name) + bdev_size = self.get_lvs_divided_size(4) + for name, alias in zip(bdev_names, bdev_aliases): + uuid = self.c.construct_lvol_bdev(lvs_uuid, + name, + bdev_size) + fail_count += self.c.check_get_bdevs_methods(uuid, + bdev_size, + alias) + bdev_uuids.append(uuid) + + # Rename lvol store and check if lvol store name and + # lvol bdev aliases were updated properly + new_lvs_name = "lvs_new" + bdev_aliases = [alias.replace(self.lvs_name, new_lvs_name) for alias in bdev_aliases] + + fail_count += self.c.rename_lvol_store(self.lvs_name, new_lvs_name) + + fail_count += self.c.check_get_lvol_stores(base_name, + lvs_uuid, + self.cluster_size, + new_lvs_name) + + for uuid, alias in zip(bdev_uuids, bdev_aliases): + fail_count += self.c.check_get_bdevs_methods(uuid, + bdev_size, + alias) + + # Now try to rename the bdevs using their uuid as "old_name" + bdev_names = ["lbd_new" + str(i) for i in range(4)] + bdev_aliases = ["/".join([new_lvs_name, name]) for name in bdev_names] + print(bdev_aliases) + for uuid, new_name, new_alias in zip(bdev_uuids, bdev_names, bdev_aliases): + fail_count += self.c.rename_lvol_bdev(uuid, new_name) + fail_count += self.c.check_get_bdevs_methods(uuid, + bdev_size, + new_alias) + # Same thing but only use aliases + bdev_names = ["lbd_even_newer" + str(i) for i in range(4)] + new_bdev_aliases = ["/".join([new_lvs_name, name]) for name in bdev_names] + print(bdev_aliases) + for uuid, old_alias, new_alias, new_name in zip(bdev_uuids, bdev_aliases, new_bdev_aliases, bdev_names): + fail_count += self.c.rename_lvol_bdev(old_alias, new_name) + fail_count += self.c.check_get_bdevs_methods(uuid, + bdev_size, + new_alias) + + # Delete configuration using names after rename operation + for bdev in new_bdev_aliases: + fail_count += self.c.destroy_lvol_bdev(bdev) + fail_count += self.c.destroy_lvol_store(new_lvs_name) + fail_count += self.c.delete_malloc_bdev(base_name) + + return fail_count + + @case_message + def test_case801(self): + fail_count = 0 + if self.c.rename_lvol_store("NOTEXIST", "WHATEVER") == 0: + fail_count += 1 + return fail_count + + @case_message + def test_case802(self): + fail_count = 0 + + lvs_name_1 = "lvs_1" + lvs_name_2 = "lvs_2" + + # Create lists with lvol bdev names and aliases for later use + bdev_names_1 = ["lvol_1_" + str(i) for i in range(4)] + bdev_aliases_1 = ["/".join([lvs_name_1, name]) for name in bdev_names_1] + bdev_uuids_1 = [] + bdev_names_2 = ["lvol_2_" + str(i) for i in range(4)] + bdev_aliases_2 = ["/".join([lvs_name_2, name]) for name in bdev_names_2] + bdev_uuids_2 = [] + + base_bdev_1 = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + base_bdev_2 = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + + # Create lvol store on each malloc bdev + lvs_uuid_1 = self.c.construct_lvol_store(base_bdev_1, + lvs_name_1) + fail_count += self.c.check_get_lvol_stores(base_bdev_1, + lvs_uuid_1, + self.cluster_size, + lvs_name_1) + lvs_uuid_2 = self.c.construct_lvol_store(base_bdev_2, + lvs_name_2) + fail_count += self.c.check_get_lvol_stores(base_bdev_2, + lvs_uuid_2, + self.cluster_size, + lvs_name_2) + + # Create 4 lvol bdevs on top of each lvol store + bdev_size_1 = self.get_lvs_divided_size(4, lvs_name_1) + bdev_size_2 = self.get_lvs_divided_size(4, lvs_name_2) + for name, alias in zip(bdev_names_1, bdev_aliases_1): + uuid = self.c.construct_lvol_bdev(lvs_uuid_1, + name, + bdev_size_1) + fail_count += self.c.check_get_bdevs_methods(uuid, + bdev_size_1, + alias) + bdev_uuids_1.append(uuid) + for name, alias in zip(bdev_names_2, bdev_aliases_2): + uuid = self.c.construct_lvol_bdev(lvs_uuid_2, + name, + bdev_size_2) + fail_count += self.c.check_get_bdevs_methods(uuid, + bdev_size_2, + alias) + bdev_uuids_2.append(uuid) + + # Try to rename lvol store to already existing name + if self.c.rename_lvol_store(lvs_name_1, lvs_name_2) == 0: + fail_count += 1 + + # Verify that names of lvol stores and lvol bdevs did not change + fail_count += self.c.check_get_lvol_stores(base_bdev_1, + lvs_uuid_1, + self.cluster_size, + lvs_name_1) + fail_count += self.c.check_get_lvol_stores(base_bdev_2, + lvs_uuid_2, + self.cluster_size, + lvs_name_2) + + for name, alias, uuid in zip(bdev_names_1, bdev_aliases_1, bdev_uuids_1): + fail_count += self.c.check_get_bdevs_methods(uuid, + bdev_size_1, + alias) + + for name, alias, uuid in zip(bdev_names_2, bdev_aliases_2, bdev_uuids_2): + fail_count += self.c.check_get_bdevs_methods(uuid, + bdev_size_2, + alias) + + # Clean configuration + for lvol_uuid in bdev_uuids_1 + bdev_uuids_2: + fail_count += self.c.destroy_lvol_bdev(lvol_uuid) + fail_count += self.c.destroy_lvol_store(lvs_uuid_1) + fail_count += self.c.destroy_lvol_store(lvs_uuid_2) + fail_count += self.c.delete_malloc_bdev(base_bdev_1) + fail_count += self.c.delete_malloc_bdev(base_bdev_2) + + return fail_count + + @case_message + def test_case803(self): + fail_count = 0 + if self.c.rename_lvol_bdev("NOTEXIST", "WHATEVER") == 0: + fail_count += 1 + return fail_count + + @case_message + def test_case804(self): + fail_count = 0 + + base_bdev = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + lvs_uuid = self.c.construct_lvol_store(base_bdev, + self.lvs_name) + fail_count += self.c.check_get_lvol_stores(base_bdev, + lvs_uuid, + self.cluster_size, + self.lvs_name) + bdev_size = self.get_lvs_divided_size(2) + bdev_uuid_1 = self.c.construct_lvol_bdev(lvs_uuid, + self.lbd_name + "1", + bdev_size) + fail_count += self.c.check_get_bdevs_methods(bdev_uuid_1, + bdev_size) + bdev_uuid_2 = self.c.construct_lvol_bdev(lvs_uuid, + self.lbd_name + "2", + bdev_size) + fail_count += self.c.check_get_bdevs_methods(bdev_uuid_2, + bdev_size) + + if self.c.rename_lvol_bdev(self.lbd_name + "1", self.lbd_name + "2") == 0: + fail_count += 1 + fail_count += self.c.check_get_bdevs_methods(bdev_uuid_1, + bdev_size, + "/".join([self.lvs_name, self.lbd_name + "1"])) + + fail_count += self.c.destroy_lvol_bdev(bdev_uuid_1) + fail_count += self.c.destroy_lvol_bdev(bdev_uuid_2) + fail_count += self.c.destroy_lvol_store(lvs_uuid) + fail_count += self.c.delete_malloc_bdev(base_bdev) + + return fail_count + + @case_message + def test_case10000(self): + pid_path = path.join(self.path, 'vhost.pid') + + base_name = self.c.construct_malloc_bdev(self.total_size, + self.block_size) + uuid_store = self.c.construct_lvol_store(base_name, + self.lvs_name) + fail_count = self.c.check_get_lvol_stores(base_name, uuid_store, + self.cluster_size) + + fail_count += self._stop_vhost(pid_path) + return fail_count diff --git a/src/spdk/test/lvol/test_plan.md b/src/spdk/test/lvol/test_plan.md new file mode 100644 index 00000000..5e38699a --- /dev/null +++ b/src/spdk/test/lvol/test_plan.md @@ -0,0 +1,585 @@ +# Lvol feature test plan + +## Objective +The purpose of these tests is to verify the possibility of using lvol configuration in SPDK. + +## Methodology +Configuration in test is to be done using example stub application. +All management is done using RPC calls, including logical volumes management. +All tests are performed using malloc backends. +One exception to malloc backends are tests for logical volume +tasting - these require persistent merory like NVMe backend. + +Tests will be executed as scenarios - sets of smaller test step +in which return codes from RPC calls is validated. +Some configuration calls may also be validated by use of +"get_*" RPC calls, which provide additional information for verifying +results. + +Tests with thin provisioned lvol bdevs, snapshots and clones are using nbd devices. +Before writing/reading to lvol bdev, bdev is installed with rpc start_nbd_disk. +After finishing writing/reading, rpc stop_nbd_disk is used. + +## Tests + +### construct_lvol_store - positive tests + +#### TEST CASE 1 - Name: construct_lvs_positive +Positive test for constructing a new lvol store. +Call construct_lvol_store with correct base bdev name. +Steps: +- create a malloc bdev +- construct_lvol_store on correct, exisitng malloc bdev +- check correct uuid values in response get_lvol_stores command +- destroy lvol store +- delete malloc bdev + +Expected result: +- call successful, return code = 0, uuid printed to stdout +- get_lvol_stores: backend used for construct_lvol_store has uuid + field set with the same uuid as returned from RPC call +- no other operation fails + +### construct_lvol_bdev - positive tests + +#### TEST CASE 50 - Name: construct_logical_volume_positive +Positive test for constructing a new logical volume. +Call construct_lvol_bdev with correct lvol store UUID and size in MiB for this bdev. +Steps: +- create a malloc bdev +- construct_lvol_store on correct, exisitng malloc bdev +- check correct uuid values in response get_lvol_stores command +- construct_lvol_bdev on correct lvs_uuid and size +- delete lvol bdev +- destroy lvol store +- delete malloc bdev + +Expected result: +- call successful, return code = 0 +- get_bdevs: backend used for construct_lvol_bdev has name + field set with the same name as returned value from call RPC method: construct_lvol_bdev +- no other operation fails + +#### TEST CASE 51 - Name: construct_multi_logical_volumes_positive +Positive test for constructing a multi logical volumes. +Call construct_lvol_bdev with correct lvol store UUID and +size is equal one quarter of the this bdev size. +Steps: +- create a malloc bdev +- construct_lvol_store on correct, exisitng malloc bdev +- check correct uuid values in response get_lvol_stores command +- construct_lvol_bdev on correct lvs_uuid and size + (size is approximately equal to one quarter of the bdev size, + because of lvol metadata) +- repeat the previous step three more times +- delete lvol bdevs +- create and delete four lvol bdevs again from steps above +- destroy lvol store +- delete malloc bdev + +Expected result: +- call successful, return code = 0 +- get_lvol_store: backend used for construct_lvol_bdev has name + field set with the same name as returned from RPC call for all repeat +- no other operation fails + +#### TEST CASE 52 - Name: construct_lvol_bdev_using_name_positive +Positive test for constructing a logical volume using friendly names. +Verify that logical volumes can be created by using a friendly name +instead of uuid when referencing to lvol store. +Steps: +- create malloc bdev +- create logical volume store on created malloc bdev +- verify lvol store was created correctly +- create logical volume on lvol store by using a friendly name + as a reference +- verify logical volume was correctly created +- delete logical volume bdev +- destroy logical volume store +- delete malloc bdev + +Expected result: +- calls successful, return code = 0 +- no other operation fails + +#### TEST CASE 53 - Name: construct_lvol_bdev_duplicate_names_positive +Positive test for constructing a logical volumes using friendly names. +Verify that logical volumes can use the same argument for friendly names +if they are created on separate logical volume stores. +Steps: +- create two malloc bdevs +- create logical volume stores on created malloc bdevs +- verify stores were created correctly +- create logical volume on first lvol store +- verify it was correctly created +- using the same friendly name argument create logical volume on second + lvol store +- verify logical volume was correctly created +- delete logical volume bdevs +- destroy logical volume stores +- delete malloc bdevs + +Expected result: +- calls successful, return code = 0 +- no other operation fails + +### construct_lvol_bdev - negative tests + +#### TEST CASE 100 - Name: construct_logical_volume_nonexistent_lvs_uuid +Negative test for constructing a new logical_volume. +Call construct_lvol_bdev with lvs_uuid which does not +exist in configuration. +Steps: +- try to call construct_lvol_bdev with lvs_uuid which does not exist + +Expected result: +- return code != 0 +- ENODEV response printed to stdout + +#### TEST CASE 101 - Name: construct_lvol_bdev_on_full_lvol_store +Negative test for constructing a new lvol bdev. +Call construct_lvol_bdev on a full lvol store. +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response from get_lvol_stores command +- construct_lvol_bdev on correct lvs_uuid and size is smaller by 1 MB + from the full size malloc bdev +- try construct_lvol_bdev on the same lvs_uuid as in last step; + this call should fail as lvol store space is taken by previously created bdev +- destroy_lvol_store +- delete malloc bdev + +Expected result: +- first call successful +- second construct_lvol_bdev call return code != 0 +- EEXIST response printed to stdout +- no other operation fails + +#### TEST CASE 102 - Name: construct_lvol_bdev_name_twice +Negative test for constructing lvol bdev using the same +friendly name twice on the same logical volume store. +Steps: +- create malloc bdev +- create logical volume store on malloc bdev +- using get_lvol_stores verify that logical volume store was correctly created + and has arguments as provided in step earlier (cluster size, friendly name, base bdev) +- construct logical volume on lvol store and verify it was correctly created +- try to create another logical volume on the same lvol store using +the same friendly name as in previous step; this step should fail +- delete existing lvol bdev +- delete existing lvol store +- delete malloc bdevs + +Expected results: +- creating two logical volumes with the same friendly name within the same + lvol store should not be possible +- no other operation fails + +### resize_lvol_store - positive tests + +#### TEST CASE 150 - Name: resize_logical_volume_positive +Positive test for resizing a logical_volume. +Call resize_lvol_bdev with correct logical_volumes name and new size. +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response get_lvol_stores command +- construct_lvol_bdev on correct lvs_uuid and size is + equal to one quarter of size malloc bdev +- check size of the lvol bdev by command RPC : get_bdevs +- resize_lvol_bdev on correct lvs_uuid and size is + equal half to size malloc bdev +- check size of the lvol bdev by command RPC : get_bdevs +- resize_lvol_bdev on the correct lvs_uuid and size is smaller by 1 MB + from the full size malloc bdev +- check size of the lvol bdev by command RPC : get_bdevs +- resize_lvol_bdev on the correct lvs_uuid and size is equal 0 MiB +- check size of the lvol bdev by command RPC : get_bdevs +- delete lvol bdev +- destroy lvol store +- delete malloc bdev + +Expected result: +- lvol bdev should change size after resize operations +- calls successful, return code = 0 +- no other operation fails + +### resize lvol store - negative tests + +#### TEST CASE 200 - Name: resize_logical_volume_nonexistent_logical_volume +Negative test for resizing a logical_volume. +Call resize_lvol_bdev with logical volume which does not +exist in configuration. +Steps: +- try resize_lvol_store on logical volume which does not exist + +Expected result: +- return code != 0 +- Error code: ENODEV ("No such device") response printed to stdout + +#### TEST CASE 201 - Name: resize_logical_volume_with_size_out_of_range +Negative test for resizing a logical volume. +Call resize_lvol_store with size argument bigger than size of base bdev. +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response get_lvol_stores command +- construct_lvol_bdev on correct lvs_uuid and + size is equal one quarter of size malloc bdev +- try resize_lvol_bdev on correct lvs_uuid and size is + equal to size malloc bdev + 1MiB; this call should fail +- delete lvol bdev +- destroy lvol store +- delete malloc bdev + +Expected result: +- resize_lvol_bdev call return code != 0 +- Error code: ENODEV ("Not enough free clusters left on lvol store") + response printed to stdout +- no other operation fails + +### destroy_lvol_store - positive tests + +#### TEST CASE 250 - Name: destroy_lvol_store_positive +Positive test for destroying a logical volume store. +Call destroy_lvol_store with correct logical_volumes name +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response get_lvol_stores command +- destroy_lvol_store +- check correct response get_lvol_stores command +- delete malloc bdev + +Expected result: +- calls successful, return code = 0 +- get_lvol_stores: response should be of no value after destroyed lvol store +- no other operation fails + +#### TEST CASE 251 - Name: destroy_lvol_store_use_name_positive +Positive test for destroying a logical volume store using +lvol store name instead of uuid for reference. +Call destroy_lvol_store with correct logical volume name +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response from get_lvol_stores command +- destroy_lvol_store +- check correct response from get_lvol_stores command +- delete malloc bdev + +Expected result: +- calls successful, return code = 0 +- get_lvol_stores: response should be of no value after destroyed lvol store +- no other operation fails + +#### TEST CASE 252 - Name: destroy_lvol_store_with_lvol_bdev_positive +Positive test for destroying a logical volume store with lvol bdev +created on top. +Call destroy_lvol_store with correct logical_volumes name +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response get_lvol_stores command +- construct_lvol_bdev on correct lvs_uuid and size is equal to size malloc bdev +- destroy_lvol_store +- check correct response get_lvol_stores command +- delete malloc bdev + +Expected result: +- calls successful, return code = 0 +- get_lvol_stores: response should be of no value after destroyed lvol store +- no other operation fails + +#### TEST CASE 253 - Name: destroy_multi_logical_volumes_positive +Positive test for destroying a logical volume store with multiple lvol +bdevs created on top. +Call construct_lvol_bdev with correct lvol store UUID and +size is equal to one quarter of the this bdev size. +Steps: +- create a malloc bdev +- construct_lvol_store on correct, exisitng malloc bdev +- check correct uuid values in response get_lvol_stores command +- construct_lvol_bdev on correct lvs_uuid and size + (size is equal to one quarter of the bdev size) +- repeat the previous step four times +- destroy_lvol_store +- check correct response get_lvol_stores command +- delete malloc bdev + +Expected result: +- call successful, return code = 0 +- get_lvol_store: backend used for construct_lvol_bdev has name + field set with the same name as returned from RPC call for all repeat +- no other operation fails + +#### TEST CASE 254 - Name: destroy_resize_logical_volume_positive +Positive test for destroying a logical_volume after resizing. +Call destroy_lvol_store with correct logical_volumes name. +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response get_lvol_stores command +- construct_lvol_bdev on correct lvs_uuid and size is + equal to one quarter of size malloc bdev +- check size of the lvol bdev +- resize_lvol_bdev on correct lvs_uuid and size is + equal half of size malloc bdev +- check size of the lvol bdev by command RPC : get_bdevs +- Resize_lvol_bdev on the correct lvs_uuid and the size is smaller by 1 MB + from the full size malloc bdev +- check size of the lvol bdev by command RPC : get_bdevs +- resize_lvol_bdev on the correct lvs_uuid and size is equal 0 MiB +- check size of the lvol bdev by command RPC : get_bdevs +- destroy_lvol_store +- delete malloc bdev + +Expected result: +- lvol bdev should change size after resize operations +- calls successful, return code = 0 +- no other operation fails +- get_lvol_stores: response should be of no value after destroyed lvol store + +#### TEST CASE 255 - Name: delete_lvol_store_persistent_positive +Positive test for removing lvol store persistently +Steps: +- construct_lvol_store on NVMe bdev +- destroy lvol store +- delete NVMe bdev +- add NVMe bdev +- check if destroyed lvol store does not exist on NVMe bdev + +Expected result: +- get_lvol_stores should not report any existsing lvol stores in configuration + after deleting and adding NVMe bdev +- no other operation fails + +### destroy_lvol_store - negative tests + +#### TEST CASE 300 - Name: destroy_lvol_store_nonexistent_lvs_uuid +Call destroy_lvol_store with nonexistent logical_volumes name +exist in configuration. +Steps: +- try to call destroy_lvol_store with lvs_uuid which does not exist + +Expected result: +- return code != 0 +- Error code response printed to stdout + +#### TEST CASE 301 - Name: delete_lvol_store_underlying_bdev +Call destroy_lvol_store after deleting it's base bdev. +Lvol store should be automatically removed on deleting underlying bdev. +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response get_lvol_stores command +- delete malloc bdev +- try to destroy lvol store; this call should fail as lvol store + is no longer present + +Expected result: +- destroy_lvol_store retudn code != 0 +- Error code: ENODEV ("No such device") response printed to stdout +- no other operation fails + +### nested destroy_lvol_bdev - negative tests + +#### TEST CASE 350 - Name: nested_destroy_logical_volume_negative +Negative test for destroying a nested first lvol store. +Call destroy_lvol_store with correct base bdev name. +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response get_lvol_stores command +- construct_lvol_bdev on correct lvs_uuid and size is + equal to size malloc bdev +- construct first nested lvol store on created lvol_bdev +- check correct uuid values in response get_lvol_stores command +- construct first nested lvol bdev on correct lvs_uuid and size +- check size of the lvol bdev by command RPC : get_bdevs +- destroy first lvol_store +- delete malloc bdev + +Expected result: +- Error code: ENODEV ("the device is busy") response printed to stdout +- no other operation fails + +### nested construct_logical_volume - positive tests + +#### TEST CASE 400 - Name: nested_construct_logical_volume_positive +Positive test for constructing a nested new lvol store. +Call construct_lvol_store with correct base bdev name. +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response get_lvol_stores command +- construct_lvol_bdev on correct lvs_uuid and size is + equal to size malloc bdev +- construct first nested lvol store on created lvol_bdev +- check correct uuid values in response get_lvol_stores command +- construct first nested lvol bdev on correct lvs_uuid and size +- construct second nested lvol store on created first nested lvol bdev +- check correct uuid values in response get_lvol_stores command +- construct second nested lvol bdev on correct first nested lvs uuid and size +- delete nested lvol bdev and lvol store +- delete base lvol bdev and lvol store +- delete malloc bdev + +Expected result: +- calls successful, return code = 0 +- get_lvol_stores: backend used for construct_lvol_store has UUID + field set with the same UUID as returned from RPC call + backend used for construct_lvol_bdev has UUID + field set with the same UUID as returned from RPC call +- no other operation fails + +### construct_lvol_store - negative tests + +#### TEST CASE 450 - Name: construct_lvs_nonexistent_bdev +Negative test for constructing a new lvol store. +Call construct_lvol_store with base bdev name which does not +exist in configuration. +Steps: +- try construct_lvol_store on bdev which does not exist + +Expected result: +- return code != 0 +- Error code: ENODEV ("No such device") response printed to stdout + +#### TEST CASE 451 - Name: construct_lvs_on_bdev_twice +Negative test for constructing a new lvol store. +Call construct_lvol_store with base bdev name twice. +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response get_lvol_stores command +- try construct_lvol_store on the same bdev as in last step; + this call should fail as base bdev is already claimed by lvol store +- destroy lvs +- delete malloc bdev + +Expected result: +- first call successful +- second construct_lvol_store call return code != 0 +- EEXIST response printed to stdout +- no other operation fails + +#### TEST CASE 452 - Name: construct_lvs_name_twice +Negative test for constructing a new lvol store using the same +friendly name twice. +Steps: +- create two malloc bdevs +- create logical volume store on first malloc bdev +- using get_lvol_stores verify that logical volume store was correctly created + and has arguments as provided in step earlier (cluster size, friendly name, base bdev) +- try to create another logical volume store on second malloc bdev using the + same friendly name as before; this step is expected to fail as lvol stores + cannot have the same name +- delete existing lvol store +- delete malloc bdevs + +Expected results: +- creating two logical volume stores with the same friendly name should +not be possible +- no other operation fails + +### logical volume rename tests + +#### TEST CASE 800 - Name: rename_positive +Positive test for lvol store and lvol bdev rename. +Steps: +- create malloc bdev +- construct lvol store on malloc bdev +- create 4 lvol bdevs on top of previously created lvol store +- rename lvol store; verify that lvol store friendly name was + updated in get_lvol_stores output; verify that prefix in lvol bdevs + friendly names were also updated +- rename lvol bdevs; use lvols UUID's to point which lvol bdev name to change; + verify that all bdev names were successfully updated +- rename lvol bdevs; use lvols alias name to point which lvol bdev + name to change; verify that all bdev names were successfully updated +- clean running configuration: delete lvol bdevs, destroy lvol store, + delete malloc bdev; use lvol store and lvol bdev friendly names for delete + and destroy commands to check if new names can be correctly used for performing + other RPC operations; + +Expected results: +- lvol store and lvol bdevs correctly created +- lvol store and lvol bdevs names updated after renaming operation +- lvol store and lvol bdevs possible to delete using new names +- no other operation fails + +#### TEST CASE 801 - Name: rename_lvs_nonexistent +Negative test case for lvol store rename. +Check that error is returned when trying to rename not existing lvol store. + +Steps: +- call rename_lvol_store with name pointing to not existing lvol store + +Expected results: +- rename_lvol_store return code != 0 +- no other operation fails + +#### TEST CASE 802 - Name: rename_lvs_EEXIST +Negative test case for lvol store rename. +Check that error is returned when trying to rename to a name which is already +used by another lvol store. + +Steps: +- create 2 malloc bdevs +- construct lvol store on each malloc bdev +- on each lvol store create 4 lvol bdevs +- call rename_lvol_store on first lvol store and try to change its name to + the same name as used by second lvol store +- verify that both lvol stores still have the same names as before +- verify that lvol bdev have the same aliases as before + +Expected results: +- rename_lvol_store return code != 0; not possible to rename to already + used name +- no other operation fails + +#### TEST CASE 803 - Name: rename_lvol_bdev_nonexistent +Negative test case for lvol bdev rename. +Check that error is returned when trying to rename not existing lvol bdev. + +Steps: +- call rename_lvol_bdev with name pointing to not existing lvol bdev + +Expected results: +- rename_lvol_bdev return code != 0 +- no other operation fails + +#### TEST CASE 804 - Name: rename_lvol_bdev_EEXIST +Negative test case for lvol bdev rename. +Check that error is returned when trying to rename to a name which is already +used by another lvol bdev. + +Steps: +- create malloc bdev +- construct lvol store on malloc bdev +- construct 2 lvol bdevs on lvol store +- call rename_lvol_bdev on first lvol bdev and try to change its name to + the same name as used by second lvol bdev +- verify that both lvol bdev still have the same names as before + +Expected results: +- rename_lvol_bdev return code != 0; not possible to rename to already + used name +- no other operation fails + +### SIGTERM + +#### TEST CASE 10000 - Name: SIGTERM +Call CTRL+C (SIGTERM) occurs after creating lvol store +Steps: +- create a malloc bdev +- construct_lvol_store on created malloc bdev +- check correct uuid values in response get_lvol_stores command +- Send SIGTERM signal to the application + +Expected result: +- calls successful, return code = 0 +- get_bdevs: no change +- no other operation fails diff --git a/src/spdk/test/nvme/Makefile b/src/spdk/test/nvme/Makefile new file mode 100644 index 00000000..9fae60f6 --- /dev/null +++ b/src/spdk/test/nvme/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = aer reset sgl e2edp overhead deallocated_value err_injection + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/nvme/aer/.gitignore b/src/spdk/test/nvme/aer/.gitignore new file mode 100644 index 00000000..31379617 --- /dev/null +++ b/src/spdk/test/nvme/aer/.gitignore @@ -0,0 +1 @@ +aer diff --git a/src/spdk/test/nvme/aer/Makefile b/src/spdk/test/nvme/aer/Makefile new file mode 100644 index 00000000..77acabd0 --- /dev/null +++ b/src/spdk/test/nvme/aer/Makefile @@ -0,0 +1,39 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = aer + +include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk diff --git a/src/spdk/test/nvme/aer/aer.c b/src/spdk/test/nvme/aer/aer.c new file mode 100644 index 00000000..e102813f --- /dev/null +++ b/src/spdk/test/nvme/aer/aer.c @@ -0,0 +1,580 @@ +/*- + * 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/log.h" +#include "spdk/nvme.h" +#include "spdk/env.h" + +#define MAX_DEVS 64 + +struct dev { + struct spdk_nvme_ctrlr *ctrlr; + struct spdk_nvme_health_information_page *health_page; + struct spdk_nvme_ns_list *changed_ns_list; + uint32_t orig_temp_threshold; + char name[SPDK_NVMF_TRADDR_MAX_LEN + 1]; +}; + +static void get_feature_test(struct dev *dev); + +static struct dev devs[MAX_DEVS]; +static int num_devs = 0; + +#define foreach_dev(iter) \ + for (iter = devs; iter - devs < num_devs; iter++) + +static int outstanding_commands = 0; +static int aer_done = 0; +static int temperature_done = 0; +static int failed = 0; +static struct spdk_nvme_transport_id g_trid; + +/* Enable AER temperature test */ +static int enable_temp_test = 0; +/* Enable AER namespace attribute notice test, this variable holds + * the NSID that is expected to be in the Changed NS List. + */ +static uint32_t expected_ns_test = 0; + +static void +set_temp_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + struct dev *dev = cb_arg; + + outstanding_commands--; + + if (spdk_nvme_cpl_is_error(cpl)) { + printf("%s: set feature (temp threshold) failed\n", dev->name); + failed = 1; + return; + } + + /* Admin command completions are synchronized by the NVMe driver, + * so we don't need to do any special locking here. */ + temperature_done++; +} + +static int +set_temp_threshold(struct dev *dev, uint32_t temp) +{ + struct spdk_nvme_cmd cmd = {}; + int rc; + + cmd.opc = SPDK_NVME_OPC_SET_FEATURES; + cmd.cdw10 = SPDK_NVME_FEAT_TEMPERATURE_THRESHOLD; + cmd.cdw11 = temp; + + rc = spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0, set_temp_completion, dev); + if (rc == 0) { + outstanding_commands++; + } + + return rc; +} + +static void +get_temp_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + struct dev *dev = cb_arg; + + outstanding_commands--; + + if (spdk_nvme_cpl_is_error(cpl)) { + printf("%s: get feature (temp threshold) failed\n", dev->name); + failed = 1; + return; + } + + dev->orig_temp_threshold = cpl->cdw0; + printf("%s: original temperature threshold: %u Kelvin (%d Celsius)\n", + dev->name, dev->orig_temp_threshold, dev->orig_temp_threshold - 273); + + temperature_done++; +} + +static int +get_temp_threshold(struct dev *dev) +{ + struct spdk_nvme_cmd cmd = {}; + int rc; + + cmd.opc = SPDK_NVME_OPC_GET_FEATURES; + cmd.cdw10 = SPDK_NVME_FEAT_TEMPERATURE_THRESHOLD; + + rc = spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0, get_temp_completion, dev); + if (rc == 0) { + outstanding_commands++; + } + + return rc; +} + +static void +print_health_page(struct dev *dev, struct spdk_nvme_health_information_page *hip) +{ + printf("%s: Current Temperature: %u Kelvin (%d Celsius)\n", + dev->name, hip->temperature, hip->temperature - 273); +} + +static void +get_health_log_page_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + struct dev *dev = cb_arg; + + outstanding_commands --; + + if (spdk_nvme_cpl_is_error(cpl)) { + printf("%s: get log page failed\n", dev->name); + failed = 1; + return; + } + + print_health_page(dev, dev->health_page); + aer_done++; +} + +static void +get_changed_ns_log_page_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + struct dev *dev = cb_arg; + bool found = false; + uint32_t i; + + outstanding_commands --; + + if (spdk_nvme_cpl_is_error(cpl)) { + printf("%s: get log page failed\n", dev->name); + failed = 1; + return; + } + + /* Let's compare the expected namespce ID is + * in changed namespace list + */ + if (dev->changed_ns_list->ns_list[0] != 0xffffffffu) { + for (i = 0; i < sizeof(*dev->changed_ns_list) / sizeof(uint32_t); i++) { + if (expected_ns_test == dev->changed_ns_list->ns_list[i]) { + printf("%s: changed NS list contains expected NSID: %u\n", + dev->name, expected_ns_test); + found = true; + break; + } + } + } + + if (!found) { + printf("%s: Error: Can't find expected NSID %u\n", dev->name, expected_ns_test); + failed = 1; + } + + aer_done++; +} + +static int +get_health_log_page(struct dev *dev) +{ + int rc; + + rc = spdk_nvme_ctrlr_cmd_get_log_page(dev->ctrlr, SPDK_NVME_LOG_HEALTH_INFORMATION, + SPDK_NVME_GLOBAL_NS_TAG, dev->health_page, sizeof(*dev->health_page), 0, + get_health_log_page_completion, dev); + + if (rc == 0) { + outstanding_commands++; + } + + return rc; +} + +static int +get_changed_ns_log_page(struct dev *dev) +{ + int rc; + + rc = spdk_nvme_ctrlr_cmd_get_log_page(dev->ctrlr, SPDK_NVME_LOG_CHANGED_NS_LIST, + SPDK_NVME_GLOBAL_NS_TAG, dev->changed_ns_list, + sizeof(*dev->changed_ns_list), 0, + get_changed_ns_log_page_completion, dev); + + if (rc == 0) { + outstanding_commands++; + } + + return rc; +} + +static void +cleanup(void) +{ + struct dev *dev; + + foreach_dev(dev) { + if (dev->health_page) { + spdk_dma_free(dev->health_page); + } + if (dev->changed_ns_list) { + spdk_dma_free(dev->changed_ns_list); + } + } +} + +static void +aer_cb(void *arg, const struct spdk_nvme_cpl *cpl) +{ + uint32_t log_page_id = (cpl->cdw0 & 0xFF0000) >> 16; + struct dev *dev = arg; + + if (spdk_nvme_cpl_is_error(cpl)) { + printf("%s: AER failed\n", dev->name); + failed = 1; + return; + } + + printf("%s: aer_cb for log page %d\n", dev->name, log_page_id); + + if (log_page_id == SPDK_NVME_LOG_HEALTH_INFORMATION) { + /* Set the temperature threshold back to the original value + * so the AER doesn't trigger again. + */ + set_temp_threshold(dev, dev->orig_temp_threshold); + get_health_log_page(dev); + } else if (log_page_id == SPDK_NVME_LOG_CHANGED_NS_LIST) { + get_changed_ns_log_page(dev); + } +} + +static void +usage(const char *program_name) +{ + printf("%s [options]", program_name); + printf("\n"); + printf("options:\n"); + printf(" -T enable temperature tests\n"); + printf(" -n expected Namespace attribute notice ID\n"); + printf(" -r trid remote NVMe over Fabrics target address\n"); + printf(" Format: 'key:value [key:value] ...'\n"); + printf(" Keys:\n"); + printf(" trtype Transport type (e.g. RDMA)\n"); + printf(" adrfam Address family (e.g. IPv4, IPv6)\n"); + printf(" traddr Transport address (e.g. 192.168.100.8)\n"); + printf(" trsvcid Transport service identifier (e.g. 4420)\n"); + printf(" subnqn Subsystem NQN (default: %s)\n", SPDK_NVMF_DISCOVERY_NQN); + printf(" Example: -r 'trtype:RDMA adrfam:IPv4 traddr:192.168.100.8 trsvcid:4420'\n"); + + spdk_tracelog_usage(stdout, "-L"); + + printf(" -v verbose (enable warnings)\n"); + printf(" -H show this usage\n"); +} + +static int +parse_args(int argc, char **argv) +{ + int op, rc; + + g_trid.trtype = SPDK_NVME_TRANSPORT_PCIE; + snprintf(g_trid.subnqn, sizeof(g_trid.subnqn), "%s", SPDK_NVMF_DISCOVERY_NQN); + + while ((op = getopt(argc, argv, "n:r:HL:T")) != -1) { + switch (op) { + case 'n': + expected_ns_test = atoi(optarg); + break; + case 'r': + if (spdk_nvme_transport_id_parse(&g_trid, optarg) != 0) { + fprintf(stderr, "Error parsing transport address\n"); + return 1; + } + break; + case 'L': + rc = spdk_log_set_trace_flag(optarg); + if (rc < 0) { + fprintf(stderr, "unknown flag\n"); + usage(argv[0]); + exit(EXIT_FAILURE); + } + spdk_log_set_print_level(SPDK_LOG_DEBUG); +#ifndef DEBUG + fprintf(stderr, "%s must be rebuilt with CONFIG_DEBUG=y for -L flag.\n", + argv[0]); + usage(argv[0]); + return 0; +#endif + break; + case 'T': + enable_temp_test = 1; + break; + case 'H': + default: + usage(argv[0]); + return 1; + } + } + + return 0; +} + +static bool +probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr_opts *opts) +{ + printf("Attaching to %s\n", trid->traddr); + + return true; +} + +static void +attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ + struct dev *dev; + + /* add to dev list */ + dev = &devs[num_devs++]; + + dev->ctrlr = ctrlr; + + snprintf(dev->name, sizeof(dev->name), "%s", + trid->traddr); + + printf("Attached to %s\n", dev->name); + + dev->health_page = spdk_dma_zmalloc(sizeof(*dev->health_page), 4096, NULL); + if (dev->health_page == NULL) { + printf("Allocation error (health page)\n"); + failed = 1; + } + dev->changed_ns_list = spdk_dma_zmalloc(sizeof(*dev->changed_ns_list), 4096, NULL); + if (dev->changed_ns_list == NULL) { + printf("Allocation error (changed namespace list page)\n"); + failed = 1; + } +} + +static void +get_feature_test_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + struct dev *dev = cb_arg; + + outstanding_commands--; + + if (spdk_nvme_cpl_is_error(cpl)) { + printf("%s: get number of queues failed\n", dev->name); + failed = 1; + return; + } + + if (aer_done < num_devs) { + /* + * Resubmit Get Features command to continue filling admin queue + * while the test is running. + */ + get_feature_test(dev); + } +} + +static void +get_feature_test(struct dev *dev) +{ + struct spdk_nvme_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opc = SPDK_NVME_OPC_GET_FEATURES; + cmd.cdw10 = SPDK_NVME_FEAT_NUMBER_OF_QUEUES; + if (spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0, + get_feature_test_cb, dev) != 0) { + printf("Failed to send Get Features command for dev=%p\n", dev); + failed = 1; + return; + } + + outstanding_commands++; +} + +static int +spdk_aer_temperature_test(void) +{ + struct dev *dev; + + printf("Getting temperature thresholds of all controllers...\n"); + foreach_dev(dev) { + /* Get the original temperature threshold */ + get_temp_threshold(dev); + } + + while (!failed && temperature_done < num_devs) { + foreach_dev(dev) { + spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr); + } + } + + if (failed) { + return failed; + } + temperature_done = 0; + aer_done = 0; + + /* Send admin commands to test admin queue wraparound while waiting for the AER */ + foreach_dev(dev) { + get_feature_test(dev); + } + + if (failed) { + return failed; + } + + printf("Waiting for all controllers to trigger AER...\n"); + foreach_dev(dev) { + /* Set the temperature threshold to a low value */ + set_temp_threshold(dev, 200); + } + + if (failed) { + return failed; + } + + while (!failed && (aer_done < num_devs || temperature_done < num_devs)) { + foreach_dev(dev) { + spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr); + } + } + + if (failed) { + return failed; + } + + return 0; +} + +static int +spdk_aer_changed_ns_test(void) +{ + struct dev *dev; + + aer_done = 0; + + printf("Starting namespce attribute notice tests for all controllers...\n"); + + foreach_dev(dev) { + get_feature_test(dev); + } + + if (failed) { + return failed; + } + + while (!failed && (aer_done < num_devs)) { + foreach_dev(dev) { + spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr); + } + } + + if (failed) { + return failed; + } + + return 0; +} + +int main(int argc, char **argv) +{ + struct dev *dev; + int i; + struct spdk_env_opts opts; + int rc; + + rc = parse_args(argc, argv); + if (rc != 0) { + return rc; + } + + spdk_env_opts_init(&opts); + opts.name = "aer"; + opts.core_mask = "0x1"; + opts.mem_size = 64; + if (spdk_env_init(&opts) < 0) { + fprintf(stderr, "Unable to initialize SPDK env\n"); + return 1; + } + + printf("Asynchronous Event Request test\n"); + + if (spdk_nvme_probe(&g_trid, NULL, probe_cb, attach_cb, NULL) != 0) { + fprintf(stderr, "spdk_nvme_probe() failed\n"); + return 1; + } + + if (failed) { + goto done; + } + + printf("Registering asynchronous event callbacks...\n"); + foreach_dev(dev) { + spdk_nvme_ctrlr_register_aer_callback(dev->ctrlr, aer_cb, dev); + } + + /* AER temperature test */ + if (enable_temp_test) { + if (spdk_aer_temperature_test()) { + goto done; + } + } + + /* AER changed namespace list test */ + if (expected_ns_test) { + if (spdk_aer_changed_ns_test()) { + goto done; + } + } + + printf("Cleaning up...\n"); + + while (outstanding_commands) { + foreach_dev(dev) { + spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr); + } + } + + for (i = 0; i < num_devs; i++) { + struct dev *dev = &devs[i]; + + spdk_nvme_detach(dev->ctrlr); + } + +done: + cleanup(); + + return failed; +} diff --git a/src/spdk/test/nvme/deallocated_value/.gitignore b/src/spdk/test/nvme/deallocated_value/.gitignore new file mode 100644 index 00000000..8460e82e --- /dev/null +++ b/src/spdk/test/nvme/deallocated_value/.gitignore @@ -0,0 +1 @@ +deallocated_value diff --git a/src/spdk/test/nvme/deallocated_value/Makefile b/src/spdk/test/nvme/deallocated_value/Makefile new file mode 100644 index 00000000..bff11cad --- /dev/null +++ b/src/spdk/test/nvme/deallocated_value/Makefile @@ -0,0 +1,39 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = deallocated_value + +include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk diff --git a/src/spdk/test/nvme/deallocated_value/deallocated_value.c b/src/spdk/test/nvme/deallocated_value/deallocated_value.c new file mode 100644 index 00000000..e1d1c9f6 --- /dev/null +++ b/src/spdk/test/nvme/deallocated_value/deallocated_value.c @@ -0,0 +1,445 @@ +/*- + * 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/nvme.h" +#include "spdk/env.h" + +#define NUM_BLOCKS 100 + +/* + * The purpose of this sample app is to determine the read value of deallocated logical blocks + * from a given NVMe Controller. The NVMe 1.3 spec requires the controller to list this value, + * but controllers adhering to the NVMe 1.2 spec may not report this value. According to the spec, + * "The values read from a deallocated logical block and its metadata (excluding protection information) shall + * be all bytes set to 00h, all bytes set to FFh, or the last data written to the associated logical block". + */ + +struct ns_entry { + struct spdk_nvme_ctrlr *ctrlr; + struct spdk_nvme_ns *ns; + struct ns_entry *next; + struct spdk_nvme_qpair *qpair; +}; + +struct deallocate_context { + struct ns_entry *ns_entry; + char **write_buf; + char **read_buf; + char *zero_buf; + char *FFh_buf; + int writes_completed; + int reads_completed; + int deallocate_completed; + int flush_complete; + int matches_zeroes; + int matches_previous_data; + int matches_FFh; +}; + +static struct ns_entry *g_namespaces = NULL; + +static void cleanup(struct deallocate_context *context); + +static void +fill_random(char *buf, size_t num_bytes) +{ + size_t i; + + srand((unsigned) time(NULL)); + for (i = 0; i < num_bytes; i++) { + buf[i] = rand() % 0x100; + } +} + +static void +register_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns *ns) +{ + struct ns_entry *entry; + const struct spdk_nvme_ctrlr_data *cdata; + + cdata = spdk_nvme_ctrlr_get_data(ctrlr); + + if (!spdk_nvme_ns_is_active(ns)) { + printf("Controller %-20.20s (%-20.20s): Skipping inactive NS %u\n", + cdata->mn, cdata->sn, + spdk_nvme_ns_get_id(ns)); + return; + } + + entry = malloc(sizeof(struct ns_entry)); + if (entry == NULL) { + perror("ns_entry malloc"); + exit(1); + } + + entry->ctrlr = ctrlr; + entry->ns = ns; + entry->next = g_namespaces; + g_namespaces = entry; + + printf(" Namespace ID: %d size: %juGB\n", spdk_nvme_ns_get_id(ns), + spdk_nvme_ns_get_size(ns) / 1000000000); +} + +static uint32_t +get_max_block_size(void) +{ + struct ns_entry *ns; + uint32_t max_block_size, temp_block_size; + + ns = g_namespaces; + max_block_size = 0; + + while (ns != NULL) { + temp_block_size = spdk_nvme_ns_get_sector_size(ns->ns); + max_block_size = temp_block_size > max_block_size ? temp_block_size : max_block_size; + ns = ns->next; + } + + return max_block_size; +} + +static void +write_complete(void *arg, const struct spdk_nvme_cpl *completion) +{ + struct deallocate_context *context = arg; + + context->writes_completed++; +} + +static void +read_complete(void *arg, const struct spdk_nvme_cpl *completion) +{ + struct deallocate_context *context = arg; + struct ns_entry *ns_entry = context->ns_entry; + int rc; + + rc = memcmp(context->write_buf[context->reads_completed], + context->read_buf[context->reads_completed], spdk_nvme_ns_get_sector_size(ns_entry->ns)); + if (rc == 0) { + context->matches_previous_data++; + } + + rc = memcmp(context->zero_buf, context->read_buf[context->reads_completed], + spdk_nvme_ns_get_sector_size(ns_entry->ns)); + if (rc == 0) { + context->matches_zeroes++; + } + + rc = memcmp(context->FFh_buf, context->read_buf[context->reads_completed], + spdk_nvme_ns_get_sector_size(ns_entry->ns)); + if (rc == 0) { + context->matches_FFh++; + } + context->reads_completed++; +} + +static void +deallocate_complete(void *arg, const struct spdk_nvme_cpl *completion) +{ + struct deallocate_context *context = arg; + + printf("blocks matching previous data: %d\n", context->matches_previous_data); + printf("blocks matching zeroes: %d\n", context->matches_zeroes); + printf("blocks matching 0xFF: %d\n", context->matches_FFh); + printf("Deallocating Blocks 0 to %d with random data.\n", NUM_BLOCKS - 1); + printf("On next read, read value will match deallocated block read value.\n"); + context->deallocate_completed = 1; + context->reads_completed = 0; + context->matches_previous_data = 0; + context->matches_zeroes = 0; + context->matches_FFh = 0; +} + +static void +flush_complete(void *arg, const struct spdk_nvme_cpl *completion) +{ + struct deallocate_context *context = arg; + + context->flush_complete = 1; +} + +static void +deallocate_test(void) +{ + struct ns_entry *ns_entry; + struct spdk_nvme_ctrlr *ctrlr; + const struct spdk_nvme_ctrlr_data *data; + struct deallocate_context context; + struct spdk_nvme_dsm_range range; + uint32_t max_block_size; + int rc, i; + + memset(&context, 0, sizeof(struct deallocate_context)); + max_block_size = get_max_block_size(); + ns_entry = g_namespaces; + + if (max_block_size > 0) { + context.zero_buf = malloc(max_block_size); + } else { + printf("Unable to determine max block size.\n"); + return; + } + + if (context.zero_buf == NULL) { + printf("could not allocate buffer for test.\n"); + return; + } + + context.FFh_buf = malloc(max_block_size); + if (context.FFh_buf == NULL) { + cleanup(&context); + printf("could not allocate buffer for test.\n"); + return; + } + + context.write_buf = calloc(NUM_BLOCKS, sizeof(char *)); + if (context.write_buf == NULL) { + cleanup(&context); + return; + } + + context.read_buf = calloc(NUM_BLOCKS, sizeof(char *)); + if (context.read_buf == NULL) { + printf("could not allocate buffer for test.\n"); + cleanup(&context); + return; + } + + memset(context.zero_buf, 0x00, max_block_size); + memset(context.FFh_buf, 0xFF, max_block_size); + + for (i = 0; i < NUM_BLOCKS; i++) { + context.write_buf[i] = spdk_dma_zmalloc(0x1000, max_block_size, NULL); + if (context.write_buf[i] == NULL) { + printf("could not allocate buffer for test.\n"); + cleanup(&context); + return; + } + + fill_random(context.write_buf[i], 0x1000); + context.read_buf[i] = spdk_dma_zmalloc(0x1000, max_block_size, NULL); + if (context.read_buf[i] == NULL) { + printf("could not allocate buffer for test.\n"); + cleanup(&context); + return; + } + } + + while (ns_entry != NULL) { + + ns_entry->qpair = spdk_nvme_ctrlr_alloc_io_qpair(ns_entry->ctrlr, NULL, 0); + if (ns_entry->qpair == NULL) { + printf("ERROR: spdk_nvme_ctrlr_alloc_io_qpair() failed.\n"); + return; + } + + ctrlr = spdk_nvme_ns_get_ctrlr(ns_entry->ns); + data = spdk_nvme_ctrlr_get_data(ctrlr); + + printf("\nController %-20.20s (%-20.20s)\n", data->mn, data->sn); + printf("Controller PCI vendor:%u PCI subsystem vendor:%u\n", data->vid, data->ssvid); + printf("Namespace Block Size:%u\n", spdk_nvme_ns_get_sector_size(ns_entry->ns)); + printf("Writing Blocks 0 to %d with random data.\n", NUM_BLOCKS); + printf("On next read, read value will match random data.\n"); + + context.ns_entry = ns_entry; + + for (i = 0; i < NUM_BLOCKS; i++) { + rc = spdk_nvme_ns_cmd_write(ns_entry->ns, ns_entry->qpair, context.write_buf[i], + i, + 1, + write_complete, &context, 0); + if (rc) { + printf("Error in nvme command completion, values may be inaccurate.\n"); + } + } + while (context.writes_completed < NUM_BLOCKS) { + spdk_nvme_qpair_process_completions(ns_entry->qpair, 0); + } + + spdk_nvme_ns_cmd_flush(ns_entry->ns, ns_entry->qpair, flush_complete, &context); + while (!context.flush_complete) { + spdk_nvme_qpair_process_completions(ns_entry->qpair, 0); + } + + for (i = 0; i < NUM_BLOCKS; i++) { + rc = spdk_nvme_ns_cmd_read(ns_entry->ns, ns_entry->qpair, context.read_buf[i], + i, /* LBA start */ + 1, /* number of LBAs */ + read_complete, &context, 0); + if (rc) { + printf("Error in nvme command completion, values may be inaccurate.\n"); + } + + /* block after each read command so that we can match the block to the write buffer. */ + while (context.reads_completed <= i) { + spdk_nvme_qpair_process_completions(ns_entry->qpair, 0); + } + } + + context.flush_complete = 0; + range.length = NUM_BLOCKS; + range.starting_lba = 0; + rc = spdk_nvme_ns_cmd_dataset_management(ns_entry->ns, ns_entry->qpair, + SPDK_NVME_DSM_ATTR_DEALLOCATE, &range, 1, deallocate_complete, &context); + if (rc) { + printf("Error in nvme command completion, values may be inaccurate.\n"); + } + + while (!context.deallocate_completed) { + spdk_nvme_qpair_process_completions(ns_entry->qpair, 0); + } + + for (i = 0; i < NUM_BLOCKS; i++) { + rc = spdk_nvme_ns_cmd_read(ns_entry->ns, ns_entry->qpair, context.read_buf[i], + i, /* LBA start */ + 1, /* number of LBAs */ + read_complete, &context, 0); + if (rc) { + printf("Error in nvme command completion, values may be inaccurate.\n"); + } + while (context.reads_completed <= i) { + spdk_nvme_qpair_process_completions(ns_entry->qpair, 0); + } + } + + printf("blocks matching previous data: %d\n", context.matches_previous_data); + printf("blocks matching zeroes: %d\n", context.matches_zeroes); + printf("blocks matching FFh: %d\n", context.matches_FFh); + + /* reset counters in between each namespace. */ + context.matches_previous_data = 0; + context.matches_zeroes = 0; + context.matches_FFh = 0; + context.writes_completed = 0; + context.reads_completed = 0; + context.deallocate_completed = 0; + + spdk_nvme_ctrlr_free_io_qpair(ns_entry->qpair); + ns_entry = ns_entry->next; + } + cleanup(&context); +} + +static bool +probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr_opts *opts) +{ + printf("Attaching to %s\n", trid->traddr); + + return true; +} + +static void +attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ + int num_ns; + struct spdk_nvme_ns *ns; + + printf("Attached to %s\n", trid->traddr); + /* + * Use only the first namespace from each controller since we are testing controller level functionality. + */ + num_ns = spdk_nvme_ctrlr_get_num_ns(ctrlr); + if (num_ns < 1) { + printf("No valid namespaces in controller\n"); + } else { + ns = spdk_nvme_ctrlr_get_ns(ctrlr, 1); + register_ns(ctrlr, ns); + } +} + +static void +cleanup(struct deallocate_context *context) +{ + struct ns_entry *ns_entry = g_namespaces; + int i; + + while (ns_entry) { + struct ns_entry *next = ns_entry->next; + free(ns_entry); + ns_entry = next; + } + for (i = 0; i < NUM_BLOCKS; i++) { + if (context->write_buf[i]) { + spdk_dma_free(context->write_buf[i]); + } else { + break; + } + if (context->read_buf[i]) { + spdk_dma_free(context->read_buf[i]); + } else { + break; + } + } + + free(context->write_buf); + free(context->read_buf); + free(context->zero_buf); + free(context->FFh_buf); +} + +int main(int argc, char **argv) +{ + int rc; + struct spdk_env_opts opts; + + spdk_env_opts_init(&opts); + opts.name = "deallocate_test"; + opts.shm_id = 0; + if (spdk_env_init(&opts) < 0) { + fprintf(stderr, "Unable to initialize SPDK env\n"); + return 1; + } + + printf("Initializing NVMe Controllers\n"); + + rc = spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL); + if (rc != 0) { + fprintf(stderr, "spdk_nvme_probe() failed\n"); + return 1; + } + + if (g_namespaces == NULL) { + fprintf(stderr, "no NVMe controllers found\n"); + return 1; + } + + printf("Initialization complete.\n"); + deallocate_test(); + return 0; +} diff --git a/src/spdk/test/nvme/e2edp/.gitignore b/src/spdk/test/nvme/e2edp/.gitignore new file mode 100644 index 00000000..df095820 --- /dev/null +++ b/src/spdk/test/nvme/e2edp/.gitignore @@ -0,0 +1 @@ +nvme_dp diff --git a/src/spdk/test/nvme/e2edp/Makefile b/src/spdk/test/nvme/e2edp/Makefile new file mode 100644 index 00000000..226a50e0 --- /dev/null +++ b/src/spdk/test/nvme/e2edp/Makefile @@ -0,0 +1,39 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = nvme_dp + +include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk diff --git a/src/spdk/test/nvme/e2edp/nvme_dp.c b/src/spdk/test/nvme/e2edp/nvme_dp.c new file mode 100644 index 00000000..eaf2bd32 --- /dev/null +++ b/src/spdk/test/nvme/e2edp/nvme_dp.c @@ -0,0 +1,659 @@ +/*- + * 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. + */ + +/* + * NVMe end-to-end data protection test + */ + +#include "spdk/stdinc.h" + +#include "spdk/nvme.h" +#include "spdk/env.h" +#include "spdk/crc16.h" +#include "spdk/endian.h" + +#define MAX_DEVS 64 + +#define DATA_PATTERN 0x5A + +struct dev { + struct spdk_nvme_ctrlr *ctrlr; + char name[SPDK_NVMF_TRADDR_MAX_LEN + 1]; +}; + +static struct dev devs[MAX_DEVS]; +static int num_devs = 0; + +#define foreach_dev(iter) \ + for (iter = devs; iter - devs < num_devs; iter++) + +static int io_complete_flag = 0; + +struct io_request { + void *contig; + void *metadata; + bool use_extended_lba; + bool use_sgl; + uint32_t sgl_offset; + uint32_t buf_size; + uint64_t lba; + uint32_t lba_count; + uint16_t apptag_mask; + uint16_t apptag; +}; + +static void +io_complete(void *ctx, const struct spdk_nvme_cpl *cpl) +{ + if (spdk_nvme_cpl_is_error(cpl)) { + io_complete_flag = 2; + } else { + io_complete_flag = 1; + } +} + +static void +ns_data_buffer_reset(struct spdk_nvme_ns *ns, struct io_request *req, uint8_t data_pattern) +{ + uint32_t md_size, sector_size; + uint32_t i, offset = 0; + uint8_t *buf; + + sector_size = spdk_nvme_ns_get_sector_size(ns); + md_size = spdk_nvme_ns_get_md_size(ns); + + for (i = 0; i < req->lba_count; i++) { + if (req->use_extended_lba) { + offset = (sector_size + md_size) * i; + } else { + offset = sector_size * i; + } + + buf = (uint8_t *)req->contig + offset; + memset(buf, data_pattern, sector_size); + } +} + +static void nvme_req_reset_sgl(void *cb_arg, uint32_t sgl_offset) +{ + struct io_request *req = (struct io_request *)cb_arg; + + req->sgl_offset = sgl_offset; + return; +} + +static int nvme_req_next_sge(void *cb_arg, void **address, uint32_t *length) +{ + struct io_request *req = (struct io_request *)cb_arg; + void *payload; + + payload = req->contig + req->sgl_offset; + *address = payload; + + *length = req->buf_size - req->sgl_offset; + + return 0; +} + +/* CRC-16 Guard checked for extended lba format */ +static uint32_t dp_guard_check_extended_lba_test(struct spdk_nvme_ns *ns, struct io_request *req, + uint32_t *io_flags) +{ + struct spdk_nvme_protection_info *pi; + uint32_t md_size, sector_size; + + req->lba_count = 2; + + /* extended LBA only for the test case */ + if (!(spdk_nvme_ns_supports_extended_lba(ns))) { + return 0; + } + + sector_size = spdk_nvme_ns_get_sector_size(ns); + md_size = spdk_nvme_ns_get_md_size(ns); + req->contig = spdk_dma_zmalloc((sector_size + md_size) * req->lba_count, 0x1000, NULL); + if (!req->contig) { + return 0; + } + + req->lba = 0x200000; + req->use_extended_lba = true; + req->use_sgl = true; + req->buf_size = (sector_size + md_size) * req->lba_count; + req->metadata = NULL; + ns_data_buffer_reset(ns, req, DATA_PATTERN); + pi = (struct spdk_nvme_protection_info *)(req->contig + sector_size + md_size - 8); + /* big-endian for guard */ + to_be16(&pi->guard, spdk_crc16_t10dif(req->contig, sector_size)); + + pi = (struct spdk_nvme_protection_info *)(req->contig + (sector_size + md_size) * 2 - 8); + to_be16(&pi->guard, spdk_crc16_t10dif(req->contig + sector_size + md_size, sector_size)); + + *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_GUARD; + + return req->lba_count; +} + +/* + * No protection information with PRACT setting to 1, + * both extended LBA format and separate metadata can + * run the test case. + */ +static uint32_t dp_with_pract_test(struct spdk_nvme_ns *ns, struct io_request *req, + uint32_t *io_flags) +{ + uint32_t sector_size; + + req->lba_count = 8; + + sector_size = spdk_nvme_ns_get_sector_size(ns); + /* No additional metadata buffer provided */ + req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL); + if (!req->contig) { + return 0; + } + + switch (spdk_nvme_ns_get_pi_type(ns)) { + case SPDK_NVME_FMT_NVM_PROTECTION_TYPE3: + *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_GUARD | SPDK_NVME_IO_FLAGS_PRACT; + break; + case SPDK_NVME_FMT_NVM_PROTECTION_TYPE1: + case SPDK_NVME_FMT_NVM_PROTECTION_TYPE2: + *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_GUARD | SPDK_NVME_IO_FLAGS_PRCHK_REFTAG | + SPDK_NVME_IO_FLAGS_PRACT; + break; + default: + *io_flags = 0; + break; + } + + req->lba = 0x100000; + req->use_extended_lba = false; + req->metadata = NULL; + + return req->lba_count; +} + +/* Block Reference Tag checked for TYPE1 and TYPE2 with PRACT setting to 0 */ +static uint32_t dp_without_pract_extended_lba_test(struct spdk_nvme_ns *ns, struct io_request *req, + uint32_t *io_flags) +{ + struct spdk_nvme_protection_info *pi; + uint32_t md_size, sector_size; + + req->lba_count = 2; + + switch (spdk_nvme_ns_get_pi_type(ns)) { + case SPDK_NVME_FMT_NVM_PROTECTION_TYPE3: + return 0; + default: + break; + } + + /* extended LBA only for the test case */ + if (!(spdk_nvme_ns_supports_extended_lba(ns))) { + return 0; + } + + sector_size = spdk_nvme_ns_get_sector_size(ns); + md_size = spdk_nvme_ns_get_md_size(ns); + req->contig = spdk_dma_zmalloc((sector_size + md_size) * req->lba_count, 0x1000, NULL); + if (!req->contig) { + return 0; + } + + req->lba = 0x200000; + req->use_extended_lba = true; + req->metadata = NULL; + pi = (struct spdk_nvme_protection_info *)(req->contig + sector_size + md_size - 8); + /* big-endian for reference tag */ + to_be32(&pi->ref_tag, (uint32_t)req->lba); + + pi = (struct spdk_nvme_protection_info *)(req->contig + (sector_size + md_size) * 2 - 8); + /* is incremented for each subsequent logical block */ + to_be32(&pi->ref_tag, (uint32_t)(req->lba + 1)); + + *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_REFTAG; + + return req->lba_count; +} + +/* LBA + Metadata without data protection bits setting */ +static uint32_t dp_without_flags_extended_lba_test(struct spdk_nvme_ns *ns, struct io_request *req, + uint32_t *io_flags) +{ + uint32_t md_size, sector_size; + + req->lba_count = 16; + + /* extended LBA only for the test case */ + if (!(spdk_nvme_ns_supports_extended_lba(ns))) { + return 0; + } + + sector_size = spdk_nvme_ns_get_sector_size(ns); + md_size = spdk_nvme_ns_get_md_size(ns); + req->contig = spdk_dma_zmalloc((sector_size + md_size) * req->lba_count, 0x1000, NULL); + if (!req->contig) { + return 0; + } + + req->lba = 0x400000; + req->use_extended_lba = true; + req->metadata = NULL; + *io_flags = 0; + + return req->lba_count; +} + +/* Block Reference Tag checked for TYPE1 and TYPE2 with PRACT setting to 0 */ +static uint32_t dp_without_pract_separate_meta_test(struct spdk_nvme_ns *ns, struct io_request *req, + uint32_t *io_flags) +{ + struct spdk_nvme_protection_info *pi; + uint32_t md_size, sector_size; + + req->lba_count = 2; + + switch (spdk_nvme_ns_get_pi_type(ns)) { + case SPDK_NVME_FMT_NVM_PROTECTION_TYPE3: + return 0; + default: + break; + } + + /* separate metadata payload for the test case */ + if (spdk_nvme_ns_supports_extended_lba(ns)) { + return 0; + } + + sector_size = spdk_nvme_ns_get_sector_size(ns); + md_size = spdk_nvme_ns_get_md_size(ns); + req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL); + if (!req->contig) { + return 0; + } + + req->metadata = spdk_dma_zmalloc(md_size * req->lba_count, 0x1000, NULL); + if (!req->metadata) { + spdk_dma_free(req->contig); + return 0; + } + + req->lba = 0x400000; + req->use_extended_lba = false; + + /* last 8 bytes if the metadata size bigger than 8 */ + pi = (struct spdk_nvme_protection_info *)(req->metadata + md_size - 8); + /* big-endian for reference tag */ + to_be32(&pi->ref_tag, (uint32_t)req->lba); + + pi = (struct spdk_nvme_protection_info *)(req->metadata + md_size * 2 - 8); + /* is incremented for each subsequent logical block */ + to_be32(&pi->ref_tag, (uint32_t)(req->lba + 1)); + + *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_REFTAG; + + return req->lba_count; +} + +/* Application Tag checked with PRACT setting to 0 */ +static uint32_t dp_without_pract_separate_meta_apptag_test(struct spdk_nvme_ns *ns, + struct io_request *req, + uint32_t *io_flags) +{ + struct spdk_nvme_protection_info *pi; + uint32_t md_size, sector_size; + + req->lba_count = 1; + + /* separate metadata payload for the test case */ + if (spdk_nvme_ns_supports_extended_lba(ns)) { + return 0; + } + + sector_size = spdk_nvme_ns_get_sector_size(ns); + md_size = spdk_nvme_ns_get_md_size(ns); + req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL); + if (!req->contig) { + return 0; + } + + req->metadata = spdk_dma_zmalloc(md_size * req->lba_count, 0x1000, NULL); + if (!req->metadata) { + spdk_dma_free(req->contig); + return 0; + } + + req->lba = 0x500000; + req->use_extended_lba = false; + req->apptag_mask = 0xFFFF; + req->apptag = req->lba_count; + + /* last 8 bytes if the metadata size bigger than 8 */ + pi = (struct spdk_nvme_protection_info *)(req->metadata + md_size - 8); + to_be16(&pi->app_tag, req->lba_count); + + *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_APPTAG; + + return req->lba_count; +} + +/* + * LBA + Metadata without data protection bits setting, + * separate metadata payload for the test case. + */ +static uint32_t dp_without_flags_separate_meta_test(struct spdk_nvme_ns *ns, struct io_request *req, + uint32_t *io_flags) +{ + uint32_t md_size, sector_size; + + req->lba_count = 16; + + /* separate metadata payload for the test case */ + if (spdk_nvme_ns_supports_extended_lba(ns)) { + return 0; + } + + sector_size = spdk_nvme_ns_get_sector_size(ns); + md_size = spdk_nvme_ns_get_md_size(ns); + req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL); + if (!req->contig) { + return 0; + } + + req->metadata = spdk_dma_zmalloc(md_size * req->lba_count, 0x1000, NULL); + if (!req->metadata) { + spdk_dma_free(req->contig); + return 0; + } + + req->lba = 0x600000; + req->use_extended_lba = false; + *io_flags = 0; + + return req->lba_count; +} + +typedef uint32_t (*nvme_build_io_req_fn_t)(struct spdk_nvme_ns *ns, struct io_request *req, + uint32_t *lba_count); + +static void +free_req(struct io_request *req) +{ + if (req == NULL) { + return; + } + + if (req->contig) { + spdk_dma_free(req->contig); + } + + if (req->metadata) { + spdk_dma_free(req->metadata); + } + + spdk_dma_free(req); +} + +static int +ns_data_buffer_compare(struct spdk_nvme_ns *ns, struct io_request *req, uint8_t data_pattern) +{ + uint32_t md_size, sector_size; + uint32_t i, j, offset = 0; + uint8_t *buf; + + sector_size = spdk_nvme_ns_get_sector_size(ns); + md_size = spdk_nvme_ns_get_md_size(ns); + + for (i = 0; i < req->lba_count; i++) { + if (req->use_extended_lba) { + offset = (sector_size + md_size) * i; + } else { + offset = sector_size * i; + } + + buf = (uint8_t *)req->contig + offset; + for (j = 0; j < sector_size; j++) { + if (buf[j] != data_pattern) { + return -1; + } + } + } + + return 0; +} + +static int +write_read_e2e_dp_tests(struct dev *dev, nvme_build_io_req_fn_t build_io_fn, const char *test_name) +{ + int rc = 0; + uint32_t lba_count; + uint32_t io_flags = 0; + + struct io_request *req; + struct spdk_nvme_ns *ns; + struct spdk_nvme_qpair *qpair; + const struct spdk_nvme_ns_data *nsdata; + + ns = spdk_nvme_ctrlr_get_ns(dev->ctrlr, 1); + if (!ns) { + fprintf(stderr, "Null namespace\n"); + return 0; + } + + if (!(spdk_nvme_ns_get_flags(ns) & SPDK_NVME_NS_DPS_PI_SUPPORTED)) { + return 0; + } + + nsdata = spdk_nvme_ns_get_data(ns); + if (!nsdata || !spdk_nvme_ns_get_sector_size(ns)) { + fprintf(stderr, "Empty nsdata or wrong sector size\n"); + return 0; + } + + req = spdk_dma_zmalloc(sizeof(*req), 0, NULL); + if (!req) { + fprintf(stderr, "Allocate request failed\n"); + return 0; + } + + /* IO parameters setting */ + lba_count = build_io_fn(ns, req, &io_flags); + + if (!lba_count) { + fprintf(stderr, "%s: %s bypass the test case\n", dev->name, test_name); + free_req(req); + return 0; + } + + qpair = spdk_nvme_ctrlr_alloc_io_qpair(dev->ctrlr, NULL, 0); + if (!qpair) { + free_req(req); + return -1; + } + + ns_data_buffer_reset(ns, req, DATA_PATTERN); + if (req->use_extended_lba && req->use_sgl) { + rc = spdk_nvme_ns_cmd_writev(ns, qpair, req->lba, lba_count, io_complete, req, io_flags, + nvme_req_reset_sgl, nvme_req_next_sge); + } else if (req->use_extended_lba) { + rc = spdk_nvme_ns_cmd_write(ns, qpair, req->contig, req->lba, lba_count, + io_complete, req, io_flags); + } else { + rc = spdk_nvme_ns_cmd_write_with_md(ns, qpair, req->contig, req->metadata, req->lba, lba_count, + io_complete, req, io_flags, req->apptag_mask, req->apptag); + } + + if (rc != 0) { + fprintf(stderr, "%s: %s write submit failed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return -1; + } + + io_complete_flag = 0; + + while (!io_complete_flag) { + spdk_nvme_qpair_process_completions(qpair, 1); + } + + if (io_complete_flag != 1) { + fprintf(stderr, "%s: %s write exec failed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return -1; + } + + /* reset completion flag */ + io_complete_flag = 0; + + ns_data_buffer_reset(ns, req, 0); + if (req->use_extended_lba && req->use_sgl) { + rc = spdk_nvme_ns_cmd_readv(ns, qpair, req->lba, lba_count, io_complete, req, io_flags, + nvme_req_reset_sgl, nvme_req_next_sge); + + } else if (req->use_extended_lba) { + rc = spdk_nvme_ns_cmd_read(ns, qpair, req->contig, req->lba, lba_count, + io_complete, req, io_flags); + } else { + rc = spdk_nvme_ns_cmd_read_with_md(ns, qpair, req->contig, req->metadata, req->lba, lba_count, + io_complete, req, io_flags, req->apptag_mask, req->apptag); + } + + if (rc != 0) { + fprintf(stderr, "%s: %s read failed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return -1; + } + + while (!io_complete_flag) { + spdk_nvme_qpair_process_completions(qpair, 1); + } + + if (io_complete_flag != 1) { + fprintf(stderr, "%s: %s read failed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return -1; + } + + rc = ns_data_buffer_compare(ns, req, DATA_PATTERN); + if (rc < 0) { + fprintf(stderr, "%s: %s write/read success, but memcmp Failed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return -1; + } + + fprintf(stdout, "%s: %s test passed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return rc; +} + +static bool +probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr_opts *opts) +{ + printf("Attaching to %s\n", trid->traddr); + + return true; +} + +static void +attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ + struct dev *dev; + + /* add to dev list */ + dev = &devs[num_devs++]; + + dev->ctrlr = ctrlr; + + snprintf(dev->name, sizeof(dev->name), "%s", + trid->traddr); + + printf("Attached to %s\n", dev->name); +} + +int main(int argc, char **argv) +{ + struct dev *iter; + int rc, i; + struct spdk_env_opts opts; + + spdk_env_opts_init(&opts); + opts.name = "nvme_dp"; + opts.core_mask = "0x1"; + opts.shm_id = 0; + if (spdk_env_init(&opts) < 0) { + fprintf(stderr, "Unable to initialize SPDK env\n"); + return 1; + } + + printf("NVMe Write/Read with End-to-End data protection test\n"); + + if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) { + fprintf(stderr, "nvme_probe() failed\n"); + exit(1); + } + + rc = 0; + foreach_dev(iter) { +#define TEST(x) write_read_e2e_dp_tests(iter, x, #x) + if (TEST(dp_with_pract_test) + || TEST(dp_guard_check_extended_lba_test) + || TEST(dp_without_pract_extended_lba_test) + || TEST(dp_without_flags_extended_lba_test) + || TEST(dp_without_pract_separate_meta_test) + || TEST(dp_without_pract_separate_meta_apptag_test) + || TEST(dp_without_flags_separate_meta_test)) { +#undef TEST + rc = 1; + printf("%s: failed End-to-End data protection tests\n", iter->name); + } + } + + printf("Cleaning up...\n"); + + for (i = 0; i < num_devs; i++) { + struct dev *dev = &devs[i]; + + spdk_nvme_detach(dev->ctrlr); + } + + return rc; +} diff --git a/src/spdk/test/nvme/err_injection/.gitignore b/src/spdk/test/nvme/err_injection/.gitignore new file mode 100644 index 00000000..3572a8e7 --- /dev/null +++ b/src/spdk/test/nvme/err_injection/.gitignore @@ -0,0 +1 @@ +err_injection diff --git a/src/spdk/test/nvme/err_injection/Makefile b/src/spdk/test/nvme/err_injection/Makefile new file mode 100644 index 00000000..4f5f1851 --- /dev/null +++ b/src/spdk/test/nvme/err_injection/Makefile @@ -0,0 +1,39 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = err_injection + +include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk diff --git a/src/spdk/test/nvme/err_injection/err_injection.c b/src/spdk/test/nvme/err_injection/err_injection.c new file mode 100644 index 00000000..50d67092 --- /dev/null +++ b/src/spdk/test/nvme/err_injection/err_injection.c @@ -0,0 +1,279 @@ +/*- + * 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/log.h" +#include "spdk/nvme.h" +#include "spdk/env.h" + +#define MAX_DEVS 64 + +struct dev { + bool error_expected; + struct spdk_nvme_ctrlr *ctrlr; + struct spdk_nvme_ns *ns; + struct spdk_nvme_qpair *qpair; + void *data; + char name[SPDK_NVMF_TRADDR_MAX_LEN + 1]; +}; + +static struct dev devs[MAX_DEVS]; +static int num_devs = 0; + +#define foreach_dev(iter) \ + for (iter = devs; iter - devs < num_devs; iter++) + +static int outstanding_commands = 0; +static int failed = 0; + +static bool +probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr_opts *opts) +{ + printf("Attaching to %s\n", trid->traddr); + + return true; +} + +static void +attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ + struct dev *dev; + uint32_t nsid; + + /* add to dev list */ + dev = &devs[num_devs++]; + if (num_devs >= MAX_DEVS) { + return; + } + + dev->ctrlr = ctrlr; + nsid = spdk_nvme_ctrlr_get_first_active_ns(ctrlr); + dev->ns = spdk_nvme_ctrlr_get_ns(ctrlr, nsid); + if (dev->ns == NULL) { + failed = 1; + return; + } + dev->qpair = spdk_nvme_ctrlr_alloc_io_qpair(ctrlr, NULL, 0); + if (dev->qpair == NULL) { + failed = 1; + return; + } + + snprintf(dev->name, sizeof(dev->name), "%s", + trid->traddr); + + printf("Attached to %s\n", dev->name); +} + +static void +get_feature_test_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + struct dev *dev = cb_arg; + + outstanding_commands--; + + if (spdk_nvme_cpl_is_error(cpl) && dev->error_expected) { + if (cpl->status.sct != SPDK_NVME_SCT_GENERIC || + cpl->status.sc != SPDK_NVME_SC_INVALID_FIELD) { + failed = 1; + } + printf("%s: get features failed as expected\n", dev->name); + return; + } + + if (!spdk_nvme_cpl_is_error(cpl) && !dev->error_expected) { + printf("%s: get features successfully as expected\n", dev->name); + return; + } + + failed = 1; +} + +static void +get_feature_test(bool error_expected) +{ + struct dev *dev; + struct spdk_nvme_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opc = SPDK_NVME_OPC_GET_FEATURES; + cmd.cdw10 = SPDK_NVME_FEAT_NUMBER_OF_QUEUES; + + foreach_dev(dev) { + dev->error_expected = error_expected; + if (spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0, + get_feature_test_cb, dev) != 0) { + printf("Error: failed to send Get Features command for dev=%p\n", dev); + failed = 1; + goto cleanup; + } + outstanding_commands++; + } + +cleanup: + + while (outstanding_commands) { + foreach_dev(dev) { + spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr); + } + } +} + +static void +read_test_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + struct dev *dev = cb_arg; + + outstanding_commands--; + spdk_dma_free(dev->data); + + if (spdk_nvme_cpl_is_error(cpl) && dev->error_expected) { + if (cpl->status.sct != SPDK_NVME_SCT_MEDIA_ERROR || + cpl->status.sc != SPDK_NVME_SC_UNRECOVERED_READ_ERROR) { + failed = 1; + } + printf("%s: read failed as expected\n", dev->name); + return; + } + + if (!spdk_nvme_cpl_is_error(cpl) && !dev->error_expected) { + printf("%s: read successfully as expected\n", dev->name); + return; + } + + failed = 1; +} + +static void +read_test(bool error_expected) +{ + struct dev *dev; + + foreach_dev(dev) { + dev->error_expected = error_expected; + dev->data = spdk_dma_zmalloc(0x1000, 0x1000, NULL); + if (!dev->data) { + failed = 1; + goto cleanup; + } + + if (spdk_nvme_ns_cmd_read(dev->ns, dev->qpair, dev->data, + 0, 1, read_test_cb, dev, 0) != 0) { + printf("Error: failed to send Read command for dev=%p\n", dev); + failed = 1; + goto cleanup; + } + + outstanding_commands++; + } + +cleanup: + + while (outstanding_commands) { + foreach_dev(dev) { + spdk_nvme_qpair_process_completions(dev->qpair, 0); + } + } +} + +int main(int argc, char **argv) +{ + struct dev *dev; + int i; + struct spdk_env_opts opts; + int rc; + + spdk_env_opts_init(&opts); + opts.name = "err_injection"; + opts.core_mask = "0x1"; + opts.mem_size = 64; + opts.shm_id = 0; + if (spdk_env_init(&opts) < 0) { + fprintf(stderr, "Unable to initialize SPDK env\n"); + return 1; + } + + printf("NVMe Error Injection test\n"); + + if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) { + fprintf(stderr, "spdk_nvme_probe() failed\n"); + return 1; + } + + if (failed) { + goto exit; + } + + if (!num_devs) { + printf("No NVMe controller found, %s exiting\n", argv[0]); + return 1; + } + + foreach_dev(dev) { + /* Admin error injection at submission path */ + rc = spdk_nvme_qpair_add_cmd_error_injection(dev->ctrlr, NULL, + SPDK_NVME_OPC_GET_FEATURES, true, 5000, 1, + SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_INVALID_FIELD); + failed += rc; + /* IO error injection at completion path */ + rc = spdk_nvme_qpair_add_cmd_error_injection(dev->ctrlr, dev->qpair, + SPDK_NVME_OPC_READ, false, 0, 1, + SPDK_NVME_SCT_MEDIA_ERROR, SPDK_NVME_SC_UNRECOVERED_READ_ERROR); + failed += rc; + } + + if (failed) { + goto exit; + } + + /* Admin Get Feature, expect error return */ + get_feature_test(true); + /* Admin Get Feature, expect successful return */ + get_feature_test(false); + /* Read, expect error return */ + read_test(true); + /* Read, expect successful return */ + read_test(false); + +exit: + printf("Cleaning up...\n"); + for (i = 0; i < num_devs; i++) { + struct dev *dev = &devs[i]; + spdk_nvme_detach(dev->ctrlr); + } + + return failed; +} diff --git a/src/spdk/test/nvme/hotplug.sh b/src/spdk/test/nvme/hotplug.sh new file mode 100755 index 00000000..ca661b6f --- /dev/null +++ b/src/spdk/test/nvme/hotplug.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash + +set -xe + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) +source $rootdir/test/common/autotest_common.sh + +if [ -z "${DEPENDENCY_DIR}" ]; then + echo DEPENDENCY_DIR not defined! + exit 1 +fi + +function ssh_vm() { + sshpass -p "$password" ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -p 10022 root@localhost "$@" +} + +function monitor_cmd() { + rc=0 + if ! (echo "$@" | nc localhost 4444 > mon.log); then + rc=1 + cat mon.log + fi + rm mon.log + return $rc +} + +function get_online_devices_count() { + ssh_vm "lspci | grep -c NVM" +} + +function wait_for_devices_ready() { + count=$(get_online_devices_count) + + while [ $count -ne 4 ]; do + echo "waitting for all devices online" + count=$(get_online_devices_count) + done +} + +function devices_initialization() { + timing_enter devices_initialization + dd if=/dev/zero of=/root/test0 bs=1M count=1024 + dd if=/dev/zero of=/root/test1 bs=1M count=1024 + dd if=/dev/zero of=/root/test2 bs=1M count=1024 + dd if=/dev/zero of=/root/test3 bs=1M count=1024 + monitor_cmd "drive_add 0 file=/root/test0,format=raw,id=drive0,if=none" + monitor_cmd "drive_add 1 file=/root/test1,format=raw,id=drive1,if=none" + monitor_cmd "drive_add 2 file=/root/test2,format=raw,id=drive2,if=none" + monitor_cmd "drive_add 3 file=/root/test3,format=raw,id=drive3,if=none" + timing_exit devices_initialization +} + +function insert_devices() { + monitor_cmd "device_add nvme,drive=drive0,id=nvme0,serial=nvme0" + monitor_cmd "device_add nvme,drive=drive1,id=nvme1,serial=nvme1" + monitor_cmd "device_add nvme,drive=drive2,id=nvme2,serial=nvme2" + monitor_cmd "device_add nvme,drive=drive3,id=nvme3,serial=nvme3" + wait_for_devices_ready + ssh_vm "scripts/setup.sh" +} + +function remove_devices() { + monitor_cmd "device_del nvme0" + monitor_cmd "device_del nvme1" + monitor_cmd "device_del nvme2" + monitor_cmd "device_del nvme3" +} + +function devices_delete() { + timing_enter devices_delete + rm /root/test0 + rm /root/test1 + rm /root/test2 + rm /root/test3 + timing_exit devices_delete +} + +password=$1 +base_img=${DEPENDENCY_DIR}/fedora24.img +test_img=${DEPENDENCY_DIR}/fedora24_test.img +qemu_pidfile=${DEPENDENCY_DIR}/qemupid + +if [ ! -e "$base_img" ]; then + echo "Hotplug VM image not found; skipping test" + exit 0 +fi + +timing_enter hotplug + +timing_enter start_qemu + +qemu-img create -b "$base_img" -f qcow2 "$test_img" + +qemu-system-x86_64 \ + -daemonize -display none -m 8192 \ + -pidfile "$qemu_pidfile" \ + -hda "$test_img" \ + -net user,hostfwd=tcp::10022-:22 \ + -net nic \ + -cpu host \ + -smp cores=16,sockets=1 \ + --enable-kvm \ + -chardev socket,id=mon0,host=localhost,port=4444,server,nowait \ + -mon chardev=mon0,mode=readline + +timing_exit start_qemu + +timing_enter wait_for_vm +ssh_vm 'echo ready' +timing_exit wait_for_vm + +timing_enter copy_repo +(cd "$rootdir"; tar -cf - .) | (ssh_vm 'tar -xf -') +timing_exit copy_repo + +devices_initialization +insert_devices + +timing_enter hotplug_test + +ssh_vm "examples/nvme/hotplug/hotplug -i 0 -t 25 -n 4 -r 8" & +example_pid=$! + +sleep 4 +remove_devices +sleep 4 +insert_devices +sleep 4 +remove_devices +devices_delete + +timing_enter wait_for_example +wait $example_pid +timing_exit wait_for_example + +trap - SIGINT SIGTERM EXIT + +qemupid=`cat "$qemu_pidfile" | awk '{printf $0}'` +kill -9 $qemupid +rm "$qemu_pidfile" +rm "$test_img" + +report_test_completion "nvme_hotplug" +timing_exit hotplug_test + +timing_exit hotplug diff --git a/src/spdk/test/nvme/nvme.sh b/src/spdk/test/nvme/nvme.sh new file mode 100755 index 00000000..c52b926e --- /dev/null +++ b/src/spdk/test/nvme/nvme.sh @@ -0,0 +1,187 @@ +#!/usr/bin/env bash + +set -e + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) +source $rootdir/scripts/common.sh +source $rootdir/test/common/autotest_common.sh + +function get_nvme_name_from_bdf { + lsblk -d --output NAME + nvme_devs=$(lsblk -d --output NAME | grep "^nvme") || true + if [ -z "$nvme_devs" ]; then + return + fi + for dev in $nvme_devs; do + link_name=$(readlink /sys/block/$dev/device/device) || true + if [ -z "$link_name" ]; then + link_name=$(readlink /sys/block/$dev/device) + fi + bdf=$(basename "$link_name") + if [ "$bdf" = "$1" ]; then + eval "$2=$dev" + return + fi + done +} + +timing_enter nvme + +if [ `uname` = Linux ]; then + # check that our setup.sh script does not bind NVMe devices to uio/vfio if they + # have an active mountpoint + $rootdir/scripts/setup.sh reset + # give kernel nvme driver some time to create the block devices before we start looking for them + sleep 1 + blkname='' + # first, find an NVMe device that does not have an active mountpoint already; + # this covers rare case where someone is running this test script on a system + # that has a mounted NVMe filesystem + # + # note: more work probably needs to be done to properly handle devices with multiple + # namespaces + for bdf in $(iter_pci_class_code 01 08 02); do + get_nvme_name_from_bdf "$bdf" blkname + if [ "$blkname" != "" ]; then + mountpoints=$(lsblk /dev/$blkname --output MOUNTPOINT -n | wc -w) + if [ "$mountpoints" = "0" ]; then + break + else + blkname='' + fi + fi + done + + # if we found an NVMe block device without an active mountpoint, create and mount + # a filesystem on it for purposes of testing the setup.sh script + if [ "$blkname" != "" ]; then + parted -s /dev/$blkname mklabel gpt + # just create a 100MB partition - this tests our ability to detect mountpoints + # on partitions of the device, not just the device itself; it also is faster + # since we don't trim and initialize the whole namespace + parted -s /dev/$blkname mkpart primary 1 100 + sleep 1 + mkfs.ext4 -F /dev/${blkname}p1 + mkdir -p /tmp/nvmetest + mount /dev/${blkname}p1 /tmp/nvmetest + $rootdir/scripts/setup.sh + driver=$(basename $(readlink /sys/bus/pci/devices/$bdf/driver)) + # check that the nvme driver is still loaded against the device + if [ "$driver" != "nvme" ]; then + exit 1 + fi + umount /tmp/nvmetest + rmdir /tmp/nvmetest + # write zeroes to the device to blow away the partition table and filesystem + dd if=/dev/zero of=/dev/$blkname oflag=direct bs=1M count=1 + $rootdir/scripts/setup.sh + driver=$(basename $(readlink /sys/bus/pci/devices/$bdf/driver)) + # check that the nvme driver is not loaded against the device + if [ "$driver" = "nvme" ]; then + exit 1 + fi + else + $rootdir/scripts/setup.sh + fi +fi + +if [ `uname` = Linux ]; then + start_stub "-s 4096 -i 0 -m 0xF" + trap "kill_stub; exit 1" SIGINT SIGTERM EXIT +fi + +if [ $RUN_NIGHTLY -eq 1 ]; then + # TODO: temporarily disabled - temperature AER doesn't fire on emulated controllers + #timing_enter aer + #$testdir/aer/aer + #timing_exit aer + + timing_enter reset + $testdir/reset/reset -q 64 -w write -s 4096 -t 2 + report_test_completion "nightly_nvme_reset" + timing_exit reset +fi + +timing_enter identify +$rootdir/examples/nvme/identify/identify -i 0 +for bdf in $(iter_pci_class_code 01 08 02); do + $rootdir/examples/nvme/identify/identify -r "trtype:PCIe traddr:${bdf}" -i 0 +done +timing_exit identify + +timing_enter perf +$rootdir/examples/nvme/perf/perf -q 128 -w read -o 12288 -t 1 -LL -i 0 +if [ -b /dev/ram0 ]; then + # Test perf with AIO device + $rootdir/examples/nvme/perf/perf /dev/ram0 -q 128 -w read -o 12288 -t 1 -LL -i 0 + report_test_completion "nvme_perf" +fi +timing_exit perf + +timing_enter reserve +$rootdir/examples/nvme/reserve/reserve +timing_exit reserve + +timing_enter hello_world +$rootdir/examples/nvme/hello_world/hello_world +timing_exit + +timing_enter deallocated_value +$testdir/deallocated_value/deallocated_value +timing_exit deallocated_value + +timing_enter sgl +$testdir/sgl/sgl +timing_exit sgl + +timing_enter e2edp +$testdir/e2edp/nvme_dp +timing_exit e2edp + +timing_enter err_injection +$testdir/err_injection/err_injection +timing_exit err_injection + +timing_enter overhead +$testdir/overhead/overhead -s 4096 -t 1 -H +timing_exit overhead + +timing_enter arbitration +$rootdir/examples/nvme/arbitration/arbitration -t 3 -i 0 +timing_exit arbitration + +if [ `uname` = Linux ]; then + timing_enter multi_secondary + $rootdir/examples/nvme/perf/perf -i 0 -q 16 -w read -o 4096 -t 3 -c 0x1 & + pid0=$! + $rootdir/examples/nvme/perf/perf -i 0 -q 16 -w read -o 4096 -t 3 -c 0x2 & + pid1=$! + $rootdir/examples/nvme/perf/perf -i 0 -q 16 -w read -o 4096 -t 3 -c 0x4 + wait $pid0 + wait $pid1 + report_test_completion "nvme_multi_secondary" + timing_exit multi_secondary +fi + +if [ `uname` = Linux ]; then + trap - SIGINT SIGTERM EXIT + kill_stub +fi +PLUGIN_DIR=$rootdir/examples/nvme/fio_plugin + +if [ -d /usr/src/fio ]; then + timing_enter fio_plugin + for bdf in $(iter_pci_class_code 01 08 02); do + # Only test when ASAN is not enabled. If ASAN is enabled, we cannot test. + if [ $SPDK_RUN_ASAN -eq 0 ]; then + LD_PRELOAD=$PLUGIN_DIR/fio_plugin /usr/src/fio/fio $PLUGIN_DIR/example_config.fio --filename="trtype=PCIe traddr=${bdf//:/.} ns=1" + report_test_completion "bdev_fio" + fi + break + done + + timing_exit fio_plugin +fi + +timing_exit nvme diff --git a/src/spdk/test/nvme/overhead/.gitignore b/src/spdk/test/nvme/overhead/.gitignore new file mode 100644 index 00000000..d5a7d6f4 --- /dev/null +++ b/src/spdk/test/nvme/overhead/.gitignore @@ -0,0 +1 @@ +overhead diff --git a/src/spdk/test/nvme/overhead/Makefile b/src/spdk/test/nvme/overhead/Makefile new file mode 100644 index 00000000..bcb7f38d --- /dev/null +++ b/src/spdk/test/nvme/overhead/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = overhead + +ifeq ($(OS),Linux) +SYS_LIBS += -laio +CFLAGS += -DHAVE_LIBAIO +endif + +include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk diff --git a/src/spdk/test/nvme/overhead/README b/src/spdk/test/nvme/overhead/README new file mode 100644 index 00000000..b88c4217 --- /dev/null +++ b/src/spdk/test/nvme/overhead/README @@ -0,0 +1,24 @@ +This application measures the software overhead of I/O submission +and completion for both the SPDK NVMe driver and an AIO file handle. +It runs a random read, queue depth = 1 workload to a single device, +and captures TSC as follows: + +* Submission: capture TSC before and after the I/O submission + call (SPDK or AIO). +* Completion: capture TSC before and after the I/O completion + check. Only record the TSC delta if the I/O completion check + resulted in a completed I/O. Also use heuristics in the AIO + case to account for time spent in interrupt handling outside + of the actual I/O completion check. + +Usage: + +To test software overhead for a 4KB I/O over a 10 second period: + +SPDK: overhead -s 4096 -t 10 +AIO: overhead -s 4096 -t 10 /dev/nvme0n1 + +Note that for the SPDK case, it will only use the first namespace +on the first controller found by SPDK. If a different namespace is +desired, attach controllers individually to the kernel NVMe driver +to ensure they will not be enumerated by SPDK. diff --git a/src/spdk/test/nvme/overhead/overhead.c b/src/spdk/test/nvme/overhead/overhead.c new file mode 100644 index 00000000..f35247a2 --- /dev/null +++ b/src/spdk/test/nvme/overhead/overhead.c @@ -0,0 +1,720 @@ +/*- + * 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/barrier.h" +#include "spdk/fd.h" +#include "spdk/nvme.h" +#include "spdk/env.h" +#include "spdk/string.h" +#include "spdk/nvme_intel.h" +#include "spdk/histogram_data.h" + +#if HAVE_LIBAIO +#include <libaio.h> +#endif + +struct ctrlr_entry { + struct spdk_nvme_ctrlr *ctrlr; + struct ctrlr_entry *next; + char name[1024]; +}; + +enum entry_type { + ENTRY_TYPE_NVME_NS, + ENTRY_TYPE_AIO_FILE, +}; + +struct ns_entry { + enum entry_type type; + + union { + struct { + struct spdk_nvme_ctrlr *ctrlr; + struct spdk_nvme_ns *ns; + struct spdk_nvme_qpair *qpair; + } nvme; +#if HAVE_LIBAIO + struct { + int fd; + struct io_event *events; + io_context_t ctx; + } aio; +#endif + } u; + + uint32_t io_size_blocks; + uint64_t size_in_ios; + bool is_draining; + uint32_t current_queue_depth; + char name[1024]; + struct ns_entry *next; + + struct spdk_histogram_data *submit_histogram; + struct spdk_histogram_data *complete_histogram; +}; + +struct perf_task { + void *buf; + uint64_t submit_tsc; +#if HAVE_LIBAIO + struct iocb iocb; +#endif +}; + +static bool g_enable_histogram = false; + +static struct ctrlr_entry *g_ctrlr = NULL; +static struct ns_entry *g_ns = NULL; + +static uint64_t g_tsc_rate; + +static uint32_t g_io_size_bytes; +static int g_time_in_sec; + +static int g_aio_optind; /* Index of first AIO filename in argv */ + +struct perf_task *g_task; +uint64_t g_tsc_submit = 0; +uint64_t g_tsc_submit_min = UINT64_MAX; +uint64_t g_tsc_submit_max = 0; +uint64_t g_tsc_complete = 0; +uint64_t g_tsc_complete_min = UINT64_MAX; +uint64_t g_tsc_complete_max = 0; +uint64_t g_io_completed = 0; + +static void +register_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns *ns) +{ + struct ns_entry *entry; + const struct spdk_nvme_ctrlr_data *cdata; + + cdata = spdk_nvme_ctrlr_get_data(ctrlr); + + if (!spdk_nvme_ns_is_active(ns)) { + printf("Controller %-20.20s (%-20.20s): Skipping inactive NS %u\n", + cdata->mn, cdata->sn, + spdk_nvme_ns_get_id(ns)); + return; + } + + if (spdk_nvme_ns_get_size(ns) < g_io_size_bytes || + spdk_nvme_ns_get_sector_size(ns) > g_io_size_bytes) { + printf("WARNING: controller %-20.20s (%-20.20s) ns %u has invalid " + "ns size %" PRIu64 " / block size %u for I/O size %u\n", + cdata->mn, cdata->sn, spdk_nvme_ns_get_id(ns), + spdk_nvme_ns_get_size(ns), spdk_nvme_ns_get_sector_size(ns), g_io_size_bytes); + return; + } + + entry = calloc(1, sizeof(struct ns_entry)); + if (entry == NULL) { + perror("ns_entry malloc"); + exit(1); + } + + entry->type = ENTRY_TYPE_NVME_NS; + entry->u.nvme.ctrlr = ctrlr; + entry->u.nvme.ns = ns; + + entry->size_in_ios = spdk_nvme_ns_get_size(ns) / + g_io_size_bytes; + entry->io_size_blocks = g_io_size_bytes / spdk_nvme_ns_get_sector_size(ns); + entry->submit_histogram = spdk_histogram_data_alloc(); + entry->complete_histogram = spdk_histogram_data_alloc(); + + snprintf(entry->name, 44, "%-20.20s (%-20.20s)", cdata->mn, cdata->sn); + + entry->next = g_ns; + g_ns = entry; +} + +static void +register_ctrlr(struct spdk_nvme_ctrlr *ctrlr) +{ + int num_ns; + struct ctrlr_entry *entry = malloc(sizeof(struct ctrlr_entry)); + const struct spdk_nvme_ctrlr_data *cdata = spdk_nvme_ctrlr_get_data(ctrlr); + + if (entry == NULL) { + perror("ctrlr_entry malloc"); + exit(1); + } + + snprintf(entry->name, sizeof(entry->name), "%-20.20s (%-20.20s)", cdata->mn, cdata->sn); + + entry->ctrlr = ctrlr; + + entry->next = g_ctrlr; + g_ctrlr = entry; + + num_ns = spdk_nvme_ctrlr_get_num_ns(ctrlr); + /* Only register the first namespace. */ + if (num_ns < 1) { + fprintf(stderr, "controller found with no namespaces\n"); + exit(1); + } + + register_ns(ctrlr, spdk_nvme_ctrlr_get_ns(ctrlr, 1)); +} + +#if HAVE_LIBAIO +static int +register_aio_file(const char *path) +{ + struct ns_entry *entry; + + int fd; + uint64_t size; + uint32_t blklen; + + fd = open(path, O_RDWR | O_DIRECT); + if (fd < 0) { + fprintf(stderr, "Could not open AIO device %s: %s\n", path, strerror(errno)); + return -1; + } + + size = spdk_fd_get_size(fd); + if (size == 0) { + fprintf(stderr, "Could not determine size of AIO device %s\n", path); + close(fd); + return -1; + } + + blklen = spdk_fd_get_blocklen(fd); + if (blklen == 0) { + fprintf(stderr, "Could not determine block size of AIO device %s\n", path); + close(fd); + return -1; + } + + entry = calloc(1, sizeof(struct ns_entry)); + if (entry == NULL) { + close(fd); + perror("aio ns_entry malloc"); + return -1; + } + + entry->type = ENTRY_TYPE_AIO_FILE; + entry->u.aio.fd = fd; + entry->size_in_ios = size / g_io_size_bytes; + entry->io_size_blocks = g_io_size_bytes / blklen; + entry->submit_histogram = spdk_histogram_data_alloc(); + entry->complete_histogram = spdk_histogram_data_alloc(); + + snprintf(entry->name, sizeof(entry->name), "%s", path); + + g_ns = entry; + + return 0; +} + +static int +aio_submit(io_context_t aio_ctx, struct iocb *iocb, int fd, enum io_iocb_cmd cmd, void *buf, + unsigned long nbytes, uint64_t offset, void *cb_ctx) +{ + iocb->aio_fildes = fd; + iocb->aio_reqprio = 0; + iocb->aio_lio_opcode = cmd; + iocb->u.c.buf = buf; + iocb->u.c.nbytes = nbytes; + iocb->u.c.offset = offset; + iocb->data = cb_ctx; + + if (io_submit(aio_ctx, 1, &iocb) < 0) { + printf("io_submit"); + return -1; + } + + return 0; +} + +static void +aio_check_io(void) +{ + int count, i; + struct timespec timeout; + + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + + count = io_getevents(g_ns->u.aio.ctx, 1, 1, g_ns->u.aio.events, &timeout); + if (count < 0) { + fprintf(stderr, "io_getevents error\n"); + exit(1); + } + + for (i = 0; i < count; i++) { + g_ns->current_queue_depth--; + } +} +#endif /* HAVE_LIBAIO */ + +static void io_complete(void *ctx, const struct spdk_nvme_cpl *completion); + +static __thread unsigned int seed = 0; + +static void +submit_single_io(void) +{ + uint64_t offset_in_ios; + uint64_t start; + int rc; + struct ns_entry *entry = g_ns; + uint64_t tsc_submit; + + offset_in_ios = rand_r(&seed) % entry->size_in_ios; + + start = spdk_get_ticks(); + spdk_rmb(); +#if HAVE_LIBAIO + if (entry->type == ENTRY_TYPE_AIO_FILE) { + rc = aio_submit(g_ns->u.aio.ctx, &g_task->iocb, entry->u.aio.fd, IO_CMD_PREAD, g_task->buf, + g_io_size_bytes, offset_in_ios * g_io_size_bytes, g_task); + } else +#endif + { + rc = spdk_nvme_ns_cmd_read(entry->u.nvme.ns, g_ns->u.nvme.qpair, g_task->buf, + offset_in_ios * entry->io_size_blocks, + entry->io_size_blocks, io_complete, g_task, 0); + } + + spdk_rmb(); + tsc_submit = spdk_get_ticks() - start; + g_tsc_submit += tsc_submit; + if (tsc_submit < g_tsc_submit_min) { + g_tsc_submit_min = tsc_submit; + } + if (tsc_submit > g_tsc_submit_max) { + g_tsc_submit_max = tsc_submit; + } + if (g_enable_histogram) { + spdk_histogram_data_tally(entry->submit_histogram, tsc_submit); + } + + if (rc != 0) { + fprintf(stderr, "starting I/O failed\n"); + } + + g_ns->current_queue_depth++; +} + +static void +io_complete(void *ctx, const struct spdk_nvme_cpl *completion) +{ + g_ns->current_queue_depth--; +} + +uint64_t g_complete_tsc_start; + +static uint64_t +check_io(void) +{ + uint64_t end, tsc_complete; + + spdk_rmb(); +#if HAVE_LIBAIO + if (g_ns->type == ENTRY_TYPE_AIO_FILE) { + aio_check_io(); + } else +#endif + { + spdk_nvme_qpair_process_completions(g_ns->u.nvme.qpair, 0); + } + spdk_rmb(); + end = spdk_get_ticks(); + if (g_ns->current_queue_depth == 1) { + /* + * Account for race condition in AIO case where interrupt occurs + * after checking for queue depth. If the timestamp capture + * is too big compared to the last capture, assume that an + * interrupt fired, and do not bump the start tsc forward. This + * will ensure this extra time is accounted for next time through + * when we see current_queue_depth drop to 0. + */ + if (g_ns->type == ENTRY_TYPE_NVME_NS || (end - g_complete_tsc_start) < 500) { + g_complete_tsc_start = end; + } + } else { + tsc_complete = end - g_complete_tsc_start; + g_tsc_complete += tsc_complete; + if (tsc_complete < g_tsc_complete_min) { + g_tsc_complete_min = tsc_complete; + } + if (tsc_complete > g_tsc_complete_max) { + g_tsc_complete_max = tsc_complete; + } + if (g_enable_histogram) { + spdk_histogram_data_tally(g_ns->complete_histogram, tsc_complete); + } + g_io_completed++; + if (!g_ns->is_draining) { + submit_single_io(); + } + end = g_complete_tsc_start = spdk_get_ticks(); + } + + return end; +} + +static void +drain_io(void) +{ + g_ns->is_draining = true; + while (g_ns->current_queue_depth > 0) { + check_io(); + } +} + +static int +init_ns_worker_ctx(void) +{ + if (g_ns->type == ENTRY_TYPE_AIO_FILE) { +#ifdef HAVE_LIBAIO + g_ns->u.aio.events = calloc(1, sizeof(struct io_event)); + if (!g_ns->u.aio.events) { + return -1; + } + g_ns->u.aio.ctx = 0; + if (io_setup(1, &g_ns->u.aio.ctx) < 0) { + free(g_ns->u.aio.events); + perror("io_setup"); + return -1; + } +#endif + } else { + /* + * TODO: If a controller has multiple namespaces, they could all use the same queue. + * For now, give each namespace/thread combination its own queue. + */ + g_ns->u.nvme.qpair = spdk_nvme_ctrlr_alloc_io_qpair(g_ns->u.nvme.ctrlr, NULL, 0); + if (!g_ns->u.nvme.qpair) { + printf("ERROR: spdk_nvme_ctrlr_alloc_io_qpair failed\n"); + return -1; + } + } + + return 0; +} + +static void +cleanup_ns_worker_ctx(void) +{ + if (g_ns->type == ENTRY_TYPE_AIO_FILE) { +#ifdef HAVE_LIBAIO + io_destroy(g_ns->u.aio.ctx); + free(g_ns->u.aio.events); +#endif + } else { + spdk_nvme_ctrlr_free_io_qpair(g_ns->u.nvme.qpair); + } +} + +static int +work_fn(void) +{ + uint64_t tsc_end, current; + + /* Allocate a queue pair for each namespace. */ + if (init_ns_worker_ctx() != 0) { + printf("ERROR: init_ns_worker_ctx() failed\n"); + return 1; + } + + tsc_end = spdk_get_ticks() + g_time_in_sec * g_tsc_rate; + + /* Submit initial I/O for each namespace. */ + submit_single_io(); + g_complete_tsc_start = spdk_get_ticks(); + + while (1) { + /* + * Check for completed I/O for each controller. A new + * I/O will be submitted in the io_complete callback + * to replace each I/O that is completed. + */ + current = check_io(); + + if (current > tsc_end) { + break; + } + } + + drain_io(); + cleanup_ns_worker_ctx(); + + return 0; +} + +static void usage(char *program_name) +{ + printf("%s options", program_name); +#if HAVE_LIBAIO + printf(" [AIO device(s)]..."); +#endif + printf("\n"); + printf("\t[-s io size in bytes]\n"); + printf("\t[-t time in seconds]\n"); + printf("\t\t(default: 1)]\n"); + printf("\t[-H enable histograms]\n"); +} + +static void +print_bucket(void *ctx, uint64_t start, uint64_t end, uint64_t count, + uint64_t total, uint64_t so_far) +{ + double so_far_pct; + + if (count == 0) { + return; + } + + so_far_pct = (double)so_far * 100 / total; + + printf("%9.3f - %9.3f: %9.4f%% (%9ju)\n", + (double)start * 1000 * 1000 / g_tsc_rate, + (double)end * 1000 * 1000 / g_tsc_rate, + so_far_pct, count); +} + +static void +print_stats(void) +{ + double divisor = (double)g_tsc_rate / (1000 * 1000 * 1000); + + printf("submit (in ns) avg, min, max = %8.1f, %8.1f, %8.1f\n", + (double)g_tsc_submit / g_io_completed / divisor, + (double)g_tsc_submit_min / divisor, + (double)g_tsc_submit_max / divisor); + printf("complete (in ns) avg, min, max = %8.1f, %8.1f, %8.1f\n", + (double)g_tsc_complete / g_io_completed / divisor, + (double)g_tsc_complete_min / divisor, + (double)g_tsc_complete_max / divisor); + + if (!g_enable_histogram) { + return; + } + + printf("\n"); + printf("Submit histogram\n"); + printf("================\n"); + printf(" Range in us Cumulative Count\n"); + spdk_histogram_data_iterate(g_ns->submit_histogram, print_bucket, NULL); + printf("\n"); + + printf("Complete histogram\n"); + printf("==================\n"); + printf(" Range in us Cumulative Count\n"); + spdk_histogram_data_iterate(g_ns->complete_histogram, print_bucket, NULL); + printf("\n"); + +} + +static int +parse_args(int argc, char **argv) +{ + int op; + + /* default value */ + g_io_size_bytes = 0; + g_time_in_sec = 0; + + while ((op = getopt(argc, argv, "hs:t:H")) != -1) { + switch (op) { + case 'h': + usage(argv[0]); + exit(0); + break; + case 's': + g_io_size_bytes = atoi(optarg); + break; + case 't': + g_time_in_sec = atoi(optarg); + break; + case 'H': + g_enable_histogram = true; + break; + default: + usage(argv[0]); + return 1; + } + } + + if (!g_io_size_bytes) { + usage(argv[0]); + return 1; + } + if (!g_time_in_sec) { + usage(argv[0]); + return 1; + } + + g_aio_optind = optind; + + return 0; +} + +static bool +probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr_opts *opts) +{ + static uint32_t ctrlr_found = 0; + + if (ctrlr_found == 1) { + fprintf(stderr, "only attaching to one controller, so skipping\n"); + fprintf(stderr, " controller at PCI address %s\n", + trid->traddr); + return false; + } + ctrlr_found = 1; + + printf("Attaching to %s\n", trid->traddr); + + return true; +} + +static void +attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ + printf("Attached to %s\n", trid->traddr); + + register_ctrlr(ctrlr); +} + +static int +register_controllers(void) +{ + printf("Initializing NVMe Controllers\n"); + + if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) { + fprintf(stderr, "spdk_nvme_probe() failed\n"); + return 1; + } + + if (g_ns == NULL) { + fprintf(stderr, "no NVMe controller found - check that device is bound to uio/vfio\n"); + return 1; + } + + return 0; +} + +static void +cleanup(void) +{ + struct ns_entry *ns_entry = g_ns; + struct ctrlr_entry *ctrlr_entry = g_ctrlr; + + while (ns_entry) { + struct ns_entry *next = ns_entry->next; + + spdk_histogram_data_free(ns_entry->submit_histogram); + spdk_histogram_data_free(ns_entry->complete_histogram); + free(ns_entry); + ns_entry = next; + } + + while (ctrlr_entry) { + struct ctrlr_entry *next = ctrlr_entry->next; + + spdk_nvme_detach(ctrlr_entry->ctrlr); + free(ctrlr_entry); + ctrlr_entry = next; + } +} + +int main(int argc, char **argv) +{ + int rc; + struct spdk_env_opts opts; + + rc = parse_args(argc, argv); + if (rc != 0) { + return rc; + } + + spdk_env_opts_init(&opts); + opts.name = "overhead"; + opts.core_mask = "0x1"; + opts.shm_id = 0; + if (spdk_env_init(&opts) < 0) { + fprintf(stderr, "Unable to initialize SPDK env\n"); + return 1; + } + + g_task = spdk_dma_zmalloc(sizeof(struct perf_task), 0, NULL); + if (g_task == NULL) { + fprintf(stderr, "g_task alloc failed\n"); + exit(1); + } + + g_task->buf = spdk_dma_zmalloc(g_io_size_bytes, 0x1000, NULL); + if (g_task->buf == NULL) { + fprintf(stderr, "g_task->buf spdk_dma_zmalloc failed\n"); + exit(1); + } + + g_tsc_rate = spdk_get_ticks_hz(); + +#if HAVE_LIBAIO + if (g_aio_optind < argc) { + printf("Measuring overhead for AIO device %s.\n", argv[g_aio_optind]); + if (register_aio_file(argv[g_aio_optind]) != 0) { + cleanup(); + return -1; + } + } else +#endif + { + if (register_controllers() != 0) { + cleanup(); + return -1; + } + } + + printf("Initialization complete. Launching workers.\n"); + + rc = work_fn(); + + print_stats(); + + cleanup(); + + if (rc != 0) { + fprintf(stderr, "%s: errors occured\n", argv[0]); + } + + return rc; +} diff --git a/src/spdk/test/nvme/reset/.gitignore b/src/spdk/test/nvme/reset/.gitignore new file mode 100644 index 00000000..a16781b1 --- /dev/null +++ b/src/spdk/test/nvme/reset/.gitignore @@ -0,0 +1 @@ +reset diff --git a/src/spdk/test/nvme/reset/Makefile b/src/spdk/test/nvme/reset/Makefile new file mode 100644 index 00000000..440f385c --- /dev/null +++ b/src/spdk/test/nvme/reset/Makefile @@ -0,0 +1,39 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = reset + +include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk diff --git a/src/spdk/test/nvme/reset/reset.c b/src/spdk/test/nvme/reset/reset.c new file mode 100644 index 00000000..fe2004e8 --- /dev/null +++ b/src/spdk/test/nvme/reset/reset.c @@ -0,0 +1,689 @@ +/*- + * 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/nvme.h" +#include "spdk/env.h" +#include "spdk/string.h" + +struct ctrlr_entry { + struct spdk_nvme_ctrlr *ctrlr; + struct ctrlr_entry *next; + char name[1024]; +}; + +struct ns_entry { + struct spdk_nvme_ns *ns; + struct spdk_nvme_ctrlr *ctrlr; + struct ns_entry *next; + uint32_t io_size_blocks; + uint64_t size_in_ios; + char name[1024]; +}; + +struct ns_worker_ctx { + struct ns_entry *entry; + struct spdk_nvme_qpair *qpair; + uint64_t io_completed; + uint64_t io_completed_error; + uint64_t io_submitted; + uint64_t current_queue_depth; + uint64_t offset_in_ios; + bool is_draining; + + struct ns_worker_ctx *next; +}; + +struct reset_task { + struct ns_worker_ctx *ns_ctx; + void *buf; +}; + +struct worker_thread { + struct ns_worker_ctx *ns_ctx; + unsigned lcore; +}; + +static struct spdk_mempool *task_pool; + +static struct ctrlr_entry *g_controllers = NULL; +static struct ns_entry *g_namespaces = NULL; +static int g_num_namespaces = 0; +static struct worker_thread *g_workers = NULL; + +static uint64_t g_tsc_rate; + +static int g_io_size_bytes; +static int g_rw_percentage; +static int g_is_random; +static int g_queue_depth; +static int g_time_in_sec; + +#define TASK_POOL_NUM 8192 + +static void +register_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns *ns) +{ + struct ns_entry *entry; + const struct spdk_nvme_ctrlr_data *cdata; + + if (!spdk_nvme_ns_is_active(ns)) { + printf("Skipping inactive NS %u\n", spdk_nvme_ns_get_id(ns)); + return; + } + + entry = malloc(sizeof(struct ns_entry)); + if (entry == NULL) { + perror("ns_entry malloc"); + exit(1); + } + + cdata = spdk_nvme_ctrlr_get_data(ctrlr); + + entry->ns = ns; + entry->ctrlr = ctrlr; + entry->size_in_ios = spdk_nvme_ns_get_size(ns) / + g_io_size_bytes; + entry->io_size_blocks = g_io_size_bytes / spdk_nvme_ns_get_sector_size(ns); + + snprintf(entry->name, 44, "%-20.20s (%-20.20s)", cdata->mn, cdata->sn); + + g_num_namespaces++; + entry->next = g_namespaces; + g_namespaces = entry; +} + +static void +register_ctrlr(struct spdk_nvme_ctrlr *ctrlr) +{ + int nsid, num_ns; + struct spdk_nvme_ns *ns; + struct ctrlr_entry *entry = malloc(sizeof(struct ctrlr_entry)); + + if (entry == NULL) { + perror("ctrlr_entry malloc"); + exit(1); + } + + entry->ctrlr = ctrlr; + entry->next = g_controllers; + g_controllers = entry; + + num_ns = spdk_nvme_ctrlr_get_num_ns(ctrlr); + for (nsid = 1; nsid <= num_ns; nsid++) { + ns = spdk_nvme_ctrlr_get_ns(ctrlr, nsid); + if (ns == NULL) { + continue; + } + register_ns(ctrlr, ns); + } +} + +static void io_complete(void *ctx, const struct spdk_nvme_cpl *completion); + +static __thread unsigned int seed = 0; + +static void +submit_single_io(struct ns_worker_ctx *ns_ctx) +{ + struct reset_task *task = NULL; + uint64_t offset_in_ios; + int rc; + struct ns_entry *entry = ns_ctx->entry; + + task = spdk_mempool_get(task_pool); + if (!task) { + fprintf(stderr, "Failed to get task from task_pool\n"); + exit(1); + } + + task->buf = spdk_dma_zmalloc(g_io_size_bytes, 0x200, NULL); + if (!task->buf) { + spdk_dma_free(task->buf); + fprintf(stderr, "task->buf spdk_dma_zmalloc failed\n"); + exit(1); + } + + task->ns_ctx = ns_ctx; + task->ns_ctx->io_submitted++; + + if (g_is_random) { + offset_in_ios = rand_r(&seed) % entry->size_in_ios; + } else { + offset_in_ios = ns_ctx->offset_in_ios++; + if (ns_ctx->offset_in_ios == entry->size_in_ios) { + ns_ctx->offset_in_ios = 0; + } + } + + if ((g_rw_percentage == 100) || + (g_rw_percentage != 0 && ((rand_r(&seed) % 100) < g_rw_percentage))) { + rc = spdk_nvme_ns_cmd_read(entry->ns, ns_ctx->qpair, task->buf, + offset_in_ios * entry->io_size_blocks, + entry->io_size_blocks, io_complete, task, 0); + } else { + rc = spdk_nvme_ns_cmd_write(entry->ns, ns_ctx->qpair, task->buf, + offset_in_ios * entry->io_size_blocks, + entry->io_size_blocks, io_complete, task, 0); + } + + if (rc != 0) { + fprintf(stderr, "starting I/O failed\n"); + } + + ns_ctx->current_queue_depth++; +} + +static void +task_complete(struct reset_task *task, const struct spdk_nvme_cpl *completion) +{ + struct ns_worker_ctx *ns_ctx; + + ns_ctx = task->ns_ctx; + ns_ctx->current_queue_depth--; + + if (spdk_nvme_cpl_is_error(completion)) { + ns_ctx->io_completed_error++; + } else { + ns_ctx->io_completed++; + } + + spdk_dma_free(task->buf); + spdk_mempool_put(task_pool, task); + + /* + * is_draining indicates when time has expired for the test run + * and we are just waiting for the previously submitted I/O + * to complete. In this case, do not submit a new I/O to replace + * the one just completed. + */ + if (!ns_ctx->is_draining) { + submit_single_io(ns_ctx); + } +} + +static void +io_complete(void *ctx, const struct spdk_nvme_cpl *completion) +{ + task_complete((struct reset_task *)ctx, completion); +} + +static void +check_io(struct ns_worker_ctx *ns_ctx) +{ + spdk_nvme_qpair_process_completions(ns_ctx->qpair, 0); +} + +static void +submit_io(struct ns_worker_ctx *ns_ctx, int queue_depth) +{ + while (queue_depth-- > 0) { + submit_single_io(ns_ctx); + } +} + +static void +drain_io(struct ns_worker_ctx *ns_ctx) +{ + ns_ctx->is_draining = true; + while (ns_ctx->current_queue_depth > 0) { + check_io(ns_ctx); + } +} + +static int +work_fn(void *arg) +{ + uint64_t tsc_end = spdk_get_ticks() + g_time_in_sec * g_tsc_rate; + struct worker_thread *worker = (struct worker_thread *)arg; + struct ns_worker_ctx *ns_ctx = NULL; + bool did_reset = false; + + printf("Starting thread on core %u\n", worker->lcore); + + /* Submit initial I/O for each namespace. */ + ns_ctx = worker->ns_ctx; + while (ns_ctx != NULL) { + ns_ctx->qpair = spdk_nvme_ctrlr_alloc_io_qpair(ns_ctx->entry->ctrlr, NULL, 0); + if (ns_ctx->qpair == NULL) { + fprintf(stderr, "spdk_nvme_ctrlr_alloc_io_qpair() failed on core %u\n", worker->lcore); + return -1; + } + submit_io(ns_ctx, g_queue_depth); + ns_ctx = ns_ctx->next; + } + + while (1) { + /* + * Check for completed I/O for each controller. A new + * I/O will be submitted in the io_complete callback + * to replace each I/O that is completed. + */ + ns_ctx = worker->ns_ctx; + while (ns_ctx != NULL) { + check_io(ns_ctx); + ns_ctx = ns_ctx->next; + } + + if (!did_reset && ((tsc_end - spdk_get_ticks()) / g_tsc_rate) > (uint64_t)g_time_in_sec / 2) { + ns_ctx = worker->ns_ctx; + while (ns_ctx != NULL) { + if (spdk_nvme_ctrlr_reset(ns_ctx->entry->ctrlr) < 0) { + fprintf(stderr, "nvme reset failed.\n"); + return -1; + } + ns_ctx = ns_ctx->next; + } + did_reset = true; + } + + if (spdk_get_ticks() > tsc_end) { + break; + } + } + + ns_ctx = worker->ns_ctx; + while (ns_ctx != NULL) { + drain_io(ns_ctx); + spdk_nvme_ctrlr_free_io_qpair(ns_ctx->qpair); + ns_ctx = ns_ctx->next; + } + + return 0; +} + +static void usage(char *program_name) +{ + printf("%s options", program_name); + printf("\n"); + printf("\t[-q io depth]\n"); + printf("\t[-s io size in bytes]\n"); + printf("\t[-w io pattern type, must be one of\n"); + printf("\t\t(read, write, randread, randwrite, rw, randrw)]\n"); + printf("\t[-M rwmixread (100 for reads, 0 for writes)]\n"); + printf("\t[-t time in seconds(should be larger than 15 seconds)]\n"); + printf("\t[-m max completions per poll]\n"); + printf("\t\t(default:0 - unlimited)\n"); +} + +static int +print_stats(void) +{ + uint64_t io_completed, io_submitted, io_completed_error; + uint64_t total_completed_io, total_submitted_io, total_completed_err_io; + struct worker_thread *worker; + struct ns_worker_ctx *ns_ctx; + + total_completed_io = 0; + total_submitted_io = 0; + total_completed_err_io = 0; + + worker = g_workers; + ns_ctx = worker->ns_ctx; + while (ns_ctx) { + io_completed = ns_ctx->io_completed; + io_submitted = ns_ctx->io_submitted; + io_completed_error = ns_ctx->io_completed_error; + total_completed_io += io_completed; + total_submitted_io += io_submitted; + total_completed_err_io += io_completed_error; + ns_ctx = ns_ctx->next; + } + + printf("========================================================\n"); + printf("%16lu IO completed successfully\n", total_completed_io); + printf("%16lu IO completed with error\n", total_completed_err_io); + printf("--------------------------------------------------------\n"); + printf("%16lu IO completed total\n", total_completed_io + total_completed_err_io); + printf("%16lu IO submitted\n", total_submitted_io); + + if (total_submitted_io != (total_completed_io + total_completed_err_io)) { + fprintf(stderr, "Some IO are missing......\n"); + return -1; + } + + return 0; +} + +static int +parse_args(int argc, char **argv) +{ + const char *workload_type; + int op; + bool mix_specified = false; + + /* default value */ + g_queue_depth = 0; + g_io_size_bytes = 0; + workload_type = NULL; + g_time_in_sec = 0; + g_rw_percentage = -1; + + while ((op = getopt(argc, argv, "m:q:s:t:w:M:")) != -1) { + switch (op) { + case 'q': + g_queue_depth = atoi(optarg); + break; + case 's': + g_io_size_bytes = atoi(optarg); + break; + case 't': + g_time_in_sec = atoi(optarg); + break; + case 'w': + workload_type = optarg; + break; + case 'M': + g_rw_percentage = atoi(optarg); + mix_specified = true; + break; + default: + usage(argv[0]); + return 1; + } + } + + if (!g_queue_depth) { + usage(argv[0]); + return 1; + } + if (!g_io_size_bytes) { + usage(argv[0]); + return 1; + } + if (!workload_type) { + usage(argv[0]); + return 1; + } + if (!g_time_in_sec) { + usage(argv[0]); + return 1; + } + + if (strcmp(workload_type, "read") && + strcmp(workload_type, "write") && + strcmp(workload_type, "randread") && + strcmp(workload_type, "randwrite") && + strcmp(workload_type, "rw") && + strcmp(workload_type, "randrw")) { + fprintf(stderr, + "io pattern type must be one of\n" + "(read, write, randread, randwrite, rw, randrw)\n"); + return 1; + } + + if (!strcmp(workload_type, "read") || + !strcmp(workload_type, "randread")) { + g_rw_percentage = 100; + } + + if (!strcmp(workload_type, "write") || + !strcmp(workload_type, "randwrite")) { + g_rw_percentage = 0; + } + + if (!strcmp(workload_type, "read") || + !strcmp(workload_type, "randread") || + !strcmp(workload_type, "write") || + !strcmp(workload_type, "randwrite")) { + if (mix_specified) { + fprintf(stderr, "Ignoring -M option... Please use -M option" + " only when using rw or randrw.\n"); + } + } + + if (!strcmp(workload_type, "rw") || + !strcmp(workload_type, "randrw")) { + if (g_rw_percentage < 0 || g_rw_percentage > 100) { + fprintf(stderr, + "-M must be specified to value from 0 to 100 " + "for rw or randrw.\n"); + return 1; + } + } + + if (!strcmp(workload_type, "read") || + !strcmp(workload_type, "write") || + !strcmp(workload_type, "rw")) { + g_is_random = 0; + } else { + g_is_random = 1; + } + + return 0; +} + +static int +register_workers(void) +{ + struct worker_thread *worker; + + worker = malloc(sizeof(struct worker_thread)); + if (worker == NULL) { + perror("worker_thread malloc"); + return -1; + } + + memset(worker, 0, sizeof(struct worker_thread)); + worker->lcore = spdk_env_get_current_core(); + + g_workers = worker; + + return 0; +} + + +static bool +probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr_opts *opts) +{ + return true; +} + +static void +attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ + register_ctrlr(ctrlr); +} + +static int +register_controllers(void) +{ + printf("Initializing NVMe Controllers\n"); + + if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) { + fprintf(stderr, "spdk_nvme_probe() failed\n"); + return 1; + } + + return 0; +} + +static void +unregister_controllers(void) +{ + struct ctrlr_entry *entry = g_controllers; + + while (entry) { + struct ctrlr_entry *next = entry->next; + spdk_nvme_detach(entry->ctrlr); + free(entry); + entry = next; + } +} + +static int +associate_workers_with_ns(void) +{ + struct ns_entry *entry = g_namespaces; + struct worker_thread *worker = g_workers; + struct ns_worker_ctx *ns_ctx; + int i, count; + + count = g_num_namespaces; + + for (i = 0; i < count; i++) { + if (entry == NULL) { + break; + } + ns_ctx = malloc(sizeof(struct ns_worker_ctx)); + if (!ns_ctx) { + return -1; + } + memset(ns_ctx, 0, sizeof(*ns_ctx)); + + printf("Associating %s with lcore %d\n", entry->name, worker->lcore); + ns_ctx->entry = entry; + ns_ctx->next = worker->ns_ctx; + worker->ns_ctx = ns_ctx; + + worker = g_workers; + + entry = entry->next; + if (entry == NULL) { + entry = g_namespaces; + } + } + + return 0; +} + +static int +run_nvme_reset_cycle(int retry_count) +{ + struct worker_thread *worker; + struct ns_worker_ctx *ns_ctx; + + spdk_nvme_retry_count = retry_count; + + if (work_fn(g_workers) != 0) { + return -1; + } + + if (print_stats() != 0) { + return -1; + } + + worker = g_workers; + ns_ctx = worker->ns_ctx; + while (ns_ctx != NULL) { + ns_ctx->io_completed = 0; + ns_ctx->io_completed_error = 0; + ns_ctx->io_submitted = 0; + ns_ctx->is_draining = false; + ns_ctx = ns_ctx->next; + } + + return 0; +} + +static void +spdk_reset_free_tasks(void) +{ + if (spdk_mempool_count(task_pool) != TASK_POOL_NUM) { + fprintf(stderr, "task_pool count is %zu but should be %d\n", + spdk_mempool_count(task_pool), TASK_POOL_NUM); + } + spdk_mempool_free(task_pool); +} + +int main(int argc, char **argv) +{ + int rc; + int i; + struct spdk_env_opts opts; + + + rc = parse_args(argc, argv); + if (rc != 0) { + return rc; + } + + spdk_env_opts_init(&opts); + opts.name = "reset"; + opts.core_mask = "0x1"; + opts.shm_id = 0; + if (spdk_env_init(&opts) < 0) { + fprintf(stderr, "Unable to initialize SPDK env\n"); + return 1; + } + + if (register_controllers() != 0) { + return 1; + } + + if (!g_controllers) { + printf("No NVMe controller found, %s exiting\n", argv[0]); + return 1; + } + + task_pool = spdk_mempool_create("task_pool", TASK_POOL_NUM, + sizeof(struct reset_task), + 64, SPDK_ENV_SOCKET_ID_ANY); + if (!task_pool) { + fprintf(stderr, "Cannot create task pool\n"); + return 1; + } + + g_tsc_rate = spdk_get_ticks_hz(); + + if (register_workers() != 0) { + return 1; + } + + if (associate_workers_with_ns() != 0) { + rc = 1; + goto cleanup; + } + + printf("Initialization complete. Launching workers.\n"); + + for (i = 2; i >= 0; i--) { + rc = run_nvme_reset_cycle(i); + if (rc != 0) { + goto cleanup; + } + } + +cleanup: + unregister_controllers(); + spdk_reset_free_tasks(); + + if (rc != 0) { + fprintf(stderr, "%s: errors occured\n", argv[0]); + } + + return rc; +} diff --git a/src/spdk/test/nvme/sgl/.gitignore b/src/spdk/test/nvme/sgl/.gitignore new file mode 100644 index 00000000..d1cebd68 --- /dev/null +++ b/src/spdk/test/nvme/sgl/.gitignore @@ -0,0 +1 @@ +sgl diff --git a/src/spdk/test/nvme/sgl/Makefile b/src/spdk/test/nvme/sgl/Makefile new file mode 100644 index 00000000..f0e0fc50 --- /dev/null +++ b/src/spdk/test/nvme/sgl/Makefile @@ -0,0 +1,39 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = sgl + +include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk diff --git a/src/spdk/test/nvme/sgl/sgl.c b/src/spdk/test/nvme/sgl/sgl.c new file mode 100644 index 00000000..ccff652c --- /dev/null +++ b/src/spdk/test/nvme/sgl/sgl.c @@ -0,0 +1,542 @@ +/*- + * 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/nvme.h" +#include "spdk/env.h" +#include "spdk/util.h" + +#define MAX_DEVS 64 + +#define MAX_IOVS 128 + +#define DATA_PATTERN 0x5A + +#define BASE_LBA_START 0x100000 + +struct dev { + struct spdk_nvme_ctrlr *ctrlr; + char name[SPDK_NVMF_TRADDR_MAX_LEN + 1]; +}; + +static struct dev devs[MAX_DEVS]; +static int num_devs = 0; + +#define foreach_dev(iter) \ + for (iter = devs; iter - devs < num_devs; iter++) + +static int io_complete_flag = 0; + +struct sgl_element { + void *base; + size_t offset; + size_t len; +}; + +struct io_request { + uint32_t current_iov_index; + uint32_t current_iov_bytes_left; + struct sgl_element iovs[MAX_IOVS]; + uint32_t nseg; + uint32_t misalign; +}; + +static void nvme_request_reset_sgl(void *cb_arg, uint32_t sgl_offset) +{ + uint32_t i; + uint32_t offset = 0; + struct sgl_element *iov; + struct io_request *req = (struct io_request *)cb_arg; + + for (i = 0; i < req->nseg; i++) { + iov = &req->iovs[i]; + offset += iov->len; + if (offset > sgl_offset) { + break; + } + } + req->current_iov_index = i; + req->current_iov_bytes_left = offset - sgl_offset; + return; +} + +static int nvme_request_next_sge(void *cb_arg, void **address, uint32_t *length) +{ + struct io_request *req = (struct io_request *)cb_arg; + struct sgl_element *iov; + + if (req->current_iov_index >= req->nseg) { + *length = 0; + *address = NULL; + return 0; + } + + iov = &req->iovs[req->current_iov_index]; + + if (req->current_iov_bytes_left) { + *address = iov->base + iov->offset + iov->len - req->current_iov_bytes_left; + *length = req->current_iov_bytes_left; + req->current_iov_bytes_left = 0; + } else { + *address = iov->base + iov->offset; + *length = iov->len; + } + + req->current_iov_index++; + + return 0; +} + +static void +io_complete(void *ctx, const struct spdk_nvme_cpl *cpl) +{ + if (spdk_nvme_cpl_is_error(cpl)) { + io_complete_flag = 2; + } else { + io_complete_flag = 1; + } +} + +static void build_io_request_0(struct io_request *req) +{ + req->nseg = 1; + + req->iovs[0].base = spdk_dma_zmalloc(0x800, 4, NULL); + req->iovs[0].len = 0x800; +} + +static void build_io_request_1(struct io_request *req) +{ + req->nseg = 1; + + /* 512B for 1st sge */ + req->iovs[0].base = spdk_dma_zmalloc(0x200, 0x200, NULL); + req->iovs[0].len = 0x200; +} + +static void build_io_request_2(struct io_request *req) +{ + req->nseg = 1; + + /* 256KB for 1st sge */ + req->iovs[0].base = spdk_dma_zmalloc(0x40000, 0x1000, NULL); + req->iovs[0].len = 0x40000; +} + +static void build_io_request_3(struct io_request *req) +{ + req->nseg = 3; + + /* 2KB for 1st sge, make sure the iov address start at 0x800 boundary, + * and end with 0x1000 boundary */ + req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL); + req->iovs[0].offset = 0x800; + req->iovs[0].len = 0x800; + + /* 4KB for 2th sge */ + req->iovs[1].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL); + req->iovs[1].len = 0x1000; + + /* 12KB for 3th sge */ + req->iovs[2].base = spdk_dma_zmalloc(0x3000, 0x1000, NULL); + req->iovs[2].len = 0x3000; +} + +static void build_io_request_4(struct io_request *req) +{ + uint32_t i; + + req->nseg = 32; + + /* 4KB for 1st sge */ + req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL); + req->iovs[0].len = 0x1000; + + /* 8KB for the rest 31 sge */ + for (i = 1; i < req->nseg; i++) { + req->iovs[i].base = spdk_dma_zmalloc(0x2000, 0x1000, NULL); + req->iovs[i].len = 0x2000; + } +} + +static void build_io_request_5(struct io_request *req) +{ + req->nseg = 1; + + /* 8KB for 1st sge */ + req->iovs[0].base = spdk_dma_zmalloc(0x2000, 0x1000, NULL); + req->iovs[0].len = 0x2000; +} + +static void build_io_request_6(struct io_request *req) +{ + req->nseg = 2; + + /* 4KB for 1st sge */ + req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL); + req->iovs[0].len = 0x1000; + + /* 4KB for 2st sge */ + req->iovs[1].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL); + req->iovs[1].len = 0x1000; +} + +static void build_io_request_7(struct io_request *req) +{ + uint8_t *base; + + req->nseg = 1; + + /* + * Create a 64KB sge, but ensure it is *not* aligned on a 4KB + * boundary. This is valid for single element buffers with PRP. + */ + base = spdk_dma_zmalloc(0x11000, 0x1000, NULL); + req->misalign = 64; + req->iovs[0].base = base + req->misalign; + req->iovs[0].len = 0x10000; +} + +static void build_io_request_8(struct io_request *req) +{ + req->nseg = 2; + + /* + * 1KB for 1st sge, make sure the iov address does not start and end + * at 0x1000 boundary + */ + req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL); + req->iovs[0].offset = 0x400; + req->iovs[0].len = 0x400; + + /* + * 1KB for 1st sge, make sure the iov address does not start and end + * at 0x1000 boundary + */ + req->iovs[1].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL); + req->iovs[1].offset = 0x400; + req->iovs[1].len = 0x400; +} + +static void build_io_request_9(struct io_request *req) +{ + /* + * Check if mixed PRP complaint and not complaint requests are handled + * properly by splitting them into subrequests. + * Construct buffers with following theme: + */ + const size_t req_len[] = { 2048, 4096, 2048, 4096, 2048, 1024 }; + const size_t req_off[] = { 0x800, 0x0, 0x0, 0x100, 0x800, 0x800 }; + struct sgl_element *iovs = req->iovs; + uint32_t i; + req->nseg = SPDK_COUNTOF(req_len); + assert(SPDK_COUNTOF(req_len) == SPDK_COUNTOF(req_off)); + + for (i = 0; i < req->nseg; i++) { + iovs[i].base = spdk_dma_zmalloc(req_off[i] + req_len[i], 0x4000, NULL); + iovs[i].offset = req_off[i]; + iovs[i].len = req_len[i]; + } +} + +static void build_io_request_10(struct io_request *req) +{ + /* + * Test the case where we have a valid PRP list, but the first and last + * elements are not exact multiples of the logical block size. + */ + const size_t req_len[] = { 4004, 4096, 92 }; + const size_t req_off[] = { 0x5c, 0x0, 0x0 }; + struct sgl_element *iovs = req->iovs; + uint32_t i; + req->nseg = SPDK_COUNTOF(req_len); + assert(SPDK_COUNTOF(req_len) == SPDK_COUNTOF(req_off)); + + for (i = 0; i < req->nseg; i++) { + iovs[i].base = spdk_dma_zmalloc(req_off[i] + req_len[i], 0x4000, NULL); + iovs[i].offset = req_off[i]; + iovs[i].len = req_len[i]; + } +} + +static void build_io_request_11(struct io_request *req) +{ + /* This test case focuses on the last element not starting on a page boundary. */ + const size_t req_len[] = { 512, 512 }; + const size_t req_off[] = { 0xe00, 0x800 }; + struct sgl_element *iovs = req->iovs; + uint32_t i; + req->nseg = SPDK_COUNTOF(req_len); + assert(SPDK_COUNTOF(req_len) == SPDK_COUNTOF(req_off)); + + for (i = 0; i < req->nseg; i++) { + iovs[i].base = spdk_dma_zmalloc(req_off[i] + req_len[i], 0x4000, NULL); + iovs[i].offset = req_off[i]; + iovs[i].len = req_len[i]; + } +} + +typedef void (*nvme_build_io_req_fn_t)(struct io_request *req); + +static void +free_req(struct io_request *req) +{ + uint32_t i; + + if (req == NULL) { + return; + } + + for (i = 0; i < req->nseg; i++) { + spdk_dma_free(req->iovs[i].base - req->misalign); + } + + spdk_dma_free(req); +} + +static int +writev_readv_tests(struct dev *dev, nvme_build_io_req_fn_t build_io_fn, const char *test_name) +{ + int rc = 0; + uint32_t len, lba_count; + uint32_t i, j, nseg, remainder; + char *buf; + + struct io_request *req; + struct spdk_nvme_ns *ns; + struct spdk_nvme_qpair *qpair; + const struct spdk_nvme_ns_data *nsdata; + + ns = spdk_nvme_ctrlr_get_ns(dev->ctrlr, 1); + if (!ns) { + fprintf(stderr, "Null namespace\n"); + return 0; + } + nsdata = spdk_nvme_ns_get_data(ns); + if (!nsdata || !spdk_nvme_ns_get_sector_size(ns)) { + fprintf(stderr, "Empty nsdata or wrong sector size\n"); + return 0; + } + + if (spdk_nvme_ns_get_flags(ns) & SPDK_NVME_NS_DPS_PI_SUPPORTED) { + return 0; + } + + req = spdk_dma_zmalloc(sizeof(*req), 0, NULL); + if (!req) { + fprintf(stderr, "Allocate request failed\n"); + return 0; + } + + /* IO parameters setting */ + build_io_fn(req); + + len = 0; + for (i = 0; i < req->nseg; i++) { + struct sgl_element *sge = &req->iovs[i]; + + len += sge->len; + } + + lba_count = len / spdk_nvme_ns_get_sector_size(ns); + remainder = len % spdk_nvme_ns_get_sector_size(ns); + if (!lba_count || remainder || (BASE_LBA_START + lba_count > (uint32_t)nsdata->nsze)) { + fprintf(stderr, "%s: %s Invalid IO length parameter\n", dev->name, test_name); + free_req(req); + return 0; + } + + qpair = spdk_nvme_ctrlr_alloc_io_qpair(dev->ctrlr, NULL, 0); + if (!qpair) { + free_req(req); + return -1; + } + + nseg = req->nseg; + for (i = 0; i < nseg; i++) { + memset(req->iovs[i].base + req->iovs[i].offset, DATA_PATTERN, req->iovs[i].len); + } + + rc = spdk_nvme_ns_cmd_writev(ns, qpair, BASE_LBA_START, lba_count, + io_complete, req, 0, + nvme_request_reset_sgl, + nvme_request_next_sge); + + if (rc != 0) { + fprintf(stderr, "%s: %s writev failed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return -1; + } + + io_complete_flag = 0; + + while (!io_complete_flag) { + spdk_nvme_qpair_process_completions(qpair, 1); + } + + if (io_complete_flag != 1) { + fprintf(stderr, "%s: %s writev failed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return -1; + } + + /* reset completion flag */ + io_complete_flag = 0; + + for (i = 0; i < nseg; i++) { + memset(req->iovs[i].base + req->iovs[i].offset, 0, req->iovs[i].len); + } + + rc = spdk_nvme_ns_cmd_readv(ns, qpair, BASE_LBA_START, lba_count, + io_complete, req, 0, + nvme_request_reset_sgl, + nvme_request_next_sge); + + if (rc != 0) { + fprintf(stderr, "%s: %s readv failed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return -1; + } + + while (!io_complete_flag) { + spdk_nvme_qpair_process_completions(qpair, 1); + } + + if (io_complete_flag != 1) { + fprintf(stderr, "%s: %s readv failed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return -1; + } + + for (i = 0; i < nseg; i++) { + buf = (char *)req->iovs[i].base + req->iovs[i].offset; + for (j = 0; j < req->iovs[i].len; j++) { + if (buf[j] != DATA_PATTERN) { + fprintf(stderr, "%s: %s write/read success, but memcmp Failed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return -1; + } + } + } + + fprintf(stdout, "%s: %s test passed\n", dev->name, test_name); + spdk_nvme_ctrlr_free_io_qpair(qpair); + free_req(req); + return rc; +} + +static bool +probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr_opts *opts) +{ + printf("Attaching to %s\n", trid->traddr); + + return true; +} + +static void +attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ + struct dev *dev; + + /* add to dev list */ + dev = &devs[num_devs++]; + + dev->ctrlr = ctrlr; + + snprintf(dev->name, sizeof(dev->name), "%s", + trid->traddr); + + printf("Attached to %s\n", dev->name); +} + +int main(int argc, char **argv) +{ + struct dev *iter; + int rc, i; + struct spdk_env_opts opts; + + spdk_env_opts_init(&opts); + opts.name = "nvme_sgl"; + opts.core_mask = "0x1"; + opts.shm_id = 0; + if (spdk_env_init(&opts) < 0) { + fprintf(stderr, "Unable to initialize SPDK env\n"); + return 1; + } + + printf("NVMe Readv/Writev Request test\n"); + + if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) { + fprintf(stderr, "nvme_probe() failed\n"); + exit(1); + } + + rc = 0; + foreach_dev(iter) { +#define TEST(x) writev_readv_tests(iter, x, #x) + if (TEST(build_io_request_0) + || TEST(build_io_request_1) + || TEST(build_io_request_2) + || TEST(build_io_request_3) + || TEST(build_io_request_4) + || TEST(build_io_request_5) + || TEST(build_io_request_6) + || TEST(build_io_request_7) + || TEST(build_io_request_8) + || TEST(build_io_request_9) + || TEST(build_io_request_10) + || TEST(build_io_request_11)) { +#undef TEST + rc = 1; + printf("%s: failed sgl tests\n", iter->name); + } + } + + printf("Cleaning up...\n"); + + for (i = 0; i < num_devs; i++) { + struct dev *dev = &devs[i]; + + spdk_nvme_detach(dev->ctrlr); + } + + return rc; +} diff --git a/src/spdk/test/nvme/spdk_nvme_cli.sh b/src/spdk/test/nvme/spdk_nvme_cli.sh new file mode 100755 index 00000000..0051df1d --- /dev/null +++ b/src/spdk/test/nvme/spdk_nvme_cli.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +set -ex + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) +source $rootdir/scripts/common.sh +source $rootdir/test/common/autotest_common.sh + +if [ -z "${DEPENDENCY_DIR}" ]; then + echo DEPENDENCY_DIR not defined! + exit 1 +fi + +spdk_nvme_cli="${DEPENDENCY_DIR}/nvme-cli" + +if [ ! -d $spdk_nvme_cli ]; then + echo "nvme-cli repository not found at $spdk_nvme_cli; skipping tests." + exit 0 +fi + +timing_enter nvme_cli + +if [ `uname` = Linux ]; then + start_stub "-s 2048 -i 0 -m 0xF" + trap "kill_stub; exit 1" SIGINT SIGTERM EXIT +fi + +# Build against the version of SPDK under test +rm -f "$spdk_nvme_cli/spdk" +ln -sf "$rootdir" "$spdk_nvme_cli/spdk" + +bdfs=$(iter_pci_class_code 01 08 02) +bdf=$(echo $bdfs|awk '{ print $1 }') + +cd $spdk_nvme_cli +make clean && make -j$(nproc) LDFLAGS="$(make -s -C $spdk_nvme_cli/spdk ldflags)" +sed -i 's/spdk=0/spdk=1/g' spdk.conf +sed -i 's/shm_id=1/shm_id=0/g' spdk.conf +./nvme list +./nvme id-ctrl $bdf +./nvme list-ctrl $bdf +./nvme get-ns-id $bdf +./nvme id-ns $bdf +./nvme fw-log $bdf +./nvme smart-log $bdf +./nvme error-log $bdf +./nvme list-ns $bdf -n 1 +./nvme get-feature $bdf -n 1 -f 1 -s 1 -l 100 +./nvme get-log $bdf -n 1 -i 1 -l 100 +./nvme reset $bdf +if [ `uname` = Linux ]; then + trap - SIGINT SIGTERM EXIT + kill_stub +fi + +report_test_completion spdk_nvme_cli +timing_exit nvme_cli diff --git a/src/spdk/test/nvmf/bdev_io_wait/bdev_io_wait.sh b/src/spdk/test/nvmf/bdev_io_wait/bdev_io_wait.sh new file mode 100755 index 00000000..29106bde --- /dev/null +++ b/src/spdk/test/nvmf/bdev_io_wait/bdev_io_wait.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization. +# e.g. sudo ./bdev_io_wait.sh iso +nvmftestinit $1 + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter bdev_io_wait +timing_enter start_nvmf_tgt + +$NVMF_APP -m 0xF --wait-for-rpc & +nvmfpid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $nvmfpid +# Minimal number of bdev io pool (5) and cache (1) +$rpc_py set_bdev_options -p 5 -c 1 +$rpc_py start_subsystem_init +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +modprobe -v nvme-rdma + +bdevs="$bdevs $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 +for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev +done +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 + +echo "[Nvme]" > $testdir/bdevperf.conf +echo " TransportID \"trtype:RDMA adrfam:IPv4 subnqn:nqn.2016-06.io.spdk:cnode1 traddr:$NVMF_FIRST_TARGET_IP trsvcid:4420\" Nvme0" >> $testdir/bdevperf.conf +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w write -t 1 +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w read -t 1 +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w flush -t 1 +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w unmap -t 1 +sync +rm -rf $testdir/bdevperf.conf +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + +trap - SIGINT SIGTERM EXIT + +nvmfcleanup +killprocess $nvmfpid +nvmftestfini $1 +timing_exit bdev_io_wait diff --git a/src/spdk/test/nvmf/common.sh b/src/spdk/test/nvmf/common.sh new file mode 100755 index 00000000..af79e3f7 --- /dev/null +++ b/src/spdk/test/nvmf/common.sh @@ -0,0 +1,200 @@ +#!/usr/bin/env bash + +NVMF_PORT=4420 +NVMF_IP_PREFIX="192.168.100" +NVMF_IP_LEAST_ADDR=8 + +if [ -z "$NVMF_APP" ]; then + NVMF_APP=./app/nvmf_tgt/nvmf_tgt +fi + +if [ -z "$NVMF_TEST_CORE_MASK" ]; then + NVMF_TEST_CORE_MASK=0xFF +fi + +function load_ib_rdma_modules() +{ + if [ `uname` != Linux ]; then + return 0 + fi + + modprobe ib_cm + modprobe ib_core + # Newer kernels do not have the ib_ucm module + modprobe ib_ucm || true + modprobe ib_umad + modprobe ib_uverbs + modprobe iw_cm + modprobe rdma_cm + modprobe rdma_ucm +} + + +function detect_soft_roce_nics() +{ + if hash rxe_cfg; then + rxe_cfg start + rdma_nics=$(get_rdma_if_list) + all_nics=$(ip -o link | awk '{print $2}' | cut -d":" -f1) + non_rdma_nics=$(echo -e "$rdma_nics\n$all_nics" | sort | uniq -u) + for nic in $non_rdma_nics; do + if [[ -d /sys/class/net/${nic}/bridge ]]; then + continue + fi + rxe_cfg add $nic || true + done + fi +} + +function detect_mellanox_nics() +{ + if ! hash lspci; then + echo "No NICs" + return 0 + fi + + nvmf_nic_bdfs=`lspci | grep Ethernet | grep Mellanox | awk -F ' ' '{print "0000:"$1}'` + mlx_core_driver="mlx4_core" + mlx_ib_driver="mlx4_ib" + mlx_en_driver="mlx4_en" + + if [ -z "$nvmf_nic_bdfs" ]; then + echo "No NICs" + return 0 + fi + + # for nvmf target loopback test, suppose we only have one type of card. + for nvmf_nic_bdf in $nvmf_nic_bdfs + do + result=`lspci -vvv -s $nvmf_nic_bdf | grep 'Kernel modules' | awk -F ' ' '{print $3}'` + if [ "$result" == "mlx5_core" ]; then + mlx_core_driver="mlx5_core" + mlx_ib_driver="mlx5_ib" + mlx_en_driver="" + fi + break; + done + + modprobe $mlx_core_driver + modprobe $mlx_ib_driver + if [ -n "$mlx_en_driver" ]; then + modprobe $mlx_en_driver + fi + + # The mlx4 driver takes an extra few seconds to load after modprobe returns, + # otherwise iproute2 operations will do nothing. + sleep 5 +} + +function detect_rdma_nics() +{ + nics=$(detect_mellanox_nics) + if [ "$nics" == "No NICs" ]; then + detect_soft_roce_nics + fi +} + +function allocate_nic_ips() +{ + let count=$NVMF_IP_LEAST_ADDR + for nic_name in $(get_rdma_if_list); do + ip="$(get_ip_address $nic_name)" + if [ -z $ip ]; then + ip addr add $NVMF_IP_PREFIX.$count/24 dev $nic_name + ip link set $nic_name up + let count=$count+1 + fi + # dump configuration for debug log + ip addr show $nic_name + done +} + +function get_available_rdma_ips() +{ + for nic_name in $(get_rdma_if_list); do + get_ip_address $nic_name + done +} + +function get_rdma_if_list() +{ + for nic_type in `ls /sys/class/infiniband`; do + for nic_name in `ls /sys/class/infiniband/${nic_type}/device/net`; do + echo "$nic_name" + done + done +} + +function get_ip_address() +{ + interface=$1 + ip -o -4 addr show $interface | awk '{print $4}' | cut -d"/" -f1 +} + +function nvmfcleanup() +{ + sync + set +e + for i in {1..20}; do + modprobe -v -r nvme-rdma nvme-fabrics + if [ $? -eq 0 ]; then + set -e + return + fi + sleep 1 + done + set -e + + # So far unable to remove the kernel modules. Try + # one more time and let it fail. + modprobe -v -r nvme-rdma nvme-fabrics +} + +function nvmftestinit() +{ + if [ "$1" == "iso" ]; then + $rootdir/scripts/setup.sh + rdma_device_init + fi +} + +function nvmftestfini() +{ + if [ "$1" == "iso" ]; then + $rootdir/scripts/setup.sh reset + rdma_device_init + fi +} + +function rdma_device_init() +{ + load_ib_rdma_modules + detect_rdma_nics + allocate_nic_ips +} + +function revert_soft_roce() +{ + if hash rxe_cfg; then + interfaces="$(ip -o link | awk '{print $2}' | cut -d":" -f1)" + for interface in $interfaces; do + rxe_cfg remove $interface || true + done + rxe_cfg stop || true + fi +} + +function check_ip_is_soft_roce() +{ + IP=$1 + if hash rxe_cfg; then + dev=$(ip -4 -o addr show | grep $IP | cut -d" " -f2) + if rxe_cfg | grep $dev; then + return 0 + else + return 1 + fi + else + return 1 + fi +} diff --git a/src/spdk/test/nvmf/create_transport/create_transport.sh b/src/spdk/test/nvmf/create_transport/create_transport.sh new file mode 100755 index 00000000..3514dbc1 --- /dev/null +++ b/src/spdk/test/nvmf/create_transport/create_transport.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +NULL_BDEV_SIZE=102400 +NULL_BLOCK_SIZE=512 + +rpc_py="python $rootdir/scripts/rpc.py" + +set -e + +# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization. +# e.g. sudo ./crt_trprt.sh iso +nvmftestinit $1 + +if ! hash nvme; then + echo "nvme command not found; skipping create transport test" + exit 0 +fi + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter cr_trprt +timing_enter start_nvmf_tgt +# Start up the NVMf target in another process +$NVMF_APP -m 0xF & +nvmfpid=$! + +trap "killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $nvmfpid +# Use nvmf_create_transport call to create transport +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +null_bdevs="$($rpc_py construct_null_bdev Null0 $NULL_BDEV_SIZE $NULL_BLOCK_SIZE) " +null_bdevs+="$($rpc_py construct_null_bdev Null1 $NULL_BDEV_SIZE $NULL_BLOCK_SIZE)" + +modprobe -v nvme-rdma + +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 +for null_bdev in $null_bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $null_bdev +done +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 + +nvme discover -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT + +echo "Perform nvmf subsystem discovery via RPC" +$rpc_py get_nvmf_subsystems + +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + +for null_bdev in $null_bdevs; do + $rpc_py delete_null_bdev $null_bdev +done + +check_bdevs=$($rpc_py get_bdevs | jq -r '.[].name') +if [ -n "$check_bdevs" ]; then + echo $check_bdevs + exit 1 +fi + +trap - SIGINT SIGTERM EXIT + +nvmfcleanup +killprocess $nvmfpid +nvmftestfini $1 +timing_exit crt_trprt diff --git a/src/spdk/test/nvmf/discovery/discovery.sh b/src/spdk/test/nvmf/discovery/discovery.sh new file mode 100755 index 00000000..f0db8ab3 --- /dev/null +++ b/src/spdk/test/nvmf/discovery/discovery.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +NULL_BDEV_SIZE=102400 +NULL_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization. +# e.g. sudo ./discovery.sh iso +nvmftestinit $1 + +if ! hash nvme; then + echo "nvme command not found; skipping discovery test" + exit 0 +fi + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter discovery +timing_enter start_nvmf_tgt +# Start up the NVMf target in another process +$NVMF_APP -m 0xF & +nvmfpid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $nvmfpid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +null_bdevs="$($rpc_py construct_null_bdev Null0 $NULL_BDEV_SIZE $NULL_BLOCK_SIZE) " +null_bdevs+="$($rpc_py construct_null_bdev Null1 $NULL_BDEV_SIZE $NULL_BLOCK_SIZE)" + +modprobe -v nvme-rdma + +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 +for null_bdev in $null_bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $null_bdev +done +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 + +nvme discover -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT + +echo "Perform nvmf subsystem discovery via RPC" +$rpc_py get_nvmf_subsystems + +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + +for null_bdev in $null_bdevs; do + $rpc_py delete_null_bdev $null_bdev +done + +check_bdevs=$($rpc_py get_bdevs | jq -r '.[].name') +if [ -n "$check_bdevs" ]; then + echo $check_bdevs + exit 1 +fi + +trap - SIGINT SIGTERM EXIT + +nvmfcleanup +killprocess $nvmfpid +nvmftestfini $1 +timing_exit discovery diff --git a/src/spdk/test/nvmf/filesystem/filesystem.sh b/src/spdk/test/nvmf/filesystem/filesystem.sh new file mode 100755 index 00000000..057fc579 --- /dev/null +++ b/src/spdk/test/nvmf/filesystem/filesystem.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization. +# e.g. sudo ./filesystem.sh iso +nvmftestinit $1 + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter fs_test + +for incapsule in 0 4096; do + # Start up the NVMf target in another process + $NVMF_APP -m 0xF & + nvmfpid=$! + + trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT + + waitforlisten $nvmfpid + $rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 -c $incapsule + + bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + bdevs+=" $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + + modprobe -v nvme-rdma + + $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 + for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev + done + $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 + + nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + + waitforblk "nvme0n1" + waitforblk "nvme0n2" + + mkdir -p /mnt/device + + devs=`lsblk -l -o NAME | grep nvme` + + for dev in $devs; do + timing_enter parted + parted -s /dev/$dev mklabel msdos mkpart primary '0%' '100%' + timing_exit parted + sleep 1 + + for fstype in "ext4" "btrfs" "xfs"; do + timing_enter $fstype + if [ $fstype = ext4 ]; then + force=-F + else + force=-f + fi + + mkfs.${fstype} $force /dev/${dev}p1 + + mount /dev/${dev}p1 /mnt/device + touch /mnt/device/aaa + sync + rm /mnt/device/aaa + sync + umount /mnt/device + timing_exit $fstype + done + + parted -s /dev/$dev rm 1 + done + + sync + nvme disconnect -n "nqn.2016-06.io.spdk:cnode1" || true + + $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + + trap - SIGINT SIGTERM EXIT + + nvmfcleanup + killprocess $nvmfpid +done + +nvmftestfini $1 +timing_exit fs_test diff --git a/src/spdk/test/nvmf/fio/fio.sh b/src/spdk/test/nvmf/fio/fio.sh new file mode 100755 index 00000000..ba3b12b3 --- /dev/null +++ b/src/spdk/test/nvmf/fio/fio.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization. +# e.g. sudo ./fio.sh iso +nvmftestinit $1 + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter fio +timing_enter start_nvmf_tgt +# Start up the NVMf target in another process +$NVMF_APP -m 0xF & +nvmfpid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $nvmfpid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 + +timing_exit start_nvmf_tgt + +malloc_bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) " +malloc_bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" +# Create a RAID-0 bdev from two malloc bdevs +raid_malloc_bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) " +raid_malloc_bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" +$rpc_py construct_raid_bdev -n raid0 -s 64 -r 0 -b "$raid_malloc_bdevs" + +modprobe -v nvme-rdma + +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 +for malloc_bdev in $malloc_bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 "$malloc_bdev" +done +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 + +# Append the raid0 bdev into subsystem +$rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 raid0 + +nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + +waitforblk "nvme0n1" +waitforblk "nvme0n2" +waitforblk "nvme0n3" + +$testdir/nvmf_fio.py 4096 1 write 1 verify +$testdir/nvmf_fio.py 4096 1 randwrite 1 verify +$testdir/nvmf_fio.py 4096 128 write 1 verify +$testdir/nvmf_fio.py 4096 128 randwrite 1 verify + +sync + +#start hotplug test case +$testdir/nvmf_fio.py 4096 1 read 10 & +fio_pid=$! + +sleep 3 +set +e + +$rpc_py destroy_raid_bdev "raid0" +for malloc_bdev in $malloc_bdevs; do + $rpc_py delete_malloc_bdev "$malloc_bdev" +done + +wait $fio_pid +fio_status=$? + +nvme disconnect -n "nqn.2016-06.io.spdk:cnode1" || true + +if [ $fio_status -eq 0 ]; then + echo "nvmf hotplug test: fio successful - expected failure" + nvmfcleanup + killprocess $nvmfpid + exit 1 +else + echo "nvmf hotplug test: fio failed as expected" +fi +set -e + +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + +rm -f ./local-job0-0-verify.state +rm -f ./local-job1-1-verify.state +rm -f ./local-job2-2-verify.state + +trap - SIGINT SIGTERM EXIT + +nvmfcleanup +killprocess $nvmfpid +nvmftestfini $1 +timing_exit fio diff --git a/src/spdk/test/nvmf/fio/nvmf_fio.py b/src/spdk/test/nvmf/fio/nvmf_fio.py new file mode 100755 index 00000000..6096dd72 --- /dev/null +++ b/src/spdk/test/nvmf/fio/nvmf_fio.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 + +from subprocess import check_call, call, check_output, Popen, PIPE, CalledProcessError +import re +import sys +import signal + +fio_template = """ +[global] +thread=1 +invalidate=1 +rw=%(testtype)s +time_based=1 +runtime=%(runtime)s +ioengine=libaio +direct=1 +bs=%(blocksize)d +iodepth=%(iodepth)d +%(verify)s +verify_dump=1 + +""" + +verify_template = """ +do_verify=1 +verify=meta +verify_pattern="meta" +""" + + +fio_job_template = """ +[job%(jobnumber)d] +filename=%(device)s + +""" + + +def interrupt_handler(signum, frame): + fio.terminate() + print("FIO terminated") + sys.exit(0) + + +def main(): + + global fio + if (len(sys.argv) < 5): + print("usage:") + print(" " + sys.argv[0] + " <io_size> <queue_depth> <test_type> <runtime>") + print("advanced usage:") + print("If you want to run fio with verify, please add verify string after runtime.") + print("Currently fio.py only support write rw randwrite randrw with verify enabled.") + sys.exit(1) + + io_size = int(sys.argv[1]) + queue_depth = int(sys.argv[2]) + test_type = sys.argv[3] + runtime = sys.argv[4] + if len(sys.argv) > 5: + verify = True + else: + verify = False + + devices = get_target_devices() + print("Found devices: ", devices) + + # configure_devices(devices) + try: + fio_executable = check_output("which fio", shell=True).split()[0] + except CalledProcessError as e: + sys.stderr.write(str(e)) + sys.stderr.write("\nCan't find the fio binary, please install it.\n") + sys.exit(1) + + device_paths = ['/dev/' + dev for dev in devices] + print(device_paths) + sys.stdout.flush() + signal.signal(signal.SIGTERM, interrupt_handler) + signal.signal(signal.SIGINT, interrupt_handler) + fio = Popen([fio_executable, '-'], stdin=PIPE) + fio.communicate(create_fio_config(io_size, queue_depth, device_paths, test_type, runtime, verify)) + fio.stdin.close() + rc = fio.wait() + print("FIO completed with code %d\n" % rc) + sys.stdout.flush() + sys.exit(rc) + + +def get_target_devices(): + output = str(check_output('lsblk -l -o NAME', shell=True).decode()) + return re.findall("(nvme[0-9]+n[0-9]+)\n", output) + + +def create_fio_config(size, q_depth, devices, test, run_time, verify): + if not verify: + verifyfio = "" + else: + verifyfio = verify_template + fiofile = fio_template % {"blocksize": size, "iodepth": q_depth, + "testtype": test, "runtime": run_time, "verify": verifyfio} + for (i, dev) in enumerate(devices): + fiofile += fio_job_template % {"jobnumber": i, "device": dev} + return fiofile.encode() + + +def set_device_parameter(devices, filename_template, value): + for dev in devices: + filename = filename_template % dev + f = open(filename, 'r+b') + f.write(value) + f.close() + + +def configure_devices(devices): + set_device_parameter(devices, "/sys/block/%s/queue/nomerges", "2") + set_device_parameter(devices, "/sys/block/%s/queue/nr_requests", "128") + requested_qd = 128 + qd = requested_qd + while qd > 0: + try: + set_device_parameter(devices, "/sys/block/%s/device/queue_depth", str(qd)) + break + except IOError: + qd = qd - 1 + if qd == 0: + print("Could not set block device queue depths.") + else: + print("Requested queue_depth {} but only {} is supported.".format(str(requested_qd), str(qd))) + set_device_parameter(devices, "/sys/block/%s/queue/scheduler", "noop") + + +if __name__ == "__main__": + main() diff --git a/src/spdk/test/nvmf/host/aer.sh b/src/spdk/test/nvmf/host/aer.sh new file mode 100755 index 00000000..66e597aa --- /dev/null +++ b/src/spdk/test/nvmf/host/aer.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter aer +timing_enter start_nvmf_tgt + +$NVMF_APP -m 0xF & +nvmfpid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $nvmfpid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +modprobe -v nvme-rdma + +$rpc_py construct_malloc_bdev 64 512 --name Malloc0 +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 -m 2 +$rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 Malloc0 +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT + +$rpc_py get_nvmf_subsystems + +# TODO: this aer test tries to invoke an AER completion by setting the temperature +#threshold to a very low value. This does not work with emulated controllers +#though so currently the test is disabled. + +#$rootdir/test/nvme/aer/aer -r "\ +# trtype:RDMA \ +# adrfam:IPv4 \ +# traddr:$NVMF_FIRST_TARGET_IP \ +# trsvcid:$NVMF_PORT \ +# subnqn:nqn.2014-08.org.nvmexpress.discovery" + +# Namespace Attribute Notice Tests +$rootdir/test/nvme/aer/aer -r "\ + trtype:RDMA \ + adrfam:IPv4 \ + traddr:$NVMF_FIRST_TARGET_IP \ + trsvcid:$NVMF_PORT \ + subnqn:nqn.2016-06.io.spdk:cnode1" -n 2 & +aerpid=$! + +# Waiting for aer start to work +sleep 5 + +# Add a new namespace +$rpc_py construct_malloc_bdev 64 4096 --name Malloc1 +$rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 Malloc1 -n 2 +$rpc_py get_nvmf_subsystems + +wait $aerpid + +$rpc_py delete_malloc_bdev Malloc0 +$rpc_py delete_malloc_bdev Malloc1 +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + +trap - SIGINT SIGTERM EXIT + +nvmfcleanup +killprocess $nvmfpid +timing_exit aer diff --git a/src/spdk/test/nvmf/host/bdevperf.sh b/src/spdk/test/nvmf/host/bdevperf.sh new file mode 100755 index 00000000..1247177f --- /dev/null +++ b/src/spdk/test/nvmf/host/bdevperf.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter bdevperf +timing_enter start_nvmf_tgt + +$NVMF_APP -m 0xF & +nvmfpid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $nvmfpid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +bdevs="$bdevs $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 +for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev +done +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 + +echo "[Nvme]" > $testdir/bdevperf.conf +echo " TransportID \"trtype:RDMA adrfam:IPv4 subnqn:nqn.2016-06.io.spdk:cnode1 traddr:$NVMF_FIRST_TARGET_IP trsvcid:4420\" Nvme0" >> $testdir/bdevperf.conf +$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w verify -t 1 +sync +rm -rf $testdir/bdevperf.conf +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + +trap - SIGINT SIGTERM EXIT + +killprocess $nvmfpid +timing_exit bdevperf diff --git a/src/spdk/test/nvmf/host/fio.sh b/src/spdk/test/nvmf/host/fio.sh new file mode 100755 index 00000000..ceed86b8 --- /dev/null +++ b/src/spdk/test/nvmf/host/fio.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/scripts/common.sh +source $rootdir/test/nvmf/common.sh + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +if [ ! -d /usr/src/fio ]; then + echo "FIO not available" + exit 0 +fi + +timing_enter fio +timing_enter start_nvmf_tgt + +$NVMF_APP -m 0xF & +nvmfpid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $nvmfpid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +bdevs="$bdevs $($rpc_py construct_malloc_bdev 64 512)" +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 +for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev +done +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 + +PLUGIN_DIR=$rootdir/examples/nvme/fio_plugin + +# Test fio_plugin as host with malloc backend +LD_PRELOAD=$PLUGIN_DIR/fio_plugin /usr/src/fio/fio $PLUGIN_DIR/example_config.fio --filename="trtype=RDMA adrfam=IPv4 \ +traddr=$NVMF_FIRST_TARGET_IP trsvcid=4420 ns=1" +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + +if [ $RUN_NIGHTLY -eq 1 ]; then + # Test fio_plugin as host with nvme lvol backend + bdfs=$(iter_pci_class_code 01 08 02) + $rpc_py construct_nvme_bdev -b Nvme0 -t PCIe -a $(echo $bdfs | awk '{ print $1 }') + ls_guid=$($rpc_py construct_lvol_store Nvme0n1 lvs_0) + get_lvs_free_mb $ls_guid + lb_guid=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_0 $free_mb) + $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode2 -a -s SPDK00000000000001 + for bdev in $lb_guid; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode2 $bdev + done + $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode2 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 + LD_PRELOAD=$PLUGIN_DIR/fio_plugin /usr/src/fio/fio $PLUGIN_DIR/example_config.fio --filename="trtype=RDMA adrfam=IPv4 \ + traddr=$NVMF_FIRST_TARGET_IP trsvcid=4420 ns=1" + $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode2 + + # Test fio_plugin as host with nvme lvol nested backend + ls_nested_guid=$($rpc_py construct_lvol_store $lb_guid lvs_n_0) + get_lvs_free_mb $ls_nested_guid + lb_nested_guid=$($rpc_py construct_lvol_bdev -u $ls_nested_guid lbd_nest_0 $free_mb) + $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode3 -a -s SPDK00000000000001 + for bdev in $lb_nested_guid; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode3 $bdev + done + $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode3 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 + LD_PRELOAD=$PLUGIN_DIR/fio_plugin /usr/src/fio/fio $PLUGIN_DIR/example_config.fio --filename="trtype=RDMA adrfam=IPv4 \ + traddr=$NVMF_FIRST_TARGET_IP trsvcid=4420 ns=1" + $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode3 + + sync + # Delete lvol_bdev and destroy lvol_store. + $rpc_py destroy_lvol_bdev "$lb_nested_guid" + $rpc_py destroy_lvol_store -l lvs_n_0 + $rpc_py destroy_lvol_bdev "$lb_guid" + $rpc_py destroy_lvol_store -l lvs_0 + $rpc_py delete_nvme_controller Nvme0 +fi + +trap - SIGINT SIGTERM EXIT + +killprocess $nvmfpid +timing_exit fio diff --git a/src/spdk/test/nvmf/host/identify.sh b/src/spdk/test/nvmf/host/identify.sh new file mode 100755 index 00000000..ad101980 --- /dev/null +++ b/src/spdk/test/nvmf/host/identify.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi +timing_enter identify +timing_enter start_nvmf_tgt + +$NVMF_APP -m 0xF & +nvmfpid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $nvmfpid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +bdevs="$bdevs $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 +for bdev in $bdevs; do + # NOTE: This will assign the same NGUID and EUI64 to all bdevs, + # but currently we only have one (see above), so this is OK. + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 "$bdev" \ + --nguid "ABCDEF0123456789ABCDEF0123456789" \ + --eui64 "ABCDEF0123456789" +done +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_FIRST_TARGET_IP -s 4420 + +$rpc_py get_nvmf_subsystems + +$rootdir/examples/nvme/identify/identify -r "\ + trtype:RDMA \ + adrfam:IPv4 \ + traddr:$NVMF_FIRST_TARGET_IP \ + trsvcid:$NVMF_PORT \ + subnqn:nqn.2014-08.org.nvmexpress.discovery" -L all +$rootdir/examples/nvme/identify/identify -r "\ + trtype:RDMA \ + adrfam:IPv4 \ + traddr:$NVMF_FIRST_TARGET_IP \ + trsvcid:$NVMF_PORT \ + subnqn:nqn.2016-06.io.spdk:cnode1" -L all +sync +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + +trap - SIGINT SIGTERM EXIT + +killprocess $nvmfpid +timing_exit identify diff --git a/src/spdk/test/nvmf/host/identify_kernel_nvmf.sh b/src/spdk/test/nvmf/host/identify_kernel_nvmf.sh new file mode 100755 index 00000000..d6afe52f --- /dev/null +++ b/src/spdk/test/nvmf/host/identify_kernel_nvmf.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +set -e + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter identify_kernel_nvmf_tgt + +subsystemname=nqn.2016-06.io.spdk:testnqn + +modprobe null_blk nr_devices=1 +modprobe nvmet +modprobe nvmet-rdma +modprobe nvmet-fc +modprobe lpfc + +if [ ! -d /sys/kernel/config/nvmet/subsystems/$subsystemname ]; then + mkdir /sys/kernel/config/nvmet/subsystems/$subsystemname +fi +echo 1 > /sys/kernel/config/nvmet/subsystems/$subsystemname/attr_allow_any_host + +if [ ! -d /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1 ]; then + mkdir /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1 +fi + +echo -n /dev/nullb0 > /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1/device_path +echo 1 > /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1/enable + +if [ ! -d /sys/kernel/config/nvmet/ports/1 ]; then + mkdir /sys/kernel/config/nvmet/ports/1 +fi + +echo -n rdma > /sys/kernel/config/nvmet/ports/1/addr_trtype +echo -n ipv4 > /sys/kernel/config/nvmet/ports/1/addr_adrfam +echo -n $NVMF_FIRST_TARGET_IP > /sys/kernel/config/nvmet/ports/1/addr_traddr +echo -n $NVMF_PORT > /sys/kernel/config/nvmet/ports/1/addr_trsvcid + +ln -s /sys/kernel/config/nvmet/subsystems/$subsystemname /sys/kernel/config/nvmet/ports/1/subsystems/$subsystemname + +sleep 4 + +$rootdir/examples/nvme/identify/identify -r "\ + trtype:RDMA \ + adrfam:IPv4 \ + traddr:$NVMF_FIRST_TARGET_IP \ + trsvcid:$NVMF_PORT \ + subnqn:nqn.2014-08.org.nvmexpress.discovery" -t all +$rootdir/examples/nvme/identify/identify -r "\ + trtype:RDMA \ + adrfam:IPv4 \ + traddr:$NVMF_FIRST_TARGET_IP \ + trsvcid:$NVMF_PORT \ + subnqn:$subsystemname" + +rm -rf /sys/kernel/config/nvmet/ports/1/subsystems/$subsystemname + +echo 0 > /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1/enable +echo -n 0 > /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1/device_path + +rmdir --ignore-fail-on-non-empty /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1 +rmdir --ignore-fail-on-non-empty /sys/kernel/config/nvmet/subsystems/$subsystemname +rmdir --ignore-fail-on-non-empty /sys/kernel/config/nvmet/ports/1 + +rmmod lpfc +rmmod nvmet_fc +rmmod nvmet-rdma +rmmod null_blk +rmmod nvmet + +timing_exit identify_kernel_nvmf_tgt diff --git a/src/spdk/test/nvmf/host/perf.sh b/src/spdk/test/nvmf/host/perf.sh new file mode 100755 index 00000000..24faed5b --- /dev/null +++ b/src/spdk/test/nvmf/host/perf.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter perf +timing_enter start_nvmf_tgt + +$NVMF_APP -m 0xF -i 0 & +nvmfpid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $nvmfpid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +$rootdir/scripts/gen_nvme.sh --json | $rpc_py load_subsystem_config +timing_exit start_nvmf_tgt + +local_nvme_trid="trtype:PCIe traddr:"$($rpc_py get_subsystem_config bdev | jq -r '.[].params | select(.name=="Nvme0").traddr') +bdevs="$bdevs $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + +if [ -n "$local_nvme_trid" ]; then + bdevs="$bdevs Nvme0n1" +fi + +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 +for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev +done +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 + +# Test multi-process access to local NVMe device +if [ -n "$local_nvme_trid" ]; then + $rootdir/examples/nvme/perf/perf -i 0 -q 32 -o 4096 -w randrw -M 50 -t 1 -r "$local_nvme_trid" +fi + +$rootdir/examples/nvme/perf/perf -q 32 -o 4096 -w randrw -M 50 -t 1 -r "trtype:RDMA adrfam:IPv4 traddr:$NVMF_FIRST_TARGET_IP trsvcid:4420" +sync +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + +if [ $RUN_NIGHTLY -eq 1 ]; then + # Configure nvme devices with nvmf lvol_bdev backend + if [ -n "$local_nvme_trid" ]; then + ls_guid=$($rpc_py construct_lvol_store Nvme0n1 lvs_0) + get_lvs_free_mb $ls_guid + lb_guid=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_0 $free_mb) + + # Create lvol bdev for nested lvol stores + ls_nested_guid=$($rpc_py construct_lvol_store $lb_guid lvs_n_0) + get_lvs_free_mb $ls_nested_guid + lb_nested_guid=$($rpc_py construct_lvol_bdev -u $ls_nested_guid lbd_nest_0 $free_mb) + $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 + for bdev in $lb_nested_guid; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev + done + $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420 + # Test perf as host with different io_size and qd_depth in nightly + qd_depth=("1" "128") + io_size=("512" "131072") + for qd in ${qd_depth[@]}; do + for o in ${io_size[@]}; do + $rootdir/examples/nvme/perf/perf -q $qd -o $o -w randrw -M 50 -t 10 -r "trtype:RDMA adrfam:IPv4 traddr:$NVMF_FIRST_TARGET_IP trsvcid:4420" + done + done + + # Delete subsystems, lvol_bdev and destroy lvol_store. + $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + $rpc_py destroy_lvol_bdev "$lb_nested_guid" + $rpc_py destroy_lvol_store -l lvs_n_0 + $rpc_py destroy_lvol_bdev "$lb_guid" + $rpc_py destroy_lvol_store -l lvs_0 + $rpc_py delete_nvme_controller Nvme0 + fi +fi + +trap - SIGINT SIGTERM EXIT + +killprocess $nvmfpid +timing_exit perf diff --git a/src/spdk/test/nvmf/lvol/nvmf_lvol.sh b/src/spdk/test/nvmf/lvol/nvmf_lvol.sh new file mode 100755 index 00000000..7fe93a6c --- /dev/null +++ b/src/spdk/test/nvmf/lvol/nvmf_lvol.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 +LVOL_BDEV_SIZE=10 +SUBSYS_NR=2 +LVOL_BDEVS_NR=6 + +rpc_py="$rootdir/scripts/rpc.py" + +function disconnect_nvmf() +{ + for i in `seq 1 $SUBSYS_NR`; do + nvme disconnect -n "nqn.2016-06.io.spdk:cnode${i}" || true + done +} + +set -e + +# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization. +# e.g. sudo ./nvmf_lvol.sh iso +nvmftestinit $1 + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +# SoftRoce does not have enough queues available for +# multiconnection tests. Detect if we're using software RDMA. +# If so - lower the number of subsystems for test. +if check_ip_is_soft_roce $NVMF_FIRST_TARGET_IP; then + echo "Using software RDMA, lowering number of NVMeOF subsystems." + SUBSYS_NR=1 +fi + +timing_enter lvol_integrity +timing_enter start_nvmf_tgt +# Start up the NVMf target in another process +$NVMF_APP -m 0xF & +pid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; disconnect_nvmf; killprocess $pid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +modprobe -v nvme-rdma + +lvol_stores=() +lvol_bdevs=() +# Create the first LVS from a Raid-0 bdev, which is created from two malloc bdevs +# Create remaining LVSs from a malloc bdev, respectively +for i in `seq 1 $SUBSYS_NR`; do + if [ $i -eq 1 ]; then + # construct RAID bdev and put its name in $bdev + malloc_bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) " + malloc_bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + $rpc_py construct_raid_bdev -n raid0 -s 64 -r 0 -b "$malloc_bdevs" + bdev="raid0" + else + # construct malloc bdev and put its name in $bdev + bdev="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + fi + ls_guid="$($rpc_py construct_lvol_store $bdev lvs_$i -c 524288)" + lvol_stores+=("$ls_guid") + + # 1 NVMe-OF subsystem per malloc bdev / lvol store / 10 lvol bdevs + ns_bdevs="" + + # Create lvol bdevs on each lvol store + for j in `seq 1 $LVOL_BDEVS_NR`; do + lb_name="$($rpc_py construct_lvol_bdev -u $ls_guid lbd_$j $LVOL_BDEV_SIZE)" + lvol_bdevs+=("$lb_name") + ns_bdevs+="$lb_name " + done + + $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode$i -a -s SPDK$i + for bdev in $ns_bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$i $bdev + done + $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode$i -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT +done + +for i in `seq 1 $SUBSYS_NR`; do + k=$[$i-1] + nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode${i}" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + + for j in `seq 1 $LVOL_BDEVS_NR`; do + waitforblk "nvme${k}n${j}" + done +done + +$testdir/../fio/nvmf_fio.py 262144 64 randwrite 10 verify + +sync +disconnect_nvmf + +for i in `seq 1 $SUBSYS_NR`; do + $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode$i +done + +rm -f ./local-job* + +trap - SIGINT SIGTERM EXIT + +nvmfcleanup +killprocess $pid +nvmftestfini $1 +timing_exit lvol_integrity diff --git a/src/spdk/test/nvmf/multiconnection/multiconnection.sh b/src/spdk/test/nvmf/multiconnection/multiconnection.sh new file mode 100755 index 00000000..97155e78 --- /dev/null +++ b/src/spdk/test/nvmf/multiconnection/multiconnection.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +MALLOC_BDEV_SIZE=128 +MALLOC_BLOCK_SIZE=512 +NVMF_SUBSYS=11 + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization. +# e.g. sudo ./multiconnection.sh iso +nvmftestinit $1 + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +# SoftRoce does not have enough queues available for +# multiconnection tests. Detect if we're using software RDMA. +# If so - lower the number of subsystems for test. +if check_ip_is_soft_roce $NVMF_FIRST_TARGET_IP; then + echo "Using software RDMA, lowering number of NVMeOF subsystems." + NVMF_SUBSYS=1 +fi + +timing_enter multiconnection +timing_enter start_nvmf_tgt +# Start up the NVMf target in another process +$NVMF_APP -m 0xF & +pid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $pid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +modprobe -v nvme-rdma + +for i in `seq 1 $NVMF_SUBSYS` +do + bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode$i -a -s SPDK$i + for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$i $bdev + done + $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode$i -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT +done + +for i in `seq 1 $NVMF_SUBSYS`; do + k=$[$i-1] + nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode${i}" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + + waitforblk "nvme${k}n1" +done + +$testdir/../fio/nvmf_fio.py 262144 64 read 10 +$testdir/../fio/nvmf_fio.py 262144 64 randwrite 10 + +sync +for i in `seq 1 $NVMF_SUBSYS`; do + nvme disconnect -n "nqn.2016-06.io.spdk:cnode${i}" || true + $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode${i} +done + +rm -f ./local-job0-0-verify.state + +trap - SIGINT SIGTERM EXIT + +nvmfcleanup +killprocess $pid +nvmftestfini $1 +timing_exit multiconnection diff --git a/src/spdk/test/nvmf/nmic/nmic.sh b/src/spdk/test/nvmf/nmic/nmic.sh new file mode 100755 index 00000000..7b66a977 --- /dev/null +++ b/src/spdk/test/nvmf/nmic/nmic.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +MALLOC_BDEV_SIZE=128 +MALLOC_BLOCK_SIZE=512 + +rpc_py="python $rootdir/scripts/rpc.py" + +set -e + +# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization. +# e.g. sudo ./nmic.sh iso +nvmftestinit $1 + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +NVMF_SECOND_TARGET_IP=$(echo "$RDMA_IP_LIST" | sed -n 2p) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter nmic +timing_enter start_nvmf_tgt +# Start up the NVMf target in another process +$NVMF_APP -m 0xF & +pid=$! + +trap "killprocess $pid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +# Create subsystems +bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK1 +for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev +done +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_FIRST_TARGET_IP -s "$NVMF_PORT" + +echo "test case1: single bdev can't be used in multiple subsystems" +set +e +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode2 -a -s SPDK2 +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode2 -t RDMA -a $NVMF_FIRST_TARGET_IP -s "$NVMF_PORT" +for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode2 $bdev + nmic_status=$? + + if [ $nmic_status -eq 0 ]; then + echo " Adding namespace passed - failure expected." + killprocess $pid + exit 1 + else + echo " Adding namespace failed - expected result." + fi +done +set -e + +modprobe -v nvme-rdma + +echo "test case2: host connect to nvmf target in multiple paths" +if [ ! -z $NVMF_SECOND_TARGET_IP ]; then + $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_SECOND_TARGET_IP -s $NVMF_PORT + + nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_SECOND_TARGET_IP" -s "$NVMF_PORT" + + waitforblk "nvme0n1" + + $testdir/../fio/nvmf_fio.py 4096 1 write 1 verify +fi + +nvme disconnect -n "nqn.2016-06.io.spdk:cnode1" || true + +trap - SIGINT SIGTERM EXIT + +nvmfcleanup +killprocess $pid + +nvmftestfini $1 +timing_exit nmic diff --git a/src/spdk/test/nvmf/nvme_cli/nvme_cli.sh b/src/spdk/test/nvmf/nvme_cli/nvme_cli.sh new file mode 100755 index 00000000..c8b40794 --- /dev/null +++ b/src/spdk/test/nvmf/nvme_cli/nvme_cli.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +if [ -z "${DEPENDENCY_DIR}" ]; then + echo DEPENDENCY_DIR not defined! + exit 1 +fi + +spdk_nvme_cli="${DEPENDENCY_DIR}/nvme-cli" + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization. +# e.g. sudo ./nvme_cli.sh iso +nvmftestinit $1 + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter nvme_cli +timing_enter start_nvmf_tgt +$NVMF_APP -m 0xF & +nvmfpid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $nvmfpid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) " +bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + +modprobe -v nvme-rdma + +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 +for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev +done +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT + +nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + +waitforblk "nvme0n1" +waitforblk "nvme0n2" + +nvme list + +for ctrl in /dev/nvme?; do + nvme id-ctrl $ctrl + nvme smart-log $ctrl +done + +for ns in /dev/nvme?n*; do + nvme id-ns $ns +done + +nvme disconnect -n "nqn.2016-06.io.spdk:cnode1" || true +nvme disconnect -n "nqn.2016-06.io.spdk:cnode2" || true + +if [ -d $spdk_nvme_cli ]; then + # Test spdk/nvme-cli NVMe-oF commands: discover, connect and disconnect + cd $spdk_nvme_cli + ./nvme discover -t rdma -a $NVMF_FIRST_TARGET_IP -s "$NVMF_PORT" + nvme_num_before_connection=$(nvme list |grep "/dev/nvme*"|awk '{print $1}'|wc -l) + ./nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + sleep 1 + nvme_num=$(nvme list |grep "/dev/nvme*"|awk '{print $1}'|wc -l) + ./nvme disconnect -n "nqn.2016-06.io.spdk:cnode1" + if [ $nvme_num -le $nvme_num_before_connection ]; then + echo "spdk/nvme-cli connect target devices failed" + exit 1 + fi +fi + +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 +trap - SIGINT SIGTERM EXIT + +nvmfcleanup +killprocess $nvmfpid +nvmftestfini $1 +report_test_completion "nvmf_spdk_nvme_cli" +timing_exit nvme_cli diff --git a/src/spdk/test/nvmf/nvmf.sh b/src/spdk/test/nvmf/nvmf.sh new file mode 100755 index 00000000..70055777 --- /dev/null +++ b/src/spdk/test/nvmf/nvmf.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) +source $rootdir/test/common/autotest_common.sh + +if [ ! $(uname -s) = Linux ]; then + exit 0 +fi + +source $rootdir/test/nvmf/common.sh + +timing_enter nvmf_tgt + +# NVMF_TEST_CORE_MASK is the biggest core mask specified by +# any of the nvmf_tgt tests. Using this mask for the stub +# ensures that if this mask spans CPU sockets, that we will +# allocate memory from both sockets. The stub will *not* +# run anything on the extra cores (and will sleep on master +# core 0) so there is no impact to the nvmf_tgt tests by +# specifying the bigger core mask. +start_stub "-s 2048 -i 0 -m $NVMF_TEST_CORE_MASK" +trap "kill_stub; exit 1" SIGINT SIGTERM EXIT + +export NVMF_APP_SHM_ID="0" +export NVMF_APP="./app/nvmf_tgt/nvmf_tgt -i $NVMF_APP_SHM_ID -e 0xFFFF" + +run_test suite test/nvmf/filesystem/filesystem.sh +run_test suite test/nvmf/discovery/discovery.sh +if [ $SPDK_TEST_NVME_CLI -eq 1 ]; then + run_test suite test/nvmf/nvme_cli/nvme_cli.sh +fi +run_test suite test/nvmf/lvol/nvmf_lvol.sh +run_test suite test/nvmf/shutdown/shutdown.sh +run_test suite test/nvmf/bdev_io_wait/bdev_io_wait.sh +run_test suite test/nvmf/create_transport/create_transport.sh + +if [ $RUN_NIGHTLY -eq 1 ]; then + run_test suite test/nvmf/multiconnection/multiconnection.sh +fi + +timing_enter host + +run_test suite test/nvmf/host/bdevperf.sh +run_test suite test/nvmf/host/identify.sh +run_test suite test/nvmf/host/perf.sh +# TODO: disabled due to intermittent failures (RDMA_CM_EVENT_UNREACHABLE/ETIMEDOUT) +#run_test test/nvmf/host/identify_kernel_nvmf.sh +run_test suite test/nvmf/host/aer.sh +run_test suite test/nvmf/host/fio.sh + +run_test suite test/nvmf/nmic/nmic.sh + +timing_exit host +trap - SIGINT SIGTERM EXIT +kill_stub + +# TODO: enable nvme device detachment for multi-process so that +# we can use the stub for this test +run_test suite test/nvmf/rpc/rpc.sh +run_test suite test/nvmf/fio/fio.sh +revert_soft_roce + +report_test_completion "nvmf" +timing_exit nvmf_tgt diff --git a/src/spdk/test/nvmf/nvmfjson/json_config.sh b/src/spdk/test/nvmf/nvmfjson/json_config.sh new file mode 100755 index 00000000..bc624d21 --- /dev/null +++ b/src/spdk/test/nvmf/nvmfjson/json_config.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -xe +NVMF_JSON_DIR=$(readlink -f $(dirname $0)) +. $NVMF_JSON_DIR/../../json_config/common.sh +base_nvmf_config=$JSON_DIR/base_nvmf_config.json +last_nvmf_config=$JSON_DIR/last_nvmf_config.json + +function test_subsystems() { + run_spdk_tgt + + rpc_py="$spdk_rpc_py" + clear_config_py="$spdk_clear_config_py" + + $rpc_py start_subsystem_init + create_nvmf_subsystem_config + $rpc_py save_config > $base_nvmf_config + test_json_config + + clear_nvmf_subsystem_config + kill_targets + + run_spdk_tgt + $rpc_py load_config < $base_nvmf_config + $rpc_py save_config > $last_nvmf_config + + json_diff $base_nvmf_config $last_nvmf_config + + clear_nvmf_subsystem_config + kill_targets + rm -f $base_nvmf_config $last_nvmf_config +} + +trap 'on_error_exit "${FUNCNAME}" "${LINENO}"; rm -f $base_nvmf_config $last_nvmf_config' ERR + +timing_enter nvmf_json_config +test_subsystems +timing_exit nvmf_json_config +revert_soft_roce + +report_test_completion nvmf_json_config diff --git a/src/spdk/test/nvmf/rpc/rpc.sh b/src/spdk/test/nvmf/rpc/rpc.sh new file mode 100755 index 00000000..5e8837d1 --- /dev/null +++ b/src/spdk/test/nvmf/rpc/rpc.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization. +# e.g. sudo ./rpc.sh iso +nvmftestinit $1 + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter rpc +timing_enter start_nvmf_tgt +# Start up the NVMf target in another process +$NVMF_APP -m 0xF & +pid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $pid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +# set times for subsystem construct/delete +if [ $RUN_NIGHTLY -eq 1 ]; then + times=50 +else + times=3 +fi + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 + +bdevs="$bdevs $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + +# Disallow host NQN and make sure connect fails +$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 +for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev +done +$rpc_py nvmf_subsystem_allow_any_host -d nqn.2016-06.io.spdk:cnode1 +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT + +modprobe -v nvme-rdma +trap "killprocess $pid; nvmfcleanup; exit 1" SIGINT SIGTERM EXIT + +# This connect should fail - the host NQN is not allowed +! nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + +# Add the host NQN and verify that the connect succeeds +$rpc_py nvmf_subsystem_add_host nqn.2016-06.io.spdk:cnode1 nqn.2016-06.io.spdk:host1 +nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" +waitforblk "nvme0n1" +nvme disconnect -n nqn.2016-06.io.spdk:cnode1 + +# Remove the host and verify that the connect fails +$rpc_py nvmf_subsystem_remove_host nqn.2016-06.io.spdk:cnode1 nqn.2016-06.io.spdk:host1 +! nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + +# Allow any host and verify that the connect succeeds +$rpc_py nvmf_subsystem_allow_any_host -e nqn.2016-06.io.spdk:cnode1 +nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" +waitforblk "nvme0n1" +nvme disconnect -n nqn.2016-06.io.spdk:cnode1 + +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 + +# do frequent add delete of namespaces with different nsid. +for i in `seq 1 $times` +do + j=0 + for bdev in $bdevs; do + let j=j+1 + $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode$j -s SPDK00000000000001 + $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode$j -t RDMA -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$j $bdev -n 5 + $rpc_py nvmf_subsystem_allow_any_host nqn.2016-06.io.spdk:cnode$j + nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode$j -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + done + + waitforblk "nvme0n1" + n=$j + for j in `seq 1 $n` + do + nvme disconnect -n nqn.2016-06.io.spdk:cnode$j + done + + j=0 + for bdev in $bdevs; do + let j=j+1 + $rpc_py nvmf_subsystem_remove_ns nqn.2016-06.io.spdk:cnode$j 5 + done + + n=$j + for j in `seq 1 $n` + do + $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode$j + done + +done + +nvmfcleanup +trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT + +# do frequent add delete. +for i in `seq 1 $times` +do + j=0 + for bdev in $bdevs; do + let j=j+1 + $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode$j -s SPDK00000000000001 + $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode$j -t RDMA -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$j $bdev + $rpc_py nvmf_subsystem_allow_any_host nqn.2016-06.io.spdk:cnode$j + done + + j=0 + for bdev in $bdevs; do + let j=j+1 + $rpc_py nvmf_subsystem_remove_ns nqn.2016-06.io.spdk:cnode$j $j + done + + n=$j + for j in `seq 1 $n` + do + $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode$j + done +done + +trap - SIGINT SIGTERM EXIT + +killprocess $pid +nvmftestfini $1 +timing_exit rpc diff --git a/src/spdk/test/nvmf/shutdown/shutdown.sh b/src/spdk/test/nvmf/shutdown/shutdown.sh new file mode 100755 index 00000000..f68c4b21 --- /dev/null +++ b/src/spdk/test/nvmf/shutdown/shutdown.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +MALLOC_BDEV_SIZE=128 +MALLOC_BLOCK_SIZE=512 + +rpc_py="$rootdir/scripts/rpc.py" + +set -e + +# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization. +# e.g. sudo ./shutdown.sh iso +nvmftestinit $1 + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) +if [ -z $NVMF_FIRST_TARGET_IP ]; then + echo "no NIC for nvmf test" + exit 0 +fi + +timing_enter shutdown +timing_enter start_nvmf_tgt +# Start up the NVMf target in another process +$NVMF_APP -m 0xF & +pid=$! + +trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $pid; nvmfcleanup; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT + +waitforlisten $pid +$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 +timing_exit start_nvmf_tgt + +num_subsystems=10 +# SoftRoce does not have enough queues available for +# this test. Detect if we're using software RDMA. +# If so, only use four subsystems. +if check_ip_is_soft_roce "$NVMF_FIRST_TARGET_IP"; then + num_subsystems=4 +fi + +# Create subsystems +for i in `seq 1 $num_subsystems` +do + bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" + $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode$i -a -s SPDK$i + for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$i $bdev + done + $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode$i -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT +done + +modprobe -v nvme-rdma +modprobe -v nvme-fabrics + +# Repeatedly connect and disconnect +for ((x=0; x<5;x++)); do + # Connect kernel host to subsystems + for i in `seq 1 $num_subsystems`; do + nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode${i}" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + done + # Disconnect the subsystems in reverse order + for i in `seq $num_subsystems -1 1`; do + nvme disconnect -n nqn.2016-06.io.spdk:cnode${i} + done +done + +# Start a series of connects right before disconnecting +for i in `seq 1 $num_subsystems`; do + nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode${i}" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" +done + +waitforblk "nvme0n1" + +# Kill nvmf tgt without removing any subsystem to check whether it can shutdown correctly +rm -f ./local-job0-0-verify.state + +trap - SIGINT SIGTERM EXIT + +killprocess $pid + +nvmfcleanup +nvmftestfini $1 +timing_exit shutdown diff --git a/src/spdk/test/nvmf/test_plan.md b/src/spdk/test/nvmf/test_plan.md new file mode 100644 index 00000000..94347ef8 --- /dev/null +++ b/src/spdk/test/nvmf/test_plan.md @@ -0,0 +1,95 @@ +# SPDK nvmf_tgt test plan + +## Objective +The purpose of these tests is to verify correct behavior of SPDK NVMe-oF +feature. +These tests are run either per-commit or as nightly tests. + +## Configuration +All tests share the same basic configuration file for SPDK nvmf_tgt to run. +Static configuration from config file consists of setting number of per session +queues and enabling RPC for further configuration via RPC calls. +RPC calls used for dynamic configuration consist: +- creating Malloc backend devices +- creating Null Block backend devices +- constructing NVMe-oF subsystems +- deleting NVMe-oF subsystems + +### Tests + +#### Test 1: NVMe-oF namespace on a Logical Volumes device +This test configures a SPDK NVMe-oF subsystem backed by logical volume +devices and uses FIO to generate I/Os that target those subsystems. +The logical volume bdevs are backed by malloc bdevs. +Test steps: +- Step 1: Assign IP addresses to RDMA NICs. +- Step 2: Start SPDK nvmf_tgt application. +- Step 3: Create malloc bdevs. +- Step 4: Create logical volume stores on malloc bdevs. +- Step 5: Create 10 logical volume bdevs on each logical volume store. +- Step 6: Create NVMe-oF subsystems with logical volume bdev namespaces. +- Step 7: Connect to NVMe-oF susbsystems with kernel initiator. +- Step 8: Run FIO with workload parameters: blocksize=256k, iodepth=64, +workload=randwrite; varify flag is enabled so that FIO reads and verifies +the data written to the logical device. The run time is 10 seconds for a +quick test an 10 minutes for longer nightly test. +- Step 9: Disconnect kernel initiator from NVMe-oF subsystems. +- Step 10: Delete NVMe-oF subsystems from configuration. + +### Compatibility testing + +- Verify functionality of SPDK `nvmf_tgt` with Linux kernel NVMe-oF host + - Exercise various kernel NVMe host parameters + - `nr_io_queues` + - `queue_size` + - Test discovery subsystem with `nvme` CLI tool + - Verify that discovery service works correctly with `nvme discover` + - Verify that large responses work (many subsystems) + +### Specification compliance + +- NVMe base spec compliance + - Verify all mandatory admin commands are implemented + - Get Log Page + - Identify (including all mandatory CNS values) + - Identify Namespace + - Identify Controller + - Active Namespace List + - Allocated Namespace List + - Identify Allocated Namespace + - Attached Controller List + - Controller List + - Abort + - Set Features + - Get Features + - Asynchronous Event Request + - Keep Alive + - Verify all mandatory NVM command set I/O commands are implemented + - Flush + - Write + - Read + - Verify all mandatory log pages + - Error Information + - SMART / Health Information + - Firmware Slot Information + - Verify all mandatory Get/Set Features + - Arbitration + - Power Management + - Temperature Threshold + - Error Recovery + - Number of Queues + - Write Atomicity Normal + - Asynchronous Event Configuration + - Verify all implemented commands behave as required by the specification +- Fabric command processing + - Verify that Connect commands with invalid parameters are failed with correct response + - Invalid RECFMT + - Invalid SQSIZE + - Invalid SUBNQN, HOSTNQN (too long, incorrect format, not null terminated) + - QID != 0 before admin queue created + - CNTLID != 0xFFFF (static controller mode) + - Verify that non-Fabric commands are only allowed in the correct states + +### Configuration and RPC + +- Verify that invalid NQNs cannot be configured via conf file or RPC diff --git a/src/spdk/test/pmem/common.sh b/src/spdk/test/pmem/common.sh new file mode 100644 index 00000000..add36719 --- /dev/null +++ b/src/spdk/test/pmem/common.sh @@ -0,0 +1,107 @@ +BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../ && pwd)" +rpc_py="$TEST_DIR/scripts/rpc.py " + +source $TEST_DIR/test/common/autotest_common.sh + +# Prints error message and return error code, closes vhost app and remove +# pmem pool file +# input: error message, error code +function error() +{ + local error_code=${2:-1} + echo "===========" + echo -e "ERROR: $1" + echo "error code: $error_code" + echo "===========" + vhost_kill + pmem_clean_pool_file + return $error_code +} + +# check if there is pool file & remove it +# input: path to pool file +# default: $TEST_DIR/test/pmem/pool_file +function pmem_clean_pool_file() +{ + local pool_file=${1:-$TEST_DIR/test/pmem/pool_file} + + if [ -f $pool_file ]; then + echo "Deleting old pool_file" + rm $pool_file + fi +} + +# create new pmem file +# input: path to pool file, size in MB, block_size +# default: $TEST_DIR/test/pmem/pool_file 32 512 +function pmem_create_pool_file() +{ + local pool_file=${1:-$TEST_DIR/test/pmem/pool_file} + local size=${2:-32} + local block_size=${3:-512} + + pmem_clean_pool_file $pool_file + echo "Creating new pool file" + if ! $rpc_py create_pmem_pool $pool_file $size $block_size; then + error "Creating pool_file failed!" + fi + + if [ ! -f $pool_file ]; then + error "Creating pool_file failed!" + fi +} + +function pmem_unmount_ramspace +{ + if [ -d "$TEST_DIR/test/pmem/ramspace" ]; then + if mount | grep -q "$TEST_DIR/test/pmem/ramspace"; then + umount $TEST_DIR/test/pmem/ramspace + fi + + rm -rf $TEST_DIR/test/pmem/ramspace + fi +} + +function pmem_print_tc_name +{ + echo "" + echo "===============================================================" + echo "Now running: $1" + echo "===============================================================" +} + +function vhost_start() +{ + local vhost_pid + + $TEST_DIR/app/vhost/vhost & + if [ $? != 0 ]; then + echo -e "ERROR: Failed to launch vhost!" + return 1 + fi + + vhost_pid=$! + echo $vhost_pid > $TEST_DIR/test/pmem/vhost.pid + waitforlisten $vhost_pid +} + +function vhost_kill() +{ + local vhost_pid_file="$TEST_DIR/test/pmem/vhost.pid" + local vhost_pid="$(cat $vhost_pid_file)" + + if [[ ! -f $TEST_DIR/test/pmem/vhost.pid ]]; then + echo -e "ERROR: No vhost pid file found!" + return 1 + fi + + if ! kill -s INT $vhost_pid; then + echo -e "ERROR: Failed to exit vhost / invalid pid!" + rm $vhost_pid_file + return 1 + fi + + sleep 1 + rm $vhost_pid_file +} diff --git a/src/spdk/test/pmem/json_config/json_config.sh b/src/spdk/test/pmem/json_config/json_config.sh new file mode 100755 index 00000000..bd232b8f --- /dev/null +++ b/src/spdk/test/pmem/json_config/json_config.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -ex +VHOST_JSON_DIR=$(readlink -f $(dirname $0)) +. $VHOST_JSON_DIR/../../json_config/common.sh + +function test_subsystems() { + run_spdk_tgt + + rpc_py="$spdk_rpc_py" + clear_config_py="$spdk_clear_config_py" + $rpc_py start_subsystem_init + + create_pmem_bdev_subsytem_config + test_json_config + clear_pmem_bdev_subsystem_config + + kill_targets +} + +trap 'on_error_exit "${FUNCNAME}" "${LINENO}"' ERR +timing_enter json_config_pmem + +test_subsystems +timing_exit json_config_pmem +report_test_completion json_config_pmem diff --git a/src/spdk/test/pmem/pmem.sh b/src/spdk/test/pmem/pmem.sh new file mode 100755 index 00000000..15188635 --- /dev/null +++ b/src/spdk/test/pmem/pmem.sh @@ -0,0 +1,701 @@ +#!/usr/bin/env bash + +BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../ && pwd)" + +enable_script_debug=false +test_info=false +test_create=false +test_delete=false +test_construct_bdev=false +test_delete_bdev=false +test_all=true +test_all_get=false +default_pool_file=$TEST_DIR/test/pmem/pool_file +obj_pool_file=$TEST_DIR/test/pmem/obj_pool_file +bdev_name=pmem0 + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for automated RPC tests for PMEM" + echo "For test details, check test_plan.md or" + echo "https://review.gerrithub.io/#/c/378618/18/test/pmem/test_plan.md" + echo + echo "Usage: $(basename $1) [OPTIONS]" + echo + echo "-h, --help Print help and exit" + echo "-x set -x for script debug" + echo " --info Run test cases for pmem_pool_info" + echo " --create Run test cases for create_pmem_pool" + echo " --delete Run test cases for delete_pmem_pool" + echo " --construct_bdev Run test cases for constructing pmem bdevs" + echo " --delete_bdev Run test cases for deleting pmem bdevs" + echo " --all Run all test cases (default)" + exit 0 +} + +while getopts 'xh-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 ;; + info) test_info=true; test_all=false;; + create) test_create=true; test_all=false;; + delete) test_delete=true; test_all=false;; + construct_bdev) test_construct_bdev=true; test_all=false;; + delete_bdev) test_delete_bdev=true; test_all=false;; + all) test_all_get=true;; + *) usage $0 "Invalid argument '$OPTARG'" ;; + esac + ;; + h) usage $0 ;; + x) enable_script_debug=true ;; + *) usage $0 "Invalid argument '$OPTARG'" + esac +done + +if $test_all_get; then + test_all=true +fi + +if [[ $EUID -ne 0 ]]; then + echo "Go away user come back as root" + exit 1 +fi + +source $TEST_DIR/test/pmem/common.sh +source $TEST_DIR/test/common/autotest_common.sh + +#================================================ +# pmem_pool_info tests +#================================================ +function pmem_pool_info_tc1() +{ + pmem_print_tc_name ${FUNCNAME[0]} + + if $rpc_py pmem_pool_info; then + error "pmem_pool_info passed with missing argument!" + fi + + return 0 +} + +function pmem_pool_info_tc2() +{ + pmem_print_tc_name ${FUNCNAME[0]} + + if $rpc_py pmem_pool_info $TEST_DIR/non/existing/path/non_existent_file; then + error "pmem_pool_info passed with invalid path!" + fi + + return 0 +} + +function pmem_pool_info_tc3() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file + + echo "Creating new type OBJ pool file" + if hash pmempool; then + pmempool create -s 32000000 obj $obj_pool_file + else + echo "Warning: pmempool package not found! Creating stub file." + truncate -s "32M" $obj_pool_file + fi + + if $rpc_py pmem_pool_info $TEST_DIR/test/pmem/obj_pool_file; then + pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file + error "Pmem_pool_info passed with invalid pool_file type!" + fi + + pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file + return 0 +} + +function pmem_pool_info_tc4() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + + pmem_create_pool_file + if ! $rpc_py pmem_pool_info $default_pool_file; then + error "Failed to get pmem_pool_info!" + fi + + pmem_clean_pool_file + return 0 +} + +#================================================ +# create_pmem_pool tests +#================================================ +function create_pmem_pool_tc1() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + + if $rpc_py create_pmem_pool 32 512; then + error "Mem pool file created w/out given path!" + fi + + if $rpc_py create_pmem_pool $default_pool_file; then + error "Mem pool file created w/out size & block size arguments!" + fi + + if $rpc_py pmem_pool_info $default_pool_file; then + error "create_pmem_pool created invalid pool file!" + fi + + if $rpc_py create_pmem_pool $default_pool_file 32; then + error "Mem pool file created w/out block size argument!" + fi + + if $rpc_py pmem_pool_info $default_pool_file; then + error "create_pmem_pool created invalid pool file!" + fi + + pmem_clean_pool_file + return 0 +} + +function create_pmem_pool_tc2() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + + if $rpc_py create_pmem_pool $TEST_DIR/non/existing/path/non_existent_file 32 512; then + error "Mem pool file created with incorrect path!" + fi + + if $rpc_py pmem_pool_info $TEST_DIR/non/existing/path/non_existent_file; then + error "create_pmem_pool created invalid pool file!" + fi + + pmem_clean_pool_file + return 0 +} + +function create_pmem_pool_tc3() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + + if ! $rpc_py create_pmem_pool $default_pool_file 256 512; then + error "Failed to create pmem pool!" + fi + + if ! $rpc_py pmem_pool_info $default_pool_file; then + error "Failed to get pmem info" + fi + + if ! $rpc_py delete_pmem_pool $default_pool_file; then + error "Failed to delete pool file!" + fi + + if $rpc_py pmem_pool_info $default_pool_file; then + error "Got pmem file info but file should be deleted" + fi + + if [ -f $default_pool_file ]; then + error "Failed to delete pmem file!" + fi + + pmem_clean_pool_file + return 0 +} + +function create_pmem_pool_tc4() +{ + pmem_print_tc_name ${FUNCNAME[0]} + + pmem_unmount_ramspace + mkdir $TEST_DIR/test/pmem/ramspace + mount -t tmpfs -o size=300m tmpfs $TEST_DIR/test/pmem/ramspace + if ! $rpc_py create_pmem_pool $TEST_DIR/test/pmem/ramspace/pool_file 256 512; then + pmem_unmount_ramspace + error "Failed to create pmem pool!" + fi + + if ! $rpc_py pmem_pool_info $TEST_DIR/test/pmem/ramspace/pool_file; then + pmem_unmount_ramspace + error "Failed to get pmem info" + fi + + if ! $rpc_py delete_pmem_pool $TEST_DIR/test/pmem/ramspace/pool_file; then + pmem_unmount_ramspace + error "Failed to delete pool file!" + fi + + if [ -f $TEST_DIR/test/pmem/ramspace/pool_file ]; then + pmem_unmount_ramspace + error "Failed to delete pmem file / file still exists!" + fi + + pmem_unmount_ramspace + return 0 +} + +function create_pmem_pool_tc5() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + local pmem_block_size + local pmem_num_block + + if ! $rpc_py create_pmem_pool $default_pool_file 256 512; then + error "Failed to create pmem pool!" + fi + + if $rpc_py pmem_pool_info $default_pool_file; then + pmem_block_size=$($rpc_py pmem_pool_info $default_pool_file | jq -r '.[] .block_size') + pmem_num_block=$($rpc_py pmem_pool_info $default_pool_file | jq -r '.[] .num_blocks') + else + error "Failed to get pmem info!" + fi + + if $rpc_py create_pmem_pool $default_pool_file 512 4096; then + error "Pmem pool with already occupied path has been created!" + fi + + if $rpc_py pmem_pool_info $default_pool_file; then + if [ $pmem_block_size != $($rpc_py pmem_pool_info $default_pool_file | jq -r '.[] .block_size') ]; then + error "Invalid block size of pmem pool!" + fi + + if [ $pmem_num_block != $($rpc_py pmem_pool_info $default_pool_file | jq -r '.[] .num_blocks') ]; then + error "Invalid number of blocks of pmem pool!" + fi + else + error "Failed to get pmem info!" + fi + + if ! $rpc_py delete_pmem_pool $default_pool_file; then + error "Failed to delete pmem file!" + fi + + pmem_clean_pool_file + return 0 +} + +function create_pmem_pool_tc6() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + local created_pmem_block_size + + for i in 511 512 1024 2048 4096 131072 262144 + do + if ! $rpc_py create_pmem_pool $default_pool_file 256 $i; then + error "Failed to create pmem pool!" + fi + + created_pmem_block_size=$($rpc_py pmem_pool_info $default_pool_file | jq -r '.[] .block_size') + if [ $? != 0 ]; then + error "Failed to get pmem info!" + fi + + if [ $i != $created_pmem_block_size ]; then + error "Invalid block size of pmem pool!" + fi + + if ! $rpc_py delete_pmem_pool $default_pool_file; then + error "Failed to delete pmem file!" + fi + done + + pmem_clean_pool_file + return 0 +} + +function create_pmem_pool_tc7() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + + if $rpc_py create_pmem_pool $default_pool_file 15 512; then + error "Created pmem pool with invalid size!" + fi + + if $rpc_py pmem_pool_info $default_pool_file; then + error "Pmem file shouldn' exist!" + fi + + pmem_clean_pool_file + return 0 +} + +function create_pmem_pool_tc8() +{ + pmem_print_tc_name "create_pmem_pool_tc8" + pmem_clean_pool_file + + if $rpc_py create_pmem_pool $default_pool_file 32 65536; then + error "Created pmem pool with invalid block number!" + fi + + if $rpc_py pmem_pool_info $default_pool_file; then + error "Pmem file shouldn' exist!" + fi + + pmem_clean_pool_file + return 0 +} + +function create_pmem_pool_tc9() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + + if $rpc_py create_pmem_pool $default_pool_file 256 -1; then + error "Created pmem pool with negative block size number!" + fi + + if $rpc_py pmem_pool_info $default_pool_file; then + error "create_pmem_pool create invalid pool file!" + fi + + if $rpc_py create_pmem_pool $default_pool_file -1 512; then + error "Created pmem pool with negative size number!" + fi + + if $rpc_py pmem_pool_info $default_pool_file; then + error "create_pmem_pool create invalid pool file!" + fi + + pmem_clean_pool_file + return 0 +} + +#================================================ +# delete_pmem_pool tests +#================================================ +function delete_pmem_pool_tc1() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + + if $rpc_py delete_pmem_pool $default_pool_file; then + error "delete_pmem_pool deleted inexistant pool file!" + fi + + return 0 +} + +function delete_pmem_pool_tc2() +{ + pmem_print_tc_name "delete_pmem_pool_tc2" + pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file + + echo "Creating new type OBJ pool file" + if hash pmempool; then + pmempool create -s 32000000 obj $obj_pool_file + else + echo "Warning: pmempool package not found! Creating stub file." + truncate -s "32M" $obj_pool_file + fi + + if $rpc_py delete_pmem_pool $TEST_DIR/test/pmem/obj_pool_file; then + pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file + error "delete_pmem_pool deleted invalid pmem pool type!" + fi + + pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file + return 0 +} + +function delete_pmem_pool_tc3() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + + pmem_create_pool_file + if ! $rpc_py pmem_pool_info $default_pool_file; then + error "Failed to get info on pmem pool file!" + fi + + if ! $rpc_py delete_pmem_pool $default_pool_file; then + error "Failed to delete pmem pool file!" + fi + + if $rpc_py pmem_pool_info $default_pool_file; then + error "Pmem pool file exists after using pmem_pool_info!" + fi + + return 0 +} + +function delete_pmem_pool_tc4() +{ + pmem_print_tc_name ${FUNCNAME[0]} + + delete_pmem_pool_tc3 + if $rpc_py delete_pmem_pool $default_pool_file; then + error "Deleted pmem pool file that shouldn't exist!" + fi + + return 0 +} + +#================================================ +# construct_pmem_bdev tests +#================================================ +function construct_pmem_bdev_tc1() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + + pmem_create_pool_file + if $rpc_py construct_pmem_bdev; then + error "construct_pmem_bdev passed with missing argument!" + fi + + pmem_clean_pool_file + return 0 +} + +function construct_pmem_bdev_tc2() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + + pmem_create_pool_file + if $rpc_py construct_pmem_bdev -n $bdev_name $TEST_DIR/non/existing/path/non_existent_file; then + error "Created pmem bdev w/out valid pool file!" + fi + + if $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi pmem; then + error "construct_pmem_bdev passed with invalid argument!" + fi + + pmem_clean_pool_file + return 0 +} + +function construct_pmem_bdev_tc3() +{ + pmem_print_tc_name ${FUNCNAME[0]} + + truncate -s 32M $TEST_DIR/test/pmem/random_file + if $rpc_py construct_pmem_bdev -n $bdev_name $TEST_DIR/test/pmem/random_file; then + error "Created pmem bdev from random file!" + fi + + if [ -f $TEST_DIR/test/pmem/random_file ]; then + echo "Deleting previously created random file" + rm $TEST_DIR/test/pmem/random_file + fi + + return 0 +} + +function construct_pmem_bdev_tc4() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file + + echo "Creating new type OBJ pool file" + if hash pmempool; then + pmempool create -s 32000000 obj $obj_pool_file + else + echo "Warning: pmempool package not found! Creating stub file." + truncate -s "32M" $obj_pool_file + fi + + if $rpc_py construct_pmem_bdev -n $bdev_name $TEST_DIR/test/pmem/obj_pool_file; then + pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file + error "Created pmem bdev from obj type pmem file!" + fi + + pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file + return 0 +} + +function construct_pmem_bdev_tc5() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + pmem_create_pool_file + local pmem_bdev_name + + if ! $rpc_py pmem_pool_info $default_pool_file; then + error "Failed to get pmem info!" + fi + + pmem_bdev_name=$($rpc_py construct_pmem_bdev -n $bdev_name $default_pool_file) + if [ $? != 0 ]; then + error "Failed to create pmem bdev" + fi + + if ! $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi $pmem_bdev_name; then + error "Pmem bdev not found!" + fi + + if ! $rpc_py delete_pmem_bdev $pmem_bdev_name; then + error "Failed to delete pmem bdev!" + fi + + if ! $rpc_py delete_pmem_pool $default_pool_file; then + error "Failed to delete pmem pool file!" + fi + + pmem_clean_pool_file + return 0 +} + +function construct_pmem_bdev_tc6() +{ + pmem_print_tc_name ${FUNCNAME[0]} + local pmem_bdev_name + pmem_clean_pool_file + + pmem_create_pool_file + if ! $rpc_py pmem_pool_info $default_pool_file; then + error "Failed to get info on pmem pool file!" + fi + + pmem_bdev_name=$($rpc_py construct_pmem_bdev -n $bdev_name $default_pool_file) + if [ $? != 0 ]; then + error "Failed to create pmem bdev!" + fi + + if ! $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi $pmem_bdev_name; then + error "Pmem bdev not found!" + fi + + if $rpc_py construct_pmem_bdev -n $bdev_name $default_pool_file; then + error "Constructed pmem bdev with occupied path!" + fi + + if ! $rpc_py delete_pmem_bdev $pmem_bdev_name; then + error "Failed to delete pmem bdev!" + fi + + if ! $rpc_py delete_pmem_pool $default_pool_file; then + error "Failed to delete pmem pool file!" + fi + + pmem_clean_pool_file + return 0 +} + +#================================================ +# delete_pmem_bdev tests +#================================================ +function delete_bdev_tc1() +{ + pmem_print_tc_name ${FUNCNAME[0]} + local pmem_bdev_name + local bdevs_names + pmem_clean_pool_file + + pmem_create_pool_file $default_pool_file 256 512 + if ! $rpc_py pmem_pool_info $default_pool_file; then + error "Failed to get pmem info!" + fi + + pmem_bdev_name=$($rpc_py construct_pmem_bdev -n $bdev_name $default_pool_file) + if [ $? != 0 ]; then + error "Failed to create pmem bdev!" + fi + + if ! $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi $pmem_bdev_name; then + error "$pmem_bdev_name bdev not found!" + fi + + if ! $rpc_py delete_pmem_bdev $pmem_bdev_name; then + error "Failed to delete $pmem_bdev_name bdev!" + fi + + bdevs_names=$($rpc_py get_bdevs | jq -r '.[] .name') + if echo $bdevs_names | grep -qi $pmem_bdev_name; then + error "$pmem_bdev_name bdev is not deleted!" + fi + + pmem_clean_pool_file + return 0 +} + +function delete_bdev_tc2() +{ + pmem_print_tc_name ${FUNCNAME[0]} + pmem_clean_pool_file + pmem_create_pool_file $default_pool_file 256 512 + local pmem_bdev_name + + if ! $rpc_py pmem_pool_info $default_pool_file; then + error "Failed to get pmem info!" + fi + + pmem_bdev_name=$($rpc_py construct_pmem_bdev -n $bdev_name $default_pool_file) + if [ $? != 0 ]; then + error "Failed to create pmem bdev" + fi + + if ! $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi $pmem_bdev_name; then + error "Pmem bdev not found!" + fi + + if ! $rpc_py delete_pmem_bdev $pmem_bdev_name; then + error "Failed to delete pmem bdev!" + fi + + if $rpc_py delete_pmem_bdev $pmem_bdev_name; then + error "delete_pmem_bdev deleted pmem bdev for second time!" + fi + + pmem_clean_pool_file + return 0 +} + +timing_enter pmem +vhost_start +if ! $enable_script_debug; then + set +x +fi + +if $test_info || $test_all; then + pmem_pool_info_tc1 + pmem_pool_info_tc2 + pmem_pool_info_tc3 + pmem_pool_info_tc4 +fi + +if $test_create || $test_all; then + create_pmem_pool_tc1 + create_pmem_pool_tc2 + create_pmem_pool_tc3 + create_pmem_pool_tc4 + create_pmem_pool_tc5 + create_pmem_pool_tc6 + create_pmem_pool_tc7 + create_pmem_pool_tc8 + create_pmem_pool_tc9 +fi + +if $test_delete || $test_all; then + delete_pmem_pool_tc1 + delete_pmem_pool_tc2 + delete_pmem_pool_tc3 + delete_pmem_pool_tc4 +fi + +if $test_construct_bdev || $test_all; then + construct_pmem_bdev_tc1 + construct_pmem_bdev_tc2 + construct_pmem_bdev_tc3 + construct_pmem_bdev_tc4 + construct_pmem_bdev_tc5 + construct_pmem_bdev_tc6 +fi + +if $test_delete_bdev || $test_all; then + delete_bdev_tc1 + delete_bdev_tc2 +fi + +pmem_clean_pool_file +report_test_completion "pmem" +vhost_kill +timing_exit pmem diff --git a/src/spdk/test/pmem/test_plan.md b/src/spdk/test/pmem/test_plan.md new file mode 100644 index 00000000..18e99f36 --- /dev/null +++ b/src/spdk/test/pmem/test_plan.md @@ -0,0 +1,310 @@ +# PMEM bdev feature test plan + +## Objective +The purpose of these tests is to verify possibility of using pmem bdev +configuration in SPDK by running functional tests FIO traffic verification +tests. + +## Configuration +Configuration in tests is to be done using example stub application +(spdk/example/bdev/io/bdev_io). +All possible management is done using RPC calls with the exception of +use of split bdevs which have to be configured in .conf file. + +Functional tests are executed as scenarios - sets of smaller test steps +in which results and return codes of RPC calls are validated. +Some configuration calls may also additionally be validated +by use of "get" (e.g. get_bdevs) RPC calls, which provide additional +information for veryfing results. +In some steps additional write/read operations will be performed on +PMEM bdevs in order to check IO path correct behavior. + +FIO traffic verification tests will serve as integration tests and will +be executed to config correct behavior of PMEM bdev when working with vhost, +nvmf_tgt and iscsi_tgt applications. + +## Functional tests + +### pmem_pool_info + +#### pmem_pool_info_tc1 +Negative test for checking pmem pool file. +Call with missing path argument. +Steps & expected results: +- Call pmem_pool_info with missing path argument +- Check that return code != 0 and error code = + +#### pmem_pool_info_tc2 +Negative test for checking pmem pool file. +Call with non-existant path argument. +Steps & expected results: +- Call pmem_pool_info with path argument that points to not existing file. +- Check that return code != 0 and error code = ENODEV + +#### pmem_pool_info_tc3 +Negative test for checking pmem pool file. +Call with other type of pmem pool file. +Steps & expected results: +- Using pmem utility tools create pool of OBJ type instead of BLK +(if needed utility tools are not available - create random file in filesystem) +- Call pmem_pool_info and point to file created in previous step. +- Check that return code != 0 and error code = ENODEV + +#### pmem_pool_info_tc4 +Positive test for checking pmem pool file. +Call with existing pmem pool file. +Steps & expected results: +- Call pmem_pool_info with path argument that points to existing file. +- Check that return code == 0 + +### create_pmem_pool +From libpmemblk documentation: +- PMEM block size has to be bigger than 512 internal blocks; if lower value +is used then PMEM library will silently round it up to 512 which is defined +in pmem/libpmemblk.h file as PMEMBLK_MIN_BLK. +- Total pool size cannot be less than 16MB which is defined i +pmem/libpmemblk.h file as PMEMBLK_MIN_POOL +- Total number of segments in PMEP pool file cannot be less than 256 + +#### create_pmem_pool_tc1 +Negative test case for creating a new pmem. +Call create_pmem_pool with missing arguments. +Steps & expected results: +- call create_pmem_pool without path argument +- call return code != 0 +- call pmem_pool_info and check that pmem pool file was not created +- call return code != 0 +- call create_pmem_pool with path but without size and block size arguments +- call return code != 0 +- call pmem_pool_info and check that pmem pool file was not created +- call return code != 0 +- call create_pmem_pool with path and size but without block size arguments +- call return code != 0 +- call pmem_pool_info and check that pmem pool file was not created +- call return code != 0 + +#### create_pmem_pool_tc2 +Negative test case for creating a new pmem. +Call create_pmem_pool with non existing path argument. +Steps & expected results: +- call create_pmem_pool with path that does not exist +- call return code != 0 +- call pmem_pool_info and check that pmem pool file was not created +- call return code != 0 + +#### create_pmem_pool_tc3 +Positive test case for creating a new pmem pool on disk space. +Steps & expected results: +- call create_pmem_pool with correct path argument, +blocksize=512 and total size=256MB +- call return code = 0 +- call pmem_pool_info and check that pmem file was created +- call return code = 0 +- call delete_pmem_pool on previously created pmem +- return code = 0 and no error code + +#### create_pmem_pool_tc4 +Positive test case for creating a new pmem pool in RAM space. +# TODO: Research test steps for creating a pool in RAM!!! +Steps & expected results: +- call create_pmem_pool with correct path argument, +blocksize=512 and total size=256MB +- call return code = 0 +- call pmem_pool_info and check that pmem file was created +- call return code = 0 +- call delete_pmem_pool on previously created pmem +- return code = 0 and no error code + +#### create_pmem_pool_tc5 +Negative test case for creating two pmems with same path. +Steps & expected results: +- call create_pmem_pool with correct path argument, +blocksize=512 and total size=256MB +- call return code = 0 +- call pmem_pool_info and check that pmem file was created +- call return code = 0 +- call create_pmem_pool with the same path argument as before, +blocksize=4096 and total size=512MB +- call return code != 0, error code = EEXIST +- call create_pmem_pool and check that first pmem pool file is still +available and not modified (block size and total size stay the same) +- call return code = 0 +- call delete_pmem_pool on first created pmem pool +- return code =0 and no error code + +#### create_pmem_pool_tc6 +Positive test case for creating pmem pool file with various block sizes. +Steps & expected results: +- call create_pmem_pool with correct path argument, total size=256MB +with different block size arguments - 1, 511, 512, 513, 1024, 4096, 128k and 256k +- call pmem_pool_info on each of created pmem pool and check if it was created; +For pool files created with block size <512 their block size should be rounded up +to 512; other pool files should have the same block size as specified in create +command +- call return code = 0; block sizes as expected +- call delete_pmem_pool on all created pool files + +#### create_pmem_pool_tc7 +Negative test case for creating pmem pool file with total size of less than 16MB. +Steps & expected results: +- call create_pmem_pool with correct path argument, block size=512 and +total size less than 16MB +- return code !=0 and error code !=0 +- call pmem_pool_info to verify pmem pool file was not created +- return code = 0 + +#### create_pmem_pool_tc8 +Negative test case for creating pmem pool file with less than 256 blocks. +Steps & expected results: +- call create_pmem_pool with correct path argument, block size=128k and +total size=30MB +- return code !=0 and error code !=0 +- call pmem_pool_info to verify pmem pool file was not created +- return code = 0 + +### delete_pmem_pool + +#### delete_pmem_pool_tc1 +Negative test case for deleting a pmem. +Call delete_pmem_pool on non-exisiting pmem. +Steps & expected results: +- call delete_pmem_pool on non-existing pmem. +- return code !=0 and error code = ENOENT + +#### delete_pmem_pool_tc2 +Negative test case for deleting a pmem. +Call delete_pmem_pool on a file of wrong type +Steps & expected results: +- Using pmem utility tools create pool of OBJ type instead of BLK +(if needed utility tools are not available - create random file in filesystem) +- Call delete_pmem_pool and point to file created in previous step. +- return code !=0 and error code = ENOTBLK + +#### delete_pmem_pool_tc3 +Positive test case for creating and deleting a pemem. +Steps & expected results: +- call create_pmem_pool with correct arguments +- return code = 0 and no error code +- using pmem_pool_info check that pmem was created +- return code = 0 and no error code +- call delete_pmem_pool on previously created pmem +- return code = 0 and no error code +- using pmem_pool_info check that pmem no longer exists +- return code !=0 and error code = ENODEV + +#### delete_pmem_pool_tc4 +Negative test case for creating and deleting a pemem. +Steps & expected results: +- run scenario from test case 3 +- call delete_pmem_pool on already deleted pmem pool +- return code !=0 and error code = ENODEV + +### construct_pmem_bdev + +#### construct_pmem_bdev_tc1 +Negative test for constructing new pmem bdev. +Call create_pmem_bdev with missing argument. +Steps & expected results: +- Call construct_pmem_bdev with missing path argument. +- Check that return code != 0 + +#### construct_pmem_bdev_tc2 +Negative test for constructing new pmem bdev. +Call construct_pmem_bdev with not existing path argument. +Steps & expected results: +- call construct_pmem_bdev with incorrect (not existing) path +- call return code != 0 and error code = ENODEV +- using get_bdevs check that no pmem bdev was created + +#### construct_pmem_bdev_tc3 +Negative test for constructing pmem bdevs with random file instead of pmemblk pool. +Steps & expected results: +- using a system tool (like dd) create a random file +- call construct_pmem_bdev with path pointing to that file +- return code != 0, error code = ENOTBLK + +#### construct_pmem_bdev_tc4 +Negative test for constructing pmem bdevs with pmemobj instead of pmemblk pool. +Steps & expected results: +- Using pmem utility tools create pool of OBJ type instead of BLK +(if needed utility tools are not available - create random file in filesystem) +- call construct_pmem_bdev with path pointing to that pool +- return code != 0, error code = ENOTBLK + +#### construct_pmem_bdev_tc5 +Positive test for constructing pmem bdev. +Steps & expected results: +- call create_pmem_pool with correct arguments +- return code = 0, no errors +- call pmem_pool_info and check if pmem files exists +- return code = 0, no errors +- call construct_pmem_bdev with with correct arguments to create a pmem bdev +- return code = 0, no errors +- using get_bdevs check that pmem bdev was created +- delete pmem bdev using delete_bdev +- return code = 0, no error code +- delete previously created pmem pool +- return code = 0, no error code + +#### construct_pmem_bdev_tc6 +Negative test for constructing pmem bdevs twice on the same pmem. +Steps & expected results: +- call create_pmem_pool with correct arguments +- return code = 0, no errors +- call pmem_pool_info and check if pmem files exists +- return code = 0, no errors +- call construct_pmem_bdev with with correct arguments to create a pmem bdev +- return code = 0, no errors +- using get_bdevs check that pmem bdev was created +- call construct_pmem_bdev again on the same pmem file +- return code != 0, error code = EEXIST +- delete pmem bdev using delete_bdev +- return code = 0, no error code +- delete previously created pmem pool +- return code = 0, no error code + +### delete_bdev + +#### delete_bdev_tc1 +Positive test for deleting pmem bdevs using common delete_bdev call. +Steps & expected results: +- construct malloc and aio bdevs (also NVMe if possible) +- all calls - return code = 0, no errors; bdevs created +- call create_pmem_pool with correct path argument, +block size=512, total size=256M +- return code = 0, no errors +- call pmem_pool_info and check if pmem file exists +- return code = 0, no errors +- call construct_pmem_bdev and create a pmem bdev +- return code = 0, no errors +- using get_bdevs check that pmem bdev was created +- delete pmem bdev using delete_bdev +- return code = 0, no errors +- using get_bdevs confirm that pmem bdev was deleted and other bdevs +were unaffected. + +#### delete_bdev_tc2 +Negative test for deleting pmem bdev twice. +Steps & expected results: +- call create_pmem_pool with correct path argument, +block size=512, total size=256M +- return code = 0, no errors +- call pmem_pool_info and check if pmem file exists +- return code = 0, no errors +- call construct_pmem_bdev and create a pmem bdev +- return code = 0, no errors +- using get_bdevs check that pmem bdev was created +- delete pmem bdev using delete_bdev +- return code = 0, no errors +- using get_bdevs confirm that pmem bdev was deleted +- delete pmem bdev using delete_bdev second time +- return code != 0, error code = ENODEV + + +## Integration tests +Description of integration tests which run FIO verification traffic against +pmem_bdevs used in vhost, iscsi_tgt and nvmf_tgt applications can be found in +test directories for these components: +- spdk/test/vhost +- spdk/test/nvmf +- spdk/test/iscsi_tgt diff --git a/src/spdk/test/rpc_client/.gitignore b/src/spdk/test/rpc_client/.gitignore new file mode 100644 index 00000000..e878ca3a --- /dev/null +++ b/src/spdk/test/rpc_client/.gitignore @@ -0,0 +1 @@ +rpc_client_test diff --git a/src/spdk/test/rpc_client/Makefile b/src/spdk/test/rpc_client/Makefile new file mode 100644 index 00000000..50e976a2 --- /dev/null +++ b/src/spdk/test/rpc_client/Makefile @@ -0,0 +1,56 @@ +# +# 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)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = rpc_client_test + +C_SRCS := rpc_client_test.c + +SPDK_LIB_LIST = jsonrpc json log util + +LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) + +all : $(APP) + @: + +$(APP) : $(OBJS) $(SPDK_LIB_FILES) + $(LINK_C) + +clean : + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/src/spdk/test/rpc_client/rpc_client.sh b/src/spdk/test/rpc_client/rpc_client.sh new file mode 100755 index 00000000..296a6fff --- /dev/null +++ b/src/spdk/test/rpc_client/rpc_client.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) + +set -e + +source $rootdir/test/common/autotest_common.sh + +function rpc_client_test() { + if [ $(uname -s) = Linux ]; then + local conf=$rootdir/test/bdev/bdev.conf.in + + if [ ! -e $conf ]; then + return 1 + fi + + $rootdir/test/app/bdev_svc/bdev_svc -i 0 -c ${conf} & + svc_pid=$! + echo "Process bdev_svc pid: $svc_pid" + waitforlisten $svc_pid + trap "killprocess $svc_pid" SIGINT SIGTERM EXIT + + $rootdir/test/rpc_client/rpc_client_test + + killprocess $svc_pid + fi + + return 0 +} + +timing_enter rpc_client +rpc_client_test +timing_exit rpc_client + +trap - SIGINT SIGTERM EXIT diff --git a/src/spdk/test/rpc_client/rpc_client_test.c b/src/spdk/test/rpc_client/rpc_client_test.c new file mode 100644 index 00000000..68f84713 --- /dev/null +++ b/src/spdk/test/rpc_client/rpc_client_test.c @@ -0,0 +1,118 @@ +/*- + * 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/event.h" +#include "spdk/jsonrpc.h" + +#define RPC_MAX_METHODS 200 + +static const char *g_rpcsock_addr = SPDK_DEFAULT_RPC_ADDR; +static int g_addr_family = AF_UNIX; + +#define RPC_MAX_METHODS 200 + +struct get_jsonrpc_methods_resp { + char *method_names[RPC_MAX_METHODS]; + size_t method_num; +}; + +static int +get_jsonrpc_method_json_parser(void *parser_ctx, + const struct spdk_json_val *result) +{ + struct get_jsonrpc_methods_resp *resp = parser_ctx; + + return spdk_json_decode_array(result, spdk_json_decode_string, resp->method_names, + RPC_MAX_METHODS, &resp->method_num, sizeof(char *)); +} + +static int +spdk_jsonrpc_client_check_rpc_method(struct spdk_jsonrpc_client *client, char *method_name) +{ + int rc, i; + struct get_jsonrpc_methods_resp resp = {}; + struct spdk_json_write_ctx *w; + struct spdk_jsonrpc_client_request *request; + + request = spdk_jsonrpc_client_create_request(); + if (request == NULL) { + return -ENOMEM; + } + + w = spdk_jsonrpc_begin_request(request, 1, "get_rpc_methods"); + spdk_jsonrpc_end_request(request, w); + spdk_jsonrpc_client_send_request(client, request); + spdk_jsonrpc_client_free_request(request); + + rc = spdk_jsonrpc_client_recv_response(client, get_jsonrpc_method_json_parser, &resp); + + if (rc) { + goto out; + } + + for (i = 0; i < (int)resp.method_num; i++) { + if (strcmp(method_name, resp.method_names[i]) == 0) { + rc = 0; + goto out; + } + } + + rc = -1; + +out: + for (i = 0; i < (int)resp.method_num; i++) { + SPDK_NOTICELOG("%s\n", resp.method_names[i]); + free(resp.method_names[i]); + } + + return rc; +} + +int main(int argc, char **argv) +{ + struct spdk_jsonrpc_client *client; + int rc; + char *method_name = "get_rpc_methods"; + + client = spdk_jsonrpc_client_connect(g_rpcsock_addr, g_addr_family); + if (!client) { + return EXIT_FAILURE; + } + + rc = spdk_jsonrpc_client_check_rpc_method(client, method_name); + + spdk_jsonrpc_client_close(client); + + return rc ? EXIT_FAILURE : 0; +} diff --git a/src/spdk/test/spdk_cunit.h b/src/spdk/test/spdk_cunit.h new file mode 100644 index 00000000..6696bff3 --- /dev/null +++ b/src/spdk/test/spdk_cunit.h @@ -0,0 +1,56 @@ +/*- + * 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. + */ + +#ifndef SPDK_CUNIT_H +#define SPDK_CUNIT_H + +#include "spdk/stdinc.h" + +#include <CUnit/Basic.h> + +/* + * CU_ASSERT_FATAL calls a function that does a longjmp() internally, but only for fatal asserts, + * so the function itself is not marked as noreturn. Add an abort() after the assert to help + * static analyzers figure out that it really doesn't return. + * The abort() will never actually execute. + */ +#define SPDK_CU_ASSERT_FATAL(cond) \ + do { \ + int result_ = !!(cond); \ + CU_ASSERT_FATAL(result_); \ + if (!result_) { \ + abort(); \ + } \ + } while (0) + +#endif /* SPDK_CUNIT_H */ diff --git a/src/spdk/test/spdkcli/common.sh b/src/spdk/test/spdkcli/common.sh new file mode 100644 index 00000000..80ea6ab1 --- /dev/null +++ b/src/spdk/test/spdkcli/common.sh @@ -0,0 +1,26 @@ +set -xe + +testdir=$(readlink -f $(dirname $0)) +SPDKCLI_BUILD_DIR=$(readlink -f $testdir/../..) +spdkcli_job="$SPDKCLI_BUILD_DIR/test/spdkcli/spdkcli_job.py" +. $SPDKCLI_BUILD_DIR/test/common/autotest_common.sh + +function on_error_exit() { + set +e + killprocess $spdk_tgt_pid + rm -f $testdir/${MATCH_FILE} $testdir/match_files/spdkcli_details_vhost.test /tmp/sample_aio /tmp/sample_pmem + print_backtrace + exit 1 +} + +function run_spdk_tgt() { + $SPDKCLI_BUILD_DIR/app/spdk_tgt/spdk_tgt -m 0x3 -p 0 -s 4096 & + spdk_tgt_pid=$! + waitforlisten $spdk_tgt_pid +} + +function check_match() { + $SPDKCLI_BUILD_DIR/scripts/spdkcli.py ll $SPDKCLI_BRANCH > $testdir/match_files/${MATCH_FILE} + $SPDKCLI_BUILD_DIR/test/app/match/match -v $testdir/match_files/${MATCH_FILE}.match + rm -f $testdir/match_files/${MATCH_FILE} +} diff --git a/src/spdk/test/spdkcli/iscsi.sh b/src/spdk/test/spdkcli/iscsi.sh new file mode 100755 index 00000000..e33aba85 --- /dev/null +++ b/src/spdk/test/spdkcli/iscsi.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -xe + +MATCH_FILE="spdkcli_iscsi.test" +SPDKCLI_BRANCH="/iscsi" +testdir=$(readlink -f $(dirname $0)) +. $testdir/common.sh +. $testdir/../iscsi_tgt/common.sh + +timing_enter spdkcli_iscsi +trap 'on_error_exit;' ERR + +timing_enter run_spdk_tgt +run_spdk_tgt +timing_exit run_spdk_tgt + +timing_enter spdkcli_create_iscsi_config +$spdkcli_job "/bdevs/malloc create 32 512 Malloc0" "Malloc0" True +$spdkcli_job "/bdevs/malloc create 32 512 Malloc1" "Malloc1" True +$spdkcli_job "/bdevs/malloc create 32 512 Malloc2" "Malloc2" True +$spdkcli_job "/bdevs/malloc create 32 512 Malloc3" "Malloc3" True +$spdkcli_job "/iscsi/portal_groups create 1 \"127.0.0.1:3261 127.0.0.1:3263@0x1\"" "host=127.0.0.1, port=3261" True +$spdkcli_job "/iscsi/portal_groups create 2 127.0.0.1:3262" "host=127.0.0.1, port=3262" True +$spdkcli_job "/iscsi/initiator_groups create 2 ANY 10.0.2.15/32" "hostname=ANY, netmask=10.0.2.15/32" True +$spdkcli_job "/iscsi/initiator_groups create 3 ANZ 10.0.2.15/32" "hostname=ANZ, netmask=10.0.2.15/32" True +$spdkcli_job "/iscsi/initiator_groups add_initiator 2 ANW 10.0.2.16/32" "hostname=ANW, netmask=10.0.2.16" True +$spdkcli_job "/iscsi/target_nodes create Target0 Target0_alias \"Malloc0:0 Malloc1:1\" 1:2 64 g=1" "Target0" True +$spdkcli_job "/iscsi/target_nodes create Target1 Target1_alias Malloc2:0 1:2 64 g=1" "Target1" True +$spdkcli_job "/iscsi/target_nodes/iqn.2016-06.io.spdk:Target0 add_pg_ig_maps \"1:3 2:2\"" "portal_group1 - initiator_group3" True +$spdkcli_job "/iscsi/target_nodes add_lun iqn.2016-06.io.spdk:Target1 Malloc3 2" "Malloc3" True +$spdkcli_job "/iscsi/auth_groups create 1 \"user:test secret:test muser:mutual_test msecret:mutual_test \ +user:test3 secret:test3 muser:mutual_test3 msecret:mutual_test3\"" "user=test3" True +$spdkcli_job "/iscsi/auth_groups add_secret 1 user=test2 secret=test2 muser=mutual_test2 msecret=mutual_test2" "user=test2" True +$spdkcli_job "/iscsi/target_nodes/iqn.2016-06.io.spdk:Target0 set_auth g=1 d=true" "disable_chap: True" True +$spdkcli_job "/iscsi/global_params set_auth g=1 d=true r=false" "disable_chap: True" True +$spdkcli_job "/iscsi ls" "Malloc" True +timing_exit spdkcli_create_iscsi_config + +timing_enter spdkcli_check_match +check_match +timing_exit spdkcli_check_match + +timing_enter spdkcli_clear_iscsi_config +$spdkcli_job "/iscsi/auth_groups delete_secret 1 test2" "user=test2" +$spdkcli_job "/iscsi/auth_groups delete 1" "user=test" +$spdkcli_job "/iscsi/target_nodes/iqn.2016-06.io.spdk:Target0 delete_pg_ig_maps \"1:3 2:2\"" "portal_group1 - initiator_group3" +$spdkcli_job "/iscsi/target_nodes delete iqn.2016-06.io.spdk:Target1" "Target1" +$spdkcli_job "/iscsi/target_nodes delete iqn.2016-06.io.spdk:Target0" "Target0" +$spdkcli_job "/iscsi/initiator_groups delete_initiator 2 ANW 10.0.2.16/32" "ANW" +$spdkcli_job "/iscsi/initiator_groups delete 3" "ANYZ" +$spdkcli_job "/iscsi/portal_groups delete 1" "127.0.0.1:3261" +$spdkcli_job "/bdevs/malloc delete Malloc3" "Malloc3" +$spdkcli_job "/bdevs/malloc delete Malloc2" "Malloc2" +$spdkcli_job "/bdevs/malloc delete Malloc1" "Malloc1" +$spdkcli_job "/bdevs/malloc delete Malloc0" "Malloc0" +timing_exit spdkcli_clear_iscsi_config + +killprocess $spdk_tgt_pid +timing_exit spdkcli_iscsi +report_test_completion spdk_cli diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_details_vhost.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_details_vhost.test.match new file mode 100644 index 00000000..8cbd9e80 --- /dev/null +++ b/src/spdk/test/spdkcli/match_files/spdkcli_details_vhost.test.match @@ -0,0 +1,28 @@ +{ + "aliases": [], + "assigned_rate_limits": { + "rw_ios_per_sec": $(N), + "rw_mbytes_per_sec": $(N) + }, + "block_size": $(N), + "claimed": false, + "driver_specific": { + "split": { + "base_bdev": "Nvme0n1", + "offset_blocks": $(N) + } + }, + "name": "Nvme0n1p0", + "num_blocks": $(N), + "product_name": "Split Disk", + "supported_io_types": { + "flush": $(S), + "nvme_admin": $(S), + "nvme_io": $(S), + "read": $(S), + "reset": $(S), + "unmap": $(S), + "write": $(S), + "write_zeroes": $(S) + } +} diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_iscsi.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_iscsi.test.match new file mode 100644 index 00000000..e48d8d98 --- /dev/null +++ b/src/spdk/test/spdkcli/match_files/spdkcli_iscsi.test.match @@ -0,0 +1,53 @@ +o- iscsi ..................................................................................................................... [...] + o- auth_groups ....................................................................................................... [Groups: 1] + | o- group1 ......................................................................................................... [Secrets: 2] + | o- user=test2, secret=test2, muser=mutual_test2, msecret=mutual_test2 .................................................. [...] + | o- user=test3, secret=test3, muser=mutual_test3, msecret=mutual_test3 .................................................. [...] + o- global_params ........................................................................................................... [...] + | o- allow_duplicated_isid: False .......................................................................................... [...] + | o- chap_group: 1 ......................................................................................................... [...] + | o- default_time2retain: 20 ............................................................................................... [...] + | o- default_time2wait: 2 .................................................................................................. [...] + | o- disable_chap: True .................................................................................................... [...] + | o- error_recovery_level: 0 ............................................................................................... [...] + | o- first_burst_length: 8192 .............................................................................................. [...] + | o- immediate_data: True .................................................................................................. [...] + | o- max_connections_per_session: 2 ........................................................................................ [...] + | o- max_queue_depth: 64 ................................................................................................... [...] + | o- max_sessions: 128 ..................................................................................................... [...] + | o- min_connections_per_core: 4 ........................................................................................... [...] + | o- mutual_chap: False .................................................................................................... [...] + | o- node_base: iqn.2016-06.io.spdk ........................................................................................ [...] + | o- nop_in_interval: 30 ................................................................................................... [...] + | o- nop_timeout: 60 ....................................................................................................... [...] + | o- require_chap: False ................................................................................................... [...] + o- initiator_groups ........................................................................................ [Initiator groups: 2] + | o- initiator_group2 ............................................................................................ [Initiators: 2] + | | o- hostname=ANW, netmask=$(N).$(N).$(N).$(N)/32 $(S) [...] + | | o- hostname=ANY, netmask=$(N).$(N).$(N).$(N)/32 $(S) [...] + | o- initiator_group3 ............................................................................................ [Initiators: 1] + | o- hostname=ANZ, netmask=$(N).$(N).$(N).$(N)/32 $(S) [...] + o- iscsi_connections ............................................................................................ [Connections: 0] + o- portal_groups .............................................................................................. [Portal groups: 2] + | o- portal_group1 .................................................................................................. [Portals: 2] + | | o- host=127.0.0.1, port=3261, cpumask=0x3 .............................................................................. [...] + | | o- host=127.0.0.1, port=3263, cpumask=0x1 .............................................................................. [...] + | o- portal_group2 .................................................................................................. [Portals: 1] + | o- host=127.0.0.1, port=3262, cpumask=0x3 .............................................................................. [...] + o- target_nodes ................................................................................................ [Target nodes: 2] + o- iqn.2016-06.io.spdk:Target0 ......................................................................... [Id: 0, QueueDepth: 64] + | o- auths ........................................ [disable_chap: True, require_chap: False, mutual_chap: False, chap_group: 1] + | o- luns ............................................................................................................ [Luns: 2] + | | o- lun 0 ......................................................................................................... [Malloc0] + | | o- lun 1 ......................................................................................................... [Malloc1] + | o- pg_ig_maps ................................................................................................ [Pg_ig_maps: 3] + | o- portal_group1 - initiator_group2 .................................................................................. [...] + | o- portal_group1 - initiator_group3 .................................................................................. [...] + | o- portal_group2 - initiator_group2 .................................................................................. [...] + o- iqn.2016-06.io.spdk:Target1 ......................................................................... [Id: 1, QueueDepth: 64] + o- auths ....................................... [disable_chap: False, require_chap: False, mutual_chap: False, chap_group: 1] + o- luns ............................................................................................................ [Luns: 2] + | o- lun 0 ......................................................................................................... [Malloc2] + | o- lun 2 ......................................................................................................... [Malloc3] + o- pg_ig_maps ................................................................................................ [Pg_ig_maps: 1] + o- portal_group1 - initiator_group2 .................................................................................. [...] diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_nvmf.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_nvmf.test.match new file mode 100644 index 00000000..95b36f64 --- /dev/null +++ b/src/spdk/test/spdkcli/match_files/spdkcli_nvmf.test.match @@ -0,0 +1,32 @@ +o- nvmf ...................................................................................................................... [...] + o- subsystem ..................................................................................................... [Subsystems: 4] + o- nqn.2014-08.org.nvmexpress.discovery ......................................................... [st=Discovery, Allow any host] + | o- hosts .......................................................................................................... [Hosts: 0] + | o- listen_addresses ........................................................................................... [Addresses: 0] + o- nqn.2014-08.org.spdk:cnode1 ...................................................... [sn=$(S), st=NVMe, Allow any host] + | o- hosts .......................................................................................................... [Hosts: 1] + | | o- nqn.2014-08.org.spdk:cnode2 ....................................................................................... [...] + | o- listen_addresses ........................................................................................... [Addresses: 3] + | | o- $(N).$(N).$(N).$(N):4260 $(S) [RDMA] + | | o- $(N).$(N).$(N).$(N):4261 $(S) [RDMA] + | | o- $(N).$(N).$(N).$(N):4262 $(S) [RDMA] + | o- namespaces ................................................................................................ [Namespaces: 4] + | o- Malloc3 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc3, 1] + | o- Malloc4 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc4, 2] + | o- Malloc5 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc5, 3] + | o- Malloc6 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc6, 4] + o- nqn.2014-08.org.spdk:cnode2 ...................................................... [sn=$(S), st=NVMe, Allow any host] + | o- hosts .......................................................................................................... [Hosts: 0] + | o- listen_addresses ........................................................................................... [Addresses: 1] + | | o- $(N).$(N).$(N).$(N):4260 $(S) [RDMA] + | o- namespaces ................................................................................................ [Namespaces: 1] + | o- Malloc2 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc2, 1] + o- nqn.2014-08.org.spdk:cnode3 ...................................................... [sn=$(S), st=NVMe, Allow any host] + o- hosts .......................................................................................................... [Hosts: 2] + | o- nqn.2014-08.org.spdk:cnode1 ....................................................................................... [...] + | o- nqn.2014-08.org.spdk:cnode2 ....................................................................................... [...] + o- listen_addresses ........................................................................................... [Addresses: 2] + | o- $(N).$(N).$(N).$(N):4260 $(S) [RDMA] + | o- $(N).$(N).$(N).$(N):4261 $(S) [RDMA] + o- namespaces ................................................................................................ [Namespaces: 1] + o- Malloc1 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc1, 1] diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_pmem.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_pmem.test.match new file mode 100644 index 00000000..0ee659b5 --- /dev/null +++ b/src/spdk/test/spdkcli/match_files/spdkcli_pmem.test.match @@ -0,0 +1,2 @@ +o- pmemblk .............................................................................................................. [Bdevs: 1] + o- pmem_bdev ........................................................................................... [Size=31.6M, Not claimed] diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_rbd.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_rbd.test.match new file mode 100644 index 00000000..ea748e90 --- /dev/null +++ b/src/spdk/test/spdkcli/match_files/spdkcli_rbd.test.match @@ -0,0 +1,2 @@ +o- rbd .................................................................................................................. [Bdevs: 1] + o- Ceph0 ............................................................................................. [Size=1000.0M, Not claimed] diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_vhost.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_vhost.test.match new file mode 100644 index 00000000..a1ed8dc7 --- /dev/null +++ b/src/spdk/test/spdkcli/match_files/spdkcli_vhost.test.match @@ -0,0 +1,75 @@ +o- / ......................................................................................................................... [...] + o- bdevs ................................................................................................................... [...] + | o- aio .............................................................................................................. [Bdevs: 1] + | | o- sample ........................................................................................... [Size=$(FP)M, Not claimed] + | o- error ............................................................................................................ [Bdevs: 1] + | | o- EE_Malloc1 ...................................................................................... [Size=$(FP)M, Not claimed] + | o- iscsi ............................................................................................................ [Bdevs: 0] + | o- logical_volume ................................................................................................... [Bdevs: 1] + | | o- $(X)-$(X)-$(X)-$(X)-$(X) .................................................. [lvs/lvol, Size=$(FP)M, Not claimed] + | o- malloc ........................................................................................................... [Bdevs: 4] + | | o- Malloc0 ............................................................................................. [Size=$(FP)M, Claimed] + | | o- Malloc1 ............................................................................................. [Size=$(FP)M, Claimed] + | | o- Malloc2 ......................................................................................... [Size=$(FP)M, Not claimed] + | | o- Malloc3 ......................................................................................... [Size=$(FP)M, Not claimed] + | o- null ............................................................................................................. [Bdevs: 1] + | | o- null_bdev ....................................................................................... [Size=$(FP)M, Not claimed] + | o- nvme ............................................................................................................. [Bdevs: 1] + | | o- Nvme0n1 $(S) [Size=$(FP)G, Claimed] + | o- pmemblk .......................................................................................................... [Bdevs: 0] + | o- rbd .............................................................................................................. [Bdevs: 0] + | o- split_disk ....................................................................................................... [Bdevs: 4] + | | o- Nvme0n1p0 $(S) [Size=$(FP)G, Not claimed] + | | o- Nvme0n1p1 $(S) [Size=$(FP)G, Not claimed] + | | o- Nvme0n1p2 $(S) [Size=$(FP)G, Not claimed] + | | o- Nvme0n1p3 $(S) [Size=$(FP)G, Not claimed] + | o- virtioblk_disk ................................................................................................... [Bdevs: 0] + | o- virtioscsi_disk .................................................................................................. [Bdevs: 0] + o- iscsi ................................................................................................................... [...] + | o- auth_groups ..................................................................................................... [Groups: 0] + | o- global_params ......................................................................................................... [...] + | | o- allow_duplicated_isid: False ........................................................................................ [...] + | | o- chap_group: 0 ....................................................................................................... [...] + | | o- default_time2retain: 20 ............................................................................................. [...] + | | o- default_time2wait: 2 ................................................................................................ [...] + | | o- disable_chap: False ................................................................................................. [...] + | | o- error_recovery_level: 0 ............................................................................................. [...] + | | o- first_burst_length: 8192 ............................................................................................ [...] + | | o- immediate_data: True ................................................................................................ [...] + | | o- max_connections_per_session: 2 ...................................................................................... [...] + | | o- max_queue_depth: 64 ................................................................................................. [...] + | | o- max_sessions: 128 ................................................................................................... [...] + | | o- min_connections_per_core: 4 ......................................................................................... [...] + | | o- mutual_chap: False .................................................................................................. [...] + | | o- node_base: iqn.2016-06.io.spdk ...................................................................................... [...] + | | o- nop_in_interval: 30 ................................................................................................. [...] + | | o- nop_timeout: 60 ..................................................................................................... [...] + | | o- require_chap: False ................................................................................................. [...] + | o- initiator_groups ...................................................................................... [Initiator groups: 0] + | o- iscsi_connections .......................................................................................... [Connections: 0] + | o- portal_groups ............................................................................................ [Portal groups: 0] + | o- target_nodes .............................................................................................. [Target nodes: 0] + o- lvol_stores .................................................................................................. [Lvol stores: 1] + | o- lvs ................................................................................................ [Size=$(FP)M, Free=$(FP)M] + o- nvmf .................................................................................................................... [...] + | o- subsystem ................................................................................................... [Subsystems: 1] + | o- nqn.2014-08.org.nvmexpress.discovery ....................................................... [st=Discovery, Allow any host] + | o- hosts ........................................................................................................ [Hosts: 0] + | o- listen_addresses ......................................................................................... [Addresses: 0] + o- vhost ................................................................................................................... [...] + o- block ................................................................................................................. [...] + | o- vhost_blk1 $(S) [$(S)] + | | o- Nvme0n1p0 ......................................................................................................... [...] + | o- vhost_blk2 $(S) [$(S), Readonly] + | o- Nvme0n1p1 ......................................................................................................... [...] + o- scsi .................................................................................................................. [...] + o- vhost_scsi1 $(S) [$(S)] + | o- Target_0 .......................................................................................... [LUNs: 1,TargetID: 0] + | o- Malloc2 ......................................................................................................... [...] + o- vhost_scsi2 $(S) [$(S)] + o- Target_0 .......................................................................................... [LUNs: 1,TargetID: 0] + | o- Malloc3 ......................................................................................................... [...] + o- Target_1 .......................................................................................... [LUNs: 1,TargetID: 1] + | o- Nvme0n1p2 ....................................................................................................... [...] + o- Target_2 .......................................................................................... [LUNs: 1,TargetID: 2] + o- Nvme0n1p3 ....................................................................................................... [...] diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_virtio_pci.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_virtio_pci.test.match new file mode 100644 index 00000000..0eca06e1 --- /dev/null +++ b/src/spdk/test/spdkcli/match_files/spdkcli_virtio_pci.test.match @@ -0,0 +1,18 @@ +o- bdevs ..................................................................................................................... [...] + o- aio ................................................................................................................ [Bdevs: 0] + o- error .............................................................................................................. [Bdevs: 0] + o- iscsi .............................................................................................................. [Bdevs: 0] + o- logical_volume ..................................................................................................... [Bdevs: 0] + o- malloc ............................................................................................................. [Bdevs: 2] + | o- Malloc0 ........................................................................................... [Size=32.0M, Not claimed] + | o- Malloc1 ........................................................................................... [Size=32.0M, Not claimed] + o- null ............................................................................................................... [Bdevs: 0] + o- nvme ............................................................................................................... [Bdevs: 0] + o- pmemblk ............................................................................................................ [Bdevs: 0] + o- rbd ................................................................................................................ [Bdevs: 0] + o- split_disk ......................................................................................................... [Bdevs: 0] + o- virtioblk_disk ..................................................................................................... [Bdevs: 1] + | o- virtioblk_pci $(S) [Size=$(FP)M, Not claimed] + o- virtioscsi_disk .................................................................................................... [Bdevs: 1] + o- virtioscsi_pci ............................................................................................... [$(S)] + o- virtioscsi_pcit0 $(S) [Size=$(FP)M, Not claimed] diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_virtio_user.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_virtio_user.test.match new file mode 100644 index 00000000..157938e7 --- /dev/null +++ b/src/spdk/test/spdkcli/match_files/spdkcli_virtio_user.test.match @@ -0,0 +1,8 @@ +o- vhost ..................................................................................................................... [...] + o- block ................................................................................................................... [...] + | o- sample_block $(S) [$(S)] + | o- Malloc1 ............................................................................................................. [...] + o- scsi .................................................................................................................... [...] + o- sample_scsi $(S) [$(S)] + o- Target_0 ............................................................................................ [LUNs: 1,TargetID: 0] + o- Malloc0 ........................................................................................................... [...] diff --git a/src/spdk/test/spdkcli/nvmf.sh b/src/spdk/test/spdkcli/nvmf.sh new file mode 100755 index 00000000..cf592a4e --- /dev/null +++ b/src/spdk/test/spdkcli/nvmf.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -xe + +MATCH_FILE="spdkcli_nvmf.test" +SPDKCLI_BRANCH="/nvmf" +testdir=$(readlink -f $(dirname $0)) +. $testdir/common.sh +. $testdir/../nvmf/common.sh + +timing_enter spdkcli_nvmf +trap 'on_error_exit; revert_soft_roce' ERR +rdma_device_init + +timing_enter run_spdk_tgt +run_spdk_tgt +timing_exit run_spdk_tgt + +RDMA_IP_LIST=$(get_available_rdma_ips) +NVMF_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1) + +timing_enter spdkcli_create_nvmf_config +$spdkcli_job "/bdevs/malloc create 32 512 Malloc1" "Malloc1" True +$spdkcli_job "/bdevs/malloc create 32 512 Malloc2" "Malloc2" True +$spdkcli_job "/bdevs/malloc create 32 512 Malloc3" "Malloc3" True +$spdkcli_job "/bdevs/malloc create 32 512 Malloc4" "Malloc4" True +$spdkcli_job "/bdevs/malloc create 32 512 Malloc5" "Malloc5" True +$spdkcli_job "/bdevs/malloc create 32 512 Malloc6" "Malloc6" True +$spdkcli_job "/nvmf/subsystem create nqn.2014-08.org.spdk:cnode1 N37SXV509SRW\ + max_namespaces=4 allow_any_host=True" "nqn.2014-08.org.spdk:cnode1" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces create Malloc3 1" "Malloc3" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces create Malloc4 2" "Malloc4" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/listen_addresses create \ + RDMA $NVMF_TARGET_IP 4260 IPv4" "$NVMF_TARGET_IP:4260" True +$spdkcli_job "/nvmf/subsystem create nqn.2014-08.org.spdk:cnode2 N37SXV509SRD\ + max_namespaces=2 allow_any_host=True" "nqn.2014-08.org.spdk:cnode2" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode2/namespaces create Malloc2" "Malloc2" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode2/listen_addresses create \ + RDMA $NVMF_TARGET_IP 4260 IPv4" "$NVMF_TARGET_IP:4260" True +$spdkcli_job "/nvmf/subsystem create nqn.2014-08.org.spdk:cnode3 N37SXV509SRR\ + max_namespaces=2 allow_any_host=True" "nqn.2014-08.org.spdk:cnode2" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode3/namespaces create Malloc1" "Malloc1" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode3/listen_addresses create \ + RDMA $NVMF_TARGET_IP 4260 IPv4" "$NVMF_TARGET_IP:4260" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode3/listen_addresses create \ + RDMA $NVMF_TARGET_IP 4261 IPv4" "$NVMF_TARGET_IP:4261" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode3/hosts create \ + nqn.2014-08.org.spdk:cnode1" "nqn.2014-08.org.spdk:cnode1" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode3/hosts create \ + nqn.2014-08.org.spdk:cnode2" "nqn.2014-08.org.spdk:cnode2" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1 allow_any_host True" "Allow any host" +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1 allow_any_host False" "Allow any host" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/listen_addresses create RDMA $NVMF_TARGET_IP 4261 IPv4" "$NVMF_TARGET_IP:4261" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/listen_addresses create RDMA $NVMF_TARGET_IP 4262 IPv4" "$NVMF_TARGET_IP:4262" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/hosts create nqn.2014-08.org.spdk:cnode2" "nqn.2014-08.org.spdk:cnode2" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces create Malloc5" "Malloc5" True +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces create Malloc6" "Malloc6" True +timing_exit spdkcli_create_nvmf_config + +timing_enter spdkcli_check_match +check_match +timing_exit spdkcli_check_match + +timing_enter spdkcli_clear_nvmf_config +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces delete nsid=1" "Malloc3" +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces delete nsid=2" "Malloc4" +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/hosts delete nqn.2014-08.org.spdk:cnode2" "nqn.2014-08.org.spdk:cnode2" +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/listen_addresses delete RDMA $NVMF_TARGET_IP 4262" "$NVMF_TARGET_IP:4262" +$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/listen_addresses delete RDMA $NVMF_TARGET_IP 4261" "$NVMF_TARGET_IP:4261" +$spdkcli_job "/nvmf/subsystem delete nqn.2014-08.org.spdk:cnode3" "nqn.2014-08.org.spdk:cnode3" +$spdkcli_job "/nvmf/subsystem delete nqn.2014-08.org.spdk:cnode2" "nqn.2014-08.org.spdk:cnode2" +$spdkcli_job "/nvmf/subsystem delete nqn.2014-08.org.spdk:cnode1" "nqn.2014-08.org.spdk:cnode1" +$spdkcli_job "/bdevs/malloc delete Malloc6" "Malloc6" +$spdkcli_job "/bdevs/malloc delete Malloc5" "Malloc5" +$spdkcli_job "/bdevs/malloc delete Malloc4" "Malloc4" +$spdkcli_job "/bdevs/malloc delete Malloc3" "Malloc3" +$spdkcli_job "/bdevs/malloc delete Malloc2" "Malloc2" +$spdkcli_job "/bdevs/malloc delete Malloc1" "Malloc1" +timing_exit spdkcli_clear_nvmf_config + +killprocess $spdk_tgt_pid +#revert_soft_roce +timing_exit spdkcli_nvmf +report_test_completion spdk_cli_nvmf diff --git a/src/spdk/test/spdkcli/pmem.sh b/src/spdk/test/spdkcli/pmem.sh new file mode 100755 index 00000000..bdac1873 --- /dev/null +++ b/src/spdk/test/spdkcli/pmem.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -xe + +MATCH_FILE="spdkcli_pmem.test" +SPDKCLI_BRANCH="/bdevs/pmemblk" +testdir=$(readlink -f $(dirname $0)) +. $testdir/common.sh + +timing_enter spdkcli_pmem +trap 'on_error_exit;' ERR + +timing_enter run_spdk_tgt +run_spdk_tgt +timing_exit run_spdk_tgt + +timing_enter spdkcli_create_pmem_config +$spdkcli_job "/bdevs/pmemblk create_pmem_pool /tmp/sample_pmem 32 512" "" True +$spdkcli_job "/bdevs/pmemblk create /tmp/sample_pmem pmem_bdev" "pmem_bdev" True +timing_exit spdkcli_create_pmem_config + +timing_enter spdkcli_check_match +check_match +timing_exit spdkcli_check_match + +timing_enter spdkcli_clear_pmem_config +$spdkcli_job "/bdevs/pmemblk delete pmem_bdev" "pmem_bdev" +$spdkcli_job "/bdevs/pmemblk delete_pmem_pool /tmp/sample_pmem" "" +rm -f /tmp/sample_pmem +timing_exit spdkcli_clear_pmem_config + +killprocess $spdk_tgt_pid +timing_exit spdkcli_pmem +report_test_completion spdk_cli diff --git a/src/spdk/test/spdkcli/rbd.sh b/src/spdk/test/spdkcli/rbd.sh new file mode 100755 index 00000000..352d0645 --- /dev/null +++ b/src/spdk/test/spdkcli/rbd.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -xe + +MATCH_FILE="spdkcli_rbd.test" +SPDKCLI_BRANCH="/bdevs/rbd" +testdir=$(readlink -f $(dirname $0)) +. $testdir/common.sh + +timing_enter spdk_cli_rbd +trap 'on_error_exit' ERR +timing_enter run_spdk_tgt +run_spdk_tgt +timing_exit run_spdk_tgt + +timing_enter spdkcli_create_rbd_config +trap 'rbd_cleanup; on_error_exit' ERR +rootdir=$(readlink -f $SPDKCLI_BUILD_DIR) +rbd_setup 127.0.0.1 +$spdkcli_job "/bdevs/rbd create rbd foo 512" "Ceph0" True +timing_exit spdkcli_create_rbd_config + +timing_enter spdkcli_check_match +check_match +timing_exit spdkcli_check_match + +timing_enter spdkcli_clear_rbd_config +$spdkcli_job "/bdevs/rbd delete Ceph0" "Ceph0" +rbd_cleanup +timing_exit spdkcli_clear_rbd_config + +killprocess $spdk_tgt_pid + +timing_exit spdk_cli_rbd +report_test_completion spdk_cli_rbd diff --git a/src/spdk/test/spdkcli/spdkcli_job.py b/src/spdk/test/spdkcli/spdkcli_job.py new file mode 100755 index 00000000..a2677f6a --- /dev/null +++ b/src/spdk/test/spdkcli/spdkcli_job.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import pexpect +import os +import sys + + +def execute_command(cmd, element=None, element_exists=False): + child.sendline(cmd) + child.expect("/>") + if "error response" in child.before.decode(): + print("Error in cmd: %s" % cmd) + exit(1) + ls_tree = cmd.split(" ")[0] + if ls_tree and element: + child.sendline("ls %s" % ls_tree) + child.expect("/>") + print("child: %s" % child.before.decode()) + if element_exists: + if element not in child.before.decode(): + print("Element %s not in list" % element) + exit(1) + else: + if element in child.before.decode(): + print("Element %s is in list" % element) + exit(1) + + +if __name__ == "__main__": + socket = "/var/tmp/spdk.sock" + if len(sys.argv) == 5: + socket = sys.argv[4] + testdir = os.path.dirname(os.path.realpath(sys.argv[0])) + child = pexpect.spawn(os.path.join(testdir, "../../scripts/spdkcli.py") + " -s %s" % socket) + child.expect(">") + child.sendline("cd /") + child.expect("/>") + + execute_command(*sys.argv[1:4]) diff --git a/src/spdk/test/spdkcli/vhost.sh b/src/spdk/test/spdkcli/vhost.sh new file mode 100755 index 00000000..c479857e --- /dev/null +++ b/src/spdk/test/spdkcli/vhost.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +set -xe + +MATCH_FILE="spdkcli_vhost.test" +SPDKCLI_BRANCH="/" +testdir=$(readlink -f $(dirname $0)) +. $testdir/../json_config/common.sh +. $testdir/common.sh + +timing_enter spdk_cli_vhost +trap 'on_error_exit' ERR +timing_enter run_spdk_tgt +run_spdk_tgt +timing_exit run_spdk_tgt + +timing_enter spdkcli_create_bdevs_config +$spdkcli_job "/bdevs/malloc create 32 512 Malloc0" "Malloc0" True +$spdkcli_job "/bdevs/malloc create 32 512 Malloc1" "Malloc1" True +$spdkcli_job "/bdevs/malloc create 32 512 Malloc2" "Malloc2" True +$spdkcli_job "/bdevs/malloc create 32 4096 Malloc3" "Malloc3" True +$spdkcli_job "/bdevs/error create Malloc1" "EE_Malloc1" True +$spdkcli_job "/bdevs/null create null_bdev 32 512" "null_bdev" True +dd if=/dev/zero of=/tmp/sample_aio bs=2048 count=5000 +$spdkcli_job "/bdevs/aio create sample /tmp/sample_aio 512" "sample" True +trtype=$($SPDKCLI_BUILD_DIR/scripts/gen_nvme.sh --json | jq -r '.config[].params | select(.name=="Nvme0").trtype') +traddr=$($SPDKCLI_BUILD_DIR/scripts/gen_nvme.sh --json | jq -r '.config[].params | select(.name=="Nvme0").traddr') +$spdkcli_job "/bdevs/nvme create Nvme0 $trtype $traddr" "Nvme0" True +$spdkcli_job "/bdevs/split_disk split_bdev Nvme0n1 4" "Nvme0n1p0" True +timing_exit spdkcli_create_bdevs_config + +timing_enter spdkcli_create_lvols_config +$spdkcli_job "/lvol_stores create lvs Malloc0" "lvs" True +$spdkcli_job "/bdevs/logical_volume create lvol 16 lvs" "lvs/lvol" True +timing_exit spdkcli_create_lvols_config + +timing_enter spdkcli_create_vhosts_config +$spdkcli_job "vhost/block create vhost_blk1 Nvme0n1p0" "Nvme0n1p0" True +$spdkcli_job "vhost/block create vhost_blk2 Nvme0n1p1 0x2 readonly" "Nvme0n1p1" True +$spdkcli_job "vhost/scsi create vhost_scsi1" "vhost_scsi1" True +$spdkcli_job "vhost/scsi create vhost_scsi2" "vhost_scsi2" True +$spdkcli_job "vhost/scsi/vhost_scsi1 add_lun 0 Malloc2" "Malloc2" True +$spdkcli_job "vhost/scsi/vhost_scsi2 add_lun 0 Malloc3" "Malloc3" True +$spdkcli_job "vhost/scsi/vhost_scsi2 add_lun 1 Nvme0n1p2" "Nvme0n1p2" True +$spdkcli_job "vhost/scsi/vhost_scsi2 add_lun 2 Nvme0n1p3" "Nvme0n1p3" True +timing_exit spdkcli_create_vhosts_config + +timing_enter spdkcli_check_match +check_match +timing_exit spdkcli_check_match + +timing_enter spdkcli_save_config +$spdkcli_job "save_config $testdir/config.json" +$spdkcli_job "save_subsystem_config $testdir/config_bdev.json bdev" +$spdkcli_job "save_subsystem_config $testdir/config_vhost.json vhost" +timing_exit spdkcli_save_config + +timing_enter spdkcli_check_match_details +$SPDKCLI_BUILD_DIR/scripts/spdkcli.py bdevs/split_disk/Nvme0n1p0 show_details | jq -r -S '.' > $testdir/match_files/spdkcli_details_vhost.test +$SPDKCLI_BUILD_DIR/test/app/match/match -v $testdir/match_files/spdkcli_details_vhost.test.match +rm -f $testdir/match_files/spdkcli_details_vhost.test +timing_exit spdkcli_check_match_details + +timing_enter spdkcli_clear_config +$spdkcli_job "vhost/scsi/vhost_scsi2 remove_target 2" "Nvme0n1p3" +$spdkcli_job "vhost/scsi/vhost_scsi2 remove_target 1" "Nvme0n1p2" +$spdkcli_job "vhost/scsi/vhost_scsi2 remove_target 0" "Malloc3" +$spdkcli_job "vhost/scsi/vhost_scsi1 remove_target 0" "Malloc2" +$spdkcli_job "vhost/scsi delete vhost_scsi2" "vhost_scsi2" +$spdkcli_job "vhost/scsi delete vhost_scsi1" "vhost_scsi1" +$spdkcli_job "vhost/block delete vhost_blk2" "vhost_blk2" +$spdkcli_job "vhost/block delete vhost_blk1" "vhost_blk1" +$spdkcli_job "/bdevs/split_disk destruct_split_bdev Nvme0n1" "Nvme0n1p0" +$spdkcli_job "/bdevs/aio delete sample" "sample" +$spdkcli_job "/bdevs/nvme delete Nvme0" "Nvme0" +$spdkcli_job "/bdevs/null delete null_bdev" "null_bdev" +$spdkcli_job "/bdevs/logical_volume delete lvs/lvol" "lvs/lvol" +$spdkcli_job "/lvol_stores delete lvs" "lvs" +$spdkcli_job "/bdevs/malloc delete Malloc0" "Malloc0" +$spdkcli_job "/bdevs/malloc delete Malloc1" "Malloc1" +$spdkcli_job "/bdevs/malloc delete Malloc2" "Malloc2" +$spdkcli_job "/bdevs/malloc delete Malloc3" "Malloc3" +timing_exit spdkcli_clear_config + +timing_enter spdkcli_load_config +$spdkcli_job "load_config $testdir/config.json" +$spdkcli_job "/lvol_stores create lvs Malloc0" "lvs" True +$spdkcli_job "/bdevs/logical_volume create lvol 16 lvs" "lvs/lvol" True +check_match +$spdk_clear_config_py clear_config +# FIXME: remove this sleep when NVMe driver will be fixed to wait for reset to complete +sleep 2 +$spdkcli_job "load_subsystem_config $testdir/config_bdev.json" +$spdkcli_job "load_subsystem_config $testdir/config_vhost.json" +$spdkcli_job "/lvol_stores create lvs Malloc0" "lvs" True +$spdkcli_job "/bdevs/logical_volume create lvol 16 lvs" "lvs/lvol" True +check_match +rm -f $testdir/config.json +rm -f $testdir/config_bdev.json +rm -f $testdir/config_vhost.json +rm -f /tmp/sample_aio +timing_exit spdkcli_load_config + +killprocess $spdk_tgt_pid + +timing_exit spdk_cli_vhost +report_test_completion spdk_cli_vhost diff --git a/src/spdk/test/spdkcli/virtio.sh b/src/spdk/test/spdkcli/virtio.sh new file mode 100755 index 00000000..fd8260be --- /dev/null +++ b/src/spdk/test/spdkcli/virtio.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -xe + +testdir=$(readlink -f $(dirname $0)) +. $testdir/common.sh + +trap 'killprocess $virtio_pid; on_error_exit' ERR +timing_enter spdk_cli_vhost + +timing_enter run_spdk_tgt +run_spdk_tgt +timing_exit run_spdk_tgt + +timing_enter run_spdk_virtio +$SPDKCLI_BUILD_DIR/app/spdk_tgt/spdk_tgt -m 0x4 -p 0 -g -u -s 1024 -r /var/tmp/virtio.sock & +virtio_pid=$! +waitforlisten $virtio_pid /var/tmp/virtio.sock +timing_exit run_spdk_virtio + +timing_enter spdkcli_create_virtio_pci_config +$spdkcli_job "/bdevs/malloc create 32 512 Malloc0" "Malloc0" True +$spdkcli_job "/bdevs/malloc create 32 512 Malloc1" "Malloc1" True +pci_blk=$(lspci -nn -D | grep '1af4:1001' | head -1 | awk '{print $1;}') +if [ ! -z $pci_blk ]; then + $spdkcli_job "/bdevs/virtioblk_disk create virtioblk_pci pci $pci_blk" "virtioblk_pci" True +fi +pci_scsi=$(lspci -nn -D | grep '1af4:1004' | head -1 | awk '{print $1;}') +if [ ! -z $pci_scsi ]; then + $spdkcli_job "/bdevs/virtioscsi_disk create virtioscsi_pci pci $pci_scsi" "virtioscsi_pci" True +fi +$spdkcli_job "/vhost/scsi create sample_scsi" "sample_scsi" True +$spdkcli_job "/vhost/scsi/sample_scsi add_lun 0 Malloc0" "Malloc0" True +$spdkcli_job "/vhost/block create sample_block Malloc1" "Malloc1" True +timing_exit spdkcli_create_virtio_pci_config + +timing_enter spdkcli_check_match +if [ ! -z $pci_blk ] && [ ! -z $pci_scsi ]; then + MATCH_FILE="spdkcli_virtio_pci.test" + SPDKCLI_BRANCH="/bdevs" + check_match +fi +timing_exit spdkcli_check_match + +timing_exit spdkcli_create_virtio_user_config +$spdkcli_job "/bdevs/virtioblk_disk create virtioblk_user user $testdir/../../sample_block" "virtioblk_user" True "/var/tmp/virtio.sock" +$spdkcli_job "/bdevs/virtioscsi_disk create virtioscsi_user user $testdir/../../sample_scsi" "virtioscsi_user" True "/var/tmp/virtio.sock" +timing_exit spdkcli_create_virtio_user_config + +timing_enter spdkcli_check_match +MATCH_FILE="spdkcli_virtio_user.test" +SPDKCLI_BRANCH="/vhost" +check_match +timing_exit spdkcli_check_match + +timing_enter spdkcli_clear_virtio_config +$spdkcli_job "/bdevs/virtioscsi_disk delete virtioscsi_user" "" False "/var/tmp/virtio.sock" +$spdkcli_job "/bdevs/virtioblk_disk delete virtioblk_user" "" False "/var/tmp/virtio.sock" +$spdkcli_job "/vhost/block delete sample_block" "sample_block" +$spdkcli_job "/vhost/scsi/sample_scsi remove_target 0" "Malloc0" +$spdkcli_job "/vhost/scsi delete sample_scsi" " sample_scsi" +if [ ! -z $pci_blk ]; then + $spdkcli_job "/bdevs/virtioblk_disk delete virtioblk_pci" "virtioblk_pci" +fi +if [ ! -z $pci_scsi ]; then + $spdkcli_job "/bdevs/virtioscsi_disk delete virtioscsi_pci" "virtioscsi_pci" +fi +$spdkcli_job "/bdevs/malloc delete Malloc0" "Malloc0" +$spdkcli_job "/bdevs/malloc delete Malloc1" "Malloc1" +timing_exit spdkcli_clear_virtio_config + +killprocess $virtio_pid +killprocess $spdk_tgt_pid + +timing_exit spdk_cli_vhost diff --git a/src/spdk/test/unit/Makefile b/src/spdk/test/unit/Makefile new file mode 100644 index 00000000..dbe663cb --- /dev/null +++ b/src/spdk/test/unit/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = include lib + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/include/Makefile b/src/spdk/test/unit/include/Makefile new file mode 100644 index 00000000..0ddc1524 --- /dev/null +++ b/src/spdk/test/unit/include/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = spdk + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/include/spdk/Makefile b/src/spdk/test/unit/include/spdk/Makefile new file mode 100644 index 00000000..d99c7d63 --- /dev/null +++ b/src/spdk/test/unit/include/spdk/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = histogram_data.h + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/include/spdk/histogram_data.h/.gitignore b/src/spdk/test/unit/include/spdk/histogram_data.h/.gitignore new file mode 100644 index 00000000..b2b36ff7 --- /dev/null +++ b/src/spdk/test/unit/include/spdk/histogram_data.h/.gitignore @@ -0,0 +1 @@ +histogram_ut diff --git a/src/spdk/test/unit/include/spdk/histogram_data.h/Makefile b/src/spdk/test/unit/include/spdk/histogram_data.h/Makefile new file mode 100644 index 00000000..08d4f052 --- /dev/null +++ b/src/spdk/test/unit/include/spdk/histogram_data.h/Makefile @@ -0,0 +1,39 @@ +# +# BSD LICENSE +# +# Copyright (c) 2016 FUJITSU LIMITED, 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 the copyright holder 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = histogram_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/include/spdk/histogram_data.h/histogram_ut.c b/src/spdk/test/unit/include/spdk/histogram_data.h/histogram_ut.c new file mode 100644 index 00000000..da67e2ef --- /dev/null +++ b/src/spdk/test/unit/include/spdk/histogram_data.h/histogram_ut.c @@ -0,0 +1,135 @@ +/*- + * 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_cunit.h" + +#include "spdk/histogram_data.h" +#include "spdk/util.h" + +uint64_t g_values[] = { + 1, + 10, + 1000, + 50000, + (1ULL << 63), + UINT64_MAX +}; + +uint64_t *g_values_end = &g_values[SPDK_COUNTOF(g_values)]; +uint64_t g_total; + +static void +check_values(void *ctx, uint64_t start, uint64_t end, uint64_t count, + uint64_t total, uint64_t so_far) +{ + uint64_t **values = ctx; + + if (count == 0) { + return; + } + + CU_ASSERT(so_far == (g_total + count)); + + /* + * The bucket for this iteration does not include end, but + * subtract one anyways to account for the last bucket + * which will have end = 0x0 (UINT64_MAX + 1). + */ + end--; + + while (1) { + CU_ASSERT(**values >= start); + /* + * We subtracted one from end above, so it's OK here for + * **values to equal end. + */ + CU_ASSERT(**values <= end); + g_total++; + count--; + (*values)++; + if (*values == g_values_end || **values > end) { + break; + } + } + + CU_ASSERT(count == 0); +} + +static void +histogram_test(void) +{ + struct spdk_histogram_data *h; + uint64_t *values = g_values; + uint32_t i; + + h = spdk_histogram_data_alloc(); + + for (i = 0; i < SPDK_COUNTOF(g_values); i++) { + spdk_histogram_data_tally(h, g_values[i]); + } + + g_total = 0; + spdk_histogram_data_iterate(h, check_values, &values); + + spdk_histogram_data_free(h); +} + +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("histogram", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "histogram_test", histogram_test) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/Makefile b/src/spdk/test/unit/lib/Makefile new file mode 100644 index 00000000..205835ee --- /dev/null +++ b/src/spdk/test/unit/lib/Makefile @@ -0,0 +1,47 @@ +# +# 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)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = bdev blob blobfs event ioat iscsi json jsonrpc log lvol nvme nvmf scsi sock thread util +ifeq ($(OS),Linux) +DIRS-$(CONFIG_VHOST) += vhost +endif + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/bdev/Makefile b/src/spdk/test/unit/lib/bdev/Makefile new file mode 100644 index 00000000..61efba78 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/Makefile @@ -0,0 +1,50 @@ +# +# 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)/../../../..) +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 + +ifeq ($(CONFIG_CRYPTO),y) +DIRS-y += crypto.c +endif + +DIRS-$(CONFIG_PMDK) += pmem + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/bdev/bdev.c/.gitignore b/src/spdk/test/unit/lib/bdev/bdev.c/.gitignore new file mode 100644 index 00000000..a5a22d0d --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/bdev.c/.gitignore @@ -0,0 +1 @@ +bdev_ut diff --git a/src/spdk/test/unit/lib/bdev/bdev.c/Makefile b/src/spdk/test/unit/lib/bdev/bdev.c/Makefile new file mode 100644 index 00000000..384fa27a --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/bdev.c/Makefile @@ -0,0 +1,39 @@ +# +# BSD LICENSE +# +# Copyright (c) 2016 FUJITSU LIMITED, 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 the copyright holder 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = bdev_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/bdev/bdev.c/bdev_ut.c b/src/spdk/test/unit/lib/bdev/bdev.c/bdev_ut.c new file mode 100644 index 00000000..3c14f712 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/bdev.c/bdev_ut.c @@ -0,0 +1,1214 @@ +/*- + * 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_cunit.h" + +#include "common/lib/test_env.c" +#include "unit/lib/json_mock.c" + +#include "spdk/config.h" +/* HACK: disable VTune integration so the unit test doesn't need VTune headers and libs to build */ +#undef SPDK_CONFIG_VTUNE + +#include "bdev/bdev.c" + +DEFINE_STUB(spdk_conf_find_section, struct spdk_conf_section *, (struct spdk_conf *cp, + const char *name), NULL); +DEFINE_STUB(spdk_conf_section_get_nmval, char *, + (struct spdk_conf_section *sp, const char *key, int idx1, int idx2), NULL); +DEFINE_STUB(spdk_conf_section_get_intval, int, (struct spdk_conf_section *sp, const char *key), -1); + +struct spdk_trace_histories *g_trace_histories; +DEFINE_STUB_V(spdk_trace_add_register_fn, (struct spdk_trace_register_fn *reg_fn)); +DEFINE_STUB_V(spdk_trace_register_owner, (uint8_t type, char id_prefix)); +DEFINE_STUB_V(spdk_trace_register_object, (uint8_t type, char id_prefix)); +DEFINE_STUB_V(spdk_trace_register_description, (const char *name, const char *short_name, + uint16_t tpoint_id, uint8_t owner_type, + uint8_t object_type, uint8_t new_object, + uint8_t arg1_is_ptr, const char *arg1_name)); +DEFINE_STUB_V(_spdk_trace_record, (uint64_t tsc, uint16_t tpoint_id, uint16_t poller_id, + uint32_t size, uint64_t object_id, uint64_t arg1)); + +static void +_bdev_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx) +{ + fn(ctx); +} + +void +spdk_scsi_nvme_translate(const struct spdk_bdev_io *bdev_io, + int *sc, int *sk, int *asc, int *ascq) +{ +} + +static int +null_init(void) +{ + return 0; +} + +static int +null_clean(void) +{ + return 0; +} + +static int +stub_destruct(void *ctx) +{ + return 0; +} + +struct ut_expected_io { + uint8_t type; + uint64_t offset; + uint64_t length; + int iovcnt; + struct iovec iov[BDEV_IO_NUM_CHILD_IOV]; + TAILQ_ENTRY(ut_expected_io) link; +}; + +struct bdev_ut_channel { + TAILQ_HEAD(, spdk_bdev_io) outstanding_io; + uint32_t outstanding_io_count; + TAILQ_HEAD(, ut_expected_io) expected_io; +}; + +static bool g_io_done; +static enum spdk_bdev_io_status g_io_status; +static uint32_t g_bdev_ut_io_device; +static struct bdev_ut_channel *g_bdev_ut_channel; + +static struct ut_expected_io * +ut_alloc_expected_io(uint8_t type, uint64_t offset, uint64_t length, int iovcnt) +{ + struct ut_expected_io *expected_io; + + expected_io = calloc(1, sizeof(*expected_io)); + SPDK_CU_ASSERT_FATAL(expected_io != NULL); + + expected_io->type = type; + expected_io->offset = offset; + expected_io->length = length; + expected_io->iovcnt = iovcnt; + + return expected_io; +} + +static void +ut_expected_io_set_iov(struct ut_expected_io *expected_io, int pos, void *base, size_t len) +{ + expected_io->iov[pos].iov_base = base; + expected_io->iov[pos].iov_len = len; +} + +static void +stub_submit_request(struct spdk_io_channel *_ch, struct spdk_bdev_io *bdev_io) +{ + struct bdev_ut_channel *ch = spdk_io_channel_get_ctx(_ch); + struct ut_expected_io *expected_io; + struct iovec *iov, *expected_iov; + int i; + + TAILQ_INSERT_TAIL(&ch->outstanding_io, bdev_io, module_link); + ch->outstanding_io_count++; + + expected_io = TAILQ_FIRST(&ch->expected_io); + if (expected_io == NULL) { + return; + } + TAILQ_REMOVE(&ch->expected_io, expected_io, link); + + if (expected_io->type != SPDK_BDEV_IO_TYPE_INVALID) { + CU_ASSERT(bdev_io->type == expected_io->type); + } + + if (expected_io->length == 0) { + free(expected_io); + return; + } + + CU_ASSERT(expected_io->offset == bdev_io->u.bdev.offset_blocks); + CU_ASSERT(expected_io->length = bdev_io->u.bdev.num_blocks); + + if (expected_io->iovcnt == 0) { + free(expected_io); + /* UNMAP, WRITE_ZEROES and FLUSH don't have iovs, so we can just return now. */ + return; + } + + CU_ASSERT(expected_io->iovcnt == bdev_io->u.bdev.iovcnt); + for (i = 0; i < expected_io->iovcnt; i++) { + iov = &bdev_io->u.bdev.iovs[i]; + expected_iov = &expected_io->iov[i]; + CU_ASSERT(iov->iov_len == expected_iov->iov_len); + CU_ASSERT(iov->iov_base == expected_iov->iov_base); + } + + free(expected_io); +} + +static uint32_t +stub_complete_io(uint32_t num_to_complete) +{ + struct bdev_ut_channel *ch = g_bdev_ut_channel; + struct spdk_bdev_io *bdev_io; + uint32_t num_completed = 0; + + while (num_completed < num_to_complete) { + if (TAILQ_EMPTY(&ch->outstanding_io)) { + break; + } + bdev_io = TAILQ_FIRST(&ch->outstanding_io); + TAILQ_REMOVE(&ch->outstanding_io, bdev_io, module_link); + ch->outstanding_io_count--; + spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_SUCCESS); + num_completed++; + } + + return num_completed; +} + +static struct spdk_io_channel * +bdev_ut_get_io_channel(void *ctx) +{ + return spdk_get_io_channel(&g_bdev_ut_io_device); +} + +static bool +stub_io_type_supported(void *_bdev, enum spdk_bdev_io_type io_type) +{ + return true; +} + +static struct spdk_bdev_fn_table fn_table = { + .destruct = stub_destruct, + .submit_request = stub_submit_request, + .get_io_channel = bdev_ut_get_io_channel, + .io_type_supported = stub_io_type_supported, +}; + +static int +bdev_ut_create_ch(void *io_device, void *ctx_buf) +{ + struct bdev_ut_channel *ch = ctx_buf; + + CU_ASSERT(g_bdev_ut_channel == NULL); + g_bdev_ut_channel = ch; + + TAILQ_INIT(&ch->outstanding_io); + ch->outstanding_io_count = 0; + TAILQ_INIT(&ch->expected_io); + return 0; +} + +static void +bdev_ut_destroy_ch(void *io_device, void *ctx_buf) +{ + CU_ASSERT(g_bdev_ut_channel != NULL); + g_bdev_ut_channel = NULL; +} + +static int +bdev_ut_module_init(void) +{ + spdk_io_device_register(&g_bdev_ut_io_device, bdev_ut_create_ch, bdev_ut_destroy_ch, + sizeof(struct bdev_ut_channel), NULL); + return 0; +} + +static void +bdev_ut_module_fini(void) +{ + spdk_io_device_unregister(&g_bdev_ut_io_device, NULL); +} + +struct spdk_bdev_module bdev_ut_if = { + .name = "bdev_ut", + .module_init = bdev_ut_module_init, + .module_fini = bdev_ut_module_fini, +}; + +static void vbdev_ut_examine(struct spdk_bdev *bdev); + +static int +vbdev_ut_module_init(void) +{ + return 0; +} + +static void +vbdev_ut_module_fini(void) +{ +} + +struct spdk_bdev_module vbdev_ut_if = { + .name = "vbdev_ut", + .module_init = vbdev_ut_module_init, + .module_fini = vbdev_ut_module_fini, + .examine_config = vbdev_ut_examine, +}; + +SPDK_BDEV_MODULE_REGISTER(&bdev_ut_if) +SPDK_BDEV_MODULE_REGISTER(&vbdev_ut_if) + +static void +vbdev_ut_examine(struct spdk_bdev *bdev) +{ + spdk_bdev_module_examine_done(&vbdev_ut_if); +} + +static struct spdk_bdev * +allocate_bdev(char *name) +{ + struct spdk_bdev *bdev; + int rc; + + bdev = calloc(1, sizeof(*bdev)); + SPDK_CU_ASSERT_FATAL(bdev != NULL); + + bdev->name = name; + bdev->fn_table = &fn_table; + bdev->module = &bdev_ut_if; + bdev->blockcnt = 1024; + bdev->blocklen = 512; + + rc = spdk_bdev_register(bdev); + CU_ASSERT(rc == 0); + + return bdev; +} + +static struct spdk_bdev * +allocate_vbdev(char *name, struct spdk_bdev *base1, struct spdk_bdev *base2) +{ + struct spdk_bdev *bdev; + struct spdk_bdev *array[2]; + int rc; + + bdev = calloc(1, sizeof(*bdev)); + SPDK_CU_ASSERT_FATAL(bdev != NULL); + + bdev->name = name; + bdev->fn_table = &fn_table; + bdev->module = &vbdev_ut_if; + + /* vbdev must have at least one base bdev */ + CU_ASSERT(base1 != NULL); + + array[0] = base1; + array[1] = base2; + + rc = spdk_vbdev_register(bdev, array, base2 == NULL ? 1 : 2); + CU_ASSERT(rc == 0); + + return bdev; +} + +static void +free_bdev(struct spdk_bdev *bdev) +{ + spdk_bdev_unregister(bdev, NULL, NULL); + memset(bdev, 0xFF, sizeof(*bdev)); + free(bdev); +} + +static void +free_vbdev(struct spdk_bdev *bdev) +{ + spdk_bdev_unregister(bdev, NULL, NULL); + memset(bdev, 0xFF, sizeof(*bdev)); + free(bdev); +} + +static void +get_device_stat_cb(struct spdk_bdev *bdev, struct spdk_bdev_io_stat *stat, void *cb_arg, int rc) +{ + const char *bdev_name; + + CU_ASSERT(bdev != NULL); + CU_ASSERT(rc == 0); + bdev_name = spdk_bdev_get_name(bdev); + CU_ASSERT_STRING_EQUAL(bdev_name, "bdev0"); + + free(stat); + free_bdev(bdev); +} + +static void +get_device_stat_test(void) +{ + struct spdk_bdev *bdev; + struct spdk_bdev_io_stat *stat; + + bdev = allocate_bdev("bdev0"); + stat = calloc(1, sizeof(struct spdk_bdev_io_stat)); + if (stat == NULL) { + free_bdev(bdev); + return; + } + spdk_bdev_get_device_stat(bdev, stat, get_device_stat_cb, NULL); +} + +static void +open_write_test(void) +{ + struct spdk_bdev *bdev[9]; + struct spdk_bdev_desc *desc[9] = {}; + int rc; + + /* + * Create a tree of bdevs to test various open w/ write cases. + * + * bdev0 through bdev3 are physical block devices, such as NVMe + * namespaces or Ceph block devices. + * + * bdev4 is a virtual bdev with multiple base bdevs. This models + * caching or RAID use cases. + * + * bdev5 through bdev7 are all virtual bdevs with the same base + * bdev (except bdev7). This models partitioning or logical volume + * use cases. + * + * bdev7 is a virtual bdev with multiple base bdevs. One of base bdevs + * (bdev2) is shared with other virtual bdevs: bdev5 and bdev6. This + * models caching, RAID, partitioning or logical volumes use cases. + * + * bdev8 is a virtual bdev with multiple base bdevs, but these + * base bdevs are themselves virtual bdevs. + * + * bdev8 + * | + * +----------+ + * | | + * bdev4 bdev5 bdev6 bdev7 + * | | | | + * +---+---+ +---+ + +---+---+ + * | | \ | / \ + * bdev0 bdev1 bdev2 bdev3 + */ + + bdev[0] = allocate_bdev("bdev0"); + rc = spdk_bdev_module_claim_bdev(bdev[0], NULL, &bdev_ut_if); + CU_ASSERT(rc == 0); + + bdev[1] = allocate_bdev("bdev1"); + rc = spdk_bdev_module_claim_bdev(bdev[1], NULL, &bdev_ut_if); + CU_ASSERT(rc == 0); + + bdev[2] = allocate_bdev("bdev2"); + rc = spdk_bdev_module_claim_bdev(bdev[2], NULL, &bdev_ut_if); + CU_ASSERT(rc == 0); + + bdev[3] = allocate_bdev("bdev3"); + rc = spdk_bdev_module_claim_bdev(bdev[3], NULL, &bdev_ut_if); + CU_ASSERT(rc == 0); + + bdev[4] = allocate_vbdev("bdev4", bdev[0], bdev[1]); + rc = spdk_bdev_module_claim_bdev(bdev[4], NULL, &bdev_ut_if); + CU_ASSERT(rc == 0); + + bdev[5] = allocate_vbdev("bdev5", bdev[2], NULL); + rc = spdk_bdev_module_claim_bdev(bdev[5], NULL, &bdev_ut_if); + CU_ASSERT(rc == 0); + + bdev[6] = allocate_vbdev("bdev6", bdev[2], NULL); + + bdev[7] = allocate_vbdev("bdev7", bdev[2], bdev[3]); + + bdev[8] = allocate_vbdev("bdev8", bdev[4], bdev[5]); + + /* Open bdev0 read-only. This should succeed. */ + rc = spdk_bdev_open(bdev[0], false, NULL, NULL, &desc[0]); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(desc[0] != NULL); + spdk_bdev_close(desc[0]); + + /* + * Open bdev1 read/write. This should fail since bdev1 has been claimed + * by a vbdev module. + */ + rc = spdk_bdev_open(bdev[1], true, NULL, NULL, &desc[1]); + CU_ASSERT(rc == -EPERM); + + /* + * Open bdev4 read/write. This should fail since bdev3 has been claimed + * by a vbdev module. + */ + rc = spdk_bdev_open(bdev[4], true, NULL, NULL, &desc[4]); + CU_ASSERT(rc == -EPERM); + + /* Open bdev4 read-only. This should succeed. */ + rc = spdk_bdev_open(bdev[4], false, NULL, NULL, &desc[4]); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(desc[4] != NULL); + spdk_bdev_close(desc[4]); + + /* + * Open bdev8 read/write. This should succeed since it is a leaf + * bdev. + */ + rc = spdk_bdev_open(bdev[8], true, NULL, NULL, &desc[8]); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(desc[8] != NULL); + spdk_bdev_close(desc[8]); + + /* + * Open bdev5 read/write. This should fail since bdev4 has been claimed + * by a vbdev module. + */ + rc = spdk_bdev_open(bdev[5], true, NULL, NULL, &desc[5]); + CU_ASSERT(rc == -EPERM); + + /* Open bdev4 read-only. This should succeed. */ + rc = spdk_bdev_open(bdev[5], false, NULL, NULL, &desc[5]); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(desc[5] != NULL); + spdk_bdev_close(desc[5]); + + free_vbdev(bdev[8]); + + free_vbdev(bdev[5]); + free_vbdev(bdev[6]); + free_vbdev(bdev[7]); + + free_vbdev(bdev[4]); + + free_bdev(bdev[0]); + free_bdev(bdev[1]); + free_bdev(bdev[2]); + free_bdev(bdev[3]); +} + +static void +bytes_to_blocks_test(void) +{ + struct spdk_bdev bdev; + uint64_t offset_blocks, num_blocks; + + memset(&bdev, 0, sizeof(bdev)); + + bdev.blocklen = 512; + + /* All parameters valid */ + offset_blocks = 0; + num_blocks = 0; + CU_ASSERT(spdk_bdev_bytes_to_blocks(&bdev, 512, &offset_blocks, 1024, &num_blocks) == 0); + CU_ASSERT(offset_blocks == 1); + CU_ASSERT(num_blocks == 2); + + /* Offset not a block multiple */ + CU_ASSERT(spdk_bdev_bytes_to_blocks(&bdev, 3, &offset_blocks, 512, &num_blocks) != 0); + + /* Length not a block multiple */ + CU_ASSERT(spdk_bdev_bytes_to_blocks(&bdev, 512, &offset_blocks, 3, &num_blocks) != 0); +} + +static void +num_blocks_test(void) +{ + struct spdk_bdev bdev; + struct spdk_bdev_desc *desc = NULL; + int rc; + + memset(&bdev, 0, sizeof(bdev)); + bdev.name = "num_blocks"; + bdev.fn_table = &fn_table; + bdev.module = &bdev_ut_if; + spdk_bdev_register(&bdev); + spdk_bdev_notify_blockcnt_change(&bdev, 50); + + /* Growing block number */ + CU_ASSERT(spdk_bdev_notify_blockcnt_change(&bdev, 70) == 0); + /* Shrinking block number */ + CU_ASSERT(spdk_bdev_notify_blockcnt_change(&bdev, 30) == 0); + + /* In case bdev opened */ + rc = spdk_bdev_open(&bdev, false, NULL, NULL, &desc); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(desc != NULL); + + /* Growing block number */ + CU_ASSERT(spdk_bdev_notify_blockcnt_change(&bdev, 80) == 0); + /* Shrinking block number */ + CU_ASSERT(spdk_bdev_notify_blockcnt_change(&bdev, 20) != 0); + + spdk_bdev_close(desc); + spdk_bdev_unregister(&bdev, NULL, NULL); +} + +static void +io_valid_test(void) +{ + struct spdk_bdev bdev; + + memset(&bdev, 0, sizeof(bdev)); + + bdev.blocklen = 512; + spdk_bdev_notify_blockcnt_change(&bdev, 100); + + /* All parameters valid */ + CU_ASSERT(spdk_bdev_io_valid_blocks(&bdev, 1, 2) == true); + + /* Last valid block */ + CU_ASSERT(spdk_bdev_io_valid_blocks(&bdev, 99, 1) == true); + + /* Offset past end of bdev */ + CU_ASSERT(spdk_bdev_io_valid_blocks(&bdev, 100, 1) == false); + + /* Offset + length past end of bdev */ + CU_ASSERT(spdk_bdev_io_valid_blocks(&bdev, 99, 2) == false); + + /* Offset near end of uint64_t range (2^64 - 1) */ + CU_ASSERT(spdk_bdev_io_valid_blocks(&bdev, 18446744073709551615ULL, 1) == false); +} + +static void +alias_add_del_test(void) +{ + struct spdk_bdev *bdev[3]; + int rc; + + /* Creating and registering bdevs */ + bdev[0] = allocate_bdev("bdev0"); + SPDK_CU_ASSERT_FATAL(bdev[0] != 0); + + bdev[1] = allocate_bdev("bdev1"); + SPDK_CU_ASSERT_FATAL(bdev[1] != 0); + + bdev[2] = allocate_bdev("bdev2"); + SPDK_CU_ASSERT_FATAL(bdev[2] != 0); + + /* + * Trying adding an alias identical to name. + * Alias is identical to name, so it can not be added to aliases list + */ + rc = spdk_bdev_alias_add(bdev[0], bdev[0]->name); + CU_ASSERT(rc == -EEXIST); + + /* + * Trying to add empty alias, + * this one should fail + */ + rc = spdk_bdev_alias_add(bdev[0], NULL); + CU_ASSERT(rc == -EINVAL); + + /* Trying adding same alias to two different registered bdevs */ + + /* Alias is used first time, so this one should pass */ + rc = spdk_bdev_alias_add(bdev[0], "proper alias 0"); + CU_ASSERT(rc == 0); + + /* Alias was added to another bdev, so this one should fail */ + rc = spdk_bdev_alias_add(bdev[1], "proper alias 0"); + CU_ASSERT(rc == -EEXIST); + + /* Alias is used first time, so this one should pass */ + rc = spdk_bdev_alias_add(bdev[1], "proper alias 1"); + CU_ASSERT(rc == 0); + + /* Trying removing an alias from registered bdevs */ + + /* Alias is not on a bdev aliases list, so this one should fail */ + rc = spdk_bdev_alias_del(bdev[0], "not existing"); + CU_ASSERT(rc == -ENOENT); + + /* Alias is present on a bdev aliases list, so this one should pass */ + rc = spdk_bdev_alias_del(bdev[0], "proper alias 0"); + CU_ASSERT(rc == 0); + + /* Alias is present on a bdev aliases list, so this one should pass */ + rc = spdk_bdev_alias_del(bdev[1], "proper alias 1"); + CU_ASSERT(rc == 0); + + /* Trying to remove name instead of alias, so this one should fail, name cannot be changed or removed */ + rc = spdk_bdev_alias_del(bdev[0], bdev[0]->name); + CU_ASSERT(rc != 0); + + /* Trying to del all alias from empty alias list */ + spdk_bdev_alias_del_all(bdev[2]); + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&bdev[2]->aliases)); + + /* Trying to del all alias from non-empty alias list */ + rc = spdk_bdev_alias_add(bdev[2], "alias0"); + CU_ASSERT(rc == 0); + rc = spdk_bdev_alias_add(bdev[2], "alias1"); + CU_ASSERT(rc == 0); + spdk_bdev_alias_del_all(bdev[2]); + CU_ASSERT(TAILQ_EMPTY(&bdev[2]->aliases)); + + /* Unregister and free bdevs */ + spdk_bdev_unregister(bdev[0], NULL, NULL); + spdk_bdev_unregister(bdev[1], NULL, NULL); + spdk_bdev_unregister(bdev[2], NULL, NULL); + + free(bdev[0]); + free(bdev[1]); + free(bdev[2]); +} + +static void +io_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + g_io_done = true; + g_io_status = bdev_io->internal.status; + spdk_bdev_free_io(bdev_io); +} + +static void +bdev_init_cb(void *arg, int rc) +{ + CU_ASSERT(rc == 0); +} + +static void +bdev_fini_cb(void *arg) +{ +} + +struct bdev_ut_io_wait_entry { + struct spdk_bdev_io_wait_entry entry; + struct spdk_io_channel *io_ch; + struct spdk_bdev_desc *desc; + bool submitted; +}; + +static void +io_wait_cb(void *arg) +{ + struct bdev_ut_io_wait_entry *entry = arg; + int rc; + + rc = spdk_bdev_read_blocks(entry->desc, entry->io_ch, NULL, 0, 1, io_done, NULL); + CU_ASSERT(rc == 0); + entry->submitted = true; +} + +static void +bdev_io_wait_test(void) +{ + struct spdk_bdev *bdev; + struct spdk_bdev_desc *desc = NULL; + struct spdk_io_channel *io_ch; + struct spdk_bdev_opts bdev_opts = { + .bdev_io_pool_size = 4, + .bdev_io_cache_size = 2, + }; + struct bdev_ut_io_wait_entry io_wait_entry; + struct bdev_ut_io_wait_entry io_wait_entry2; + int rc; + + rc = spdk_bdev_set_opts(&bdev_opts); + CU_ASSERT(rc == 0); + spdk_bdev_initialize(bdev_init_cb, NULL); + + bdev = allocate_bdev("bdev0"); + + rc = spdk_bdev_open(bdev, true, NULL, NULL, &desc); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(desc != NULL); + io_ch = spdk_bdev_get_io_channel(desc); + CU_ASSERT(io_ch != NULL); + + rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL); + CU_ASSERT(rc == 0); + rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL); + CU_ASSERT(rc == 0); + rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL); + CU_ASSERT(rc == 0); + rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 4); + + rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL); + CU_ASSERT(rc == -ENOMEM); + + io_wait_entry.entry.bdev = bdev; + io_wait_entry.entry.cb_fn = io_wait_cb; + io_wait_entry.entry.cb_arg = &io_wait_entry; + io_wait_entry.io_ch = io_ch; + io_wait_entry.desc = desc; + io_wait_entry.submitted = false; + /* Cannot use the same io_wait_entry for two different calls. */ + memcpy(&io_wait_entry2, &io_wait_entry, sizeof(io_wait_entry)); + io_wait_entry2.entry.cb_arg = &io_wait_entry2; + + /* Queue two I/O waits. */ + rc = spdk_bdev_queue_io_wait(bdev, io_ch, &io_wait_entry.entry); + CU_ASSERT(rc == 0); + CU_ASSERT(io_wait_entry.submitted == false); + rc = spdk_bdev_queue_io_wait(bdev, io_ch, &io_wait_entry2.entry); + CU_ASSERT(rc == 0); + CU_ASSERT(io_wait_entry2.submitted == false); + + stub_complete_io(1); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 4); + CU_ASSERT(io_wait_entry.submitted == true); + CU_ASSERT(io_wait_entry2.submitted == false); + + stub_complete_io(1); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 4); + CU_ASSERT(io_wait_entry2.submitted == true); + + stub_complete_io(4); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 0); + + spdk_put_io_channel(io_ch); + spdk_bdev_close(desc); + free_bdev(bdev); + spdk_bdev_finish(bdev_fini_cb, NULL); +} + +static void +bdev_io_spans_boundary_test(void) +{ + struct spdk_bdev bdev; + struct spdk_bdev_io bdev_io; + + memset(&bdev, 0, sizeof(bdev)); + + bdev.optimal_io_boundary = 0; + bdev_io.bdev = &bdev; + + /* bdev has no optimal_io_boundary set - so this should return false. */ + CU_ASSERT(_spdk_bdev_io_should_split(&bdev_io) == false); + + bdev.optimal_io_boundary = 32; + bdev_io.type = SPDK_BDEV_IO_TYPE_RESET; + + /* RESETs are not based on LBAs - so this should return false. */ + CU_ASSERT(_spdk_bdev_io_should_split(&bdev_io) == false); + + bdev_io.type = SPDK_BDEV_IO_TYPE_READ; + bdev_io.u.bdev.offset_blocks = 0; + bdev_io.u.bdev.num_blocks = 32; + + /* This I/O run right up to, but does not cross, the boundary - so this should return false. */ + CU_ASSERT(_spdk_bdev_io_should_split(&bdev_io) == false); + + bdev_io.u.bdev.num_blocks = 33; + + /* This I/O spans a boundary. */ + CU_ASSERT(_spdk_bdev_io_should_split(&bdev_io) == true); +} + +static void +bdev_io_split(void) +{ + struct spdk_bdev *bdev; + struct spdk_bdev_desc *desc = NULL; + struct spdk_io_channel *io_ch; + struct spdk_bdev_opts bdev_opts = { + .bdev_io_pool_size = 512, + .bdev_io_cache_size = 64, + }; + struct iovec iov[BDEV_IO_NUM_CHILD_IOV * 2]; + struct ut_expected_io *expected_io; + uint64_t i; + int rc; + + rc = spdk_bdev_set_opts(&bdev_opts); + CU_ASSERT(rc == 0); + spdk_bdev_initialize(bdev_init_cb, NULL); + + bdev = allocate_bdev("bdev0"); + + rc = spdk_bdev_open(bdev, true, NULL, NULL, &desc); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(desc != NULL); + io_ch = spdk_bdev_get_io_channel(desc); + CU_ASSERT(io_ch != NULL); + + bdev->optimal_io_boundary = 16; + bdev->split_on_optimal_io_boundary = false; + + g_io_done = false; + + /* First test that the I/O does not get split if split_on_optimal_io_boundary == false. */ + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 14, 8, 1); + ut_expected_io_set_iov(expected_io, 0, (void *)0xF000, 8 * 512); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + rc = spdk_bdev_read_blocks(desc, io_ch, (void *)0xF000, 14, 8, io_done, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_io_done == false); + + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + stub_complete_io(1); + CU_ASSERT(g_io_done == true); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 0); + + bdev->split_on_optimal_io_boundary = true; + + /* Now test that a single-vector command is split correctly. + * Offset 14, length 8, payload 0xF000 + * Child - Offset 14, length 2, payload 0xF000 + * Child - Offset 16, length 6, payload 0xF000 + 2 * 512 + * + * Set up the expected values before calling spdk_bdev_read_blocks + */ + g_io_done = false; + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 14, 2, 1); + ut_expected_io_set_iov(expected_io, 0, (void *)0xF000, 2 * 512); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 16, 6, 1); + ut_expected_io_set_iov(expected_io, 0, (void *)(0xF000 + 2 * 512), 6 * 512); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + /* spdk_bdev_read_blocks will submit the first child immediately. */ + rc = spdk_bdev_read_blocks(desc, io_ch, (void *)0xF000, 14, 8, io_done, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_io_done == false); + + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 2); + stub_complete_io(2); + CU_ASSERT(g_io_done == true); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 0); + + /* Now set up a more complex, multi-vector command that needs to be split, + * including splitting iovecs. + */ + iov[0].iov_base = (void *)0x10000; + iov[0].iov_len = 512; + iov[1].iov_base = (void *)0x20000; + iov[1].iov_len = 20 * 512; + iov[2].iov_base = (void *)0x30000; + iov[2].iov_len = 11 * 512; + + g_io_done = false; + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 14, 2, 2); + ut_expected_io_set_iov(expected_io, 0, (void *)0x10000, 512); + ut_expected_io_set_iov(expected_io, 1, (void *)0x20000, 512); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 16, 16, 1); + ut_expected_io_set_iov(expected_io, 0, (void *)(0x20000 + 512), 16 * 512); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 32, 14, 2); + ut_expected_io_set_iov(expected_io, 0, (void *)(0x20000 + 17 * 512), 3 * 512); + ut_expected_io_set_iov(expected_io, 1, (void *)0x30000, 11 * 512); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + rc = spdk_bdev_writev_blocks(desc, io_ch, iov, 3, 14, 32, io_done, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_io_done == false); + + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 3); + stub_complete_io(3); + CU_ASSERT(g_io_done == true); + + /* Test multi vector command that needs to be split by strip and then needs to be + * split further due to the capacity of child iovs. + */ + for (i = 0; i < BDEV_IO_NUM_CHILD_IOV * 2; i++) { + iov[i].iov_base = (void *)((i + 1) * 0x10000); + iov[i].iov_len = 512; + } + + bdev->optimal_io_boundary = BDEV_IO_NUM_CHILD_IOV; + g_io_done = false; + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 0, BDEV_IO_NUM_CHILD_IOV, + BDEV_IO_NUM_CHILD_IOV); + for (i = 0; i < BDEV_IO_NUM_CHILD_IOV; i++) { + ut_expected_io_set_iov(expected_io, i, (void *)((i + 1) * 0x10000), 512); + } + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, BDEV_IO_NUM_CHILD_IOV, + BDEV_IO_NUM_CHILD_IOV, BDEV_IO_NUM_CHILD_IOV); + for (i = 0; i < BDEV_IO_NUM_CHILD_IOV; i++) { + ut_expected_io_set_iov(expected_io, i, + (void *)((i + 1 + BDEV_IO_NUM_CHILD_IOV) * 0x10000), 512); + } + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + rc = spdk_bdev_readv_blocks(desc, io_ch, iov, BDEV_IO_NUM_CHILD_IOV * 2, 0, + BDEV_IO_NUM_CHILD_IOV * 2, io_done, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_io_done == false); + + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + stub_complete_io(1); + CU_ASSERT(g_io_done == false); + + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + stub_complete_io(1); + CU_ASSERT(g_io_done == true); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 0); + + /* Test multi vector command that needs to be split by strip and then needs to be + * split further due to the capacity of child iovs, but fails to split. The cause + * of failure of split is that the length of an iovec is not multiple of block size. + */ + for (i = 0; i < BDEV_IO_NUM_CHILD_IOV - 1; i++) { + iov[i].iov_base = (void *)((i + 1) * 0x10000); + iov[i].iov_len = 512; + } + iov[BDEV_IO_NUM_CHILD_IOV - 1].iov_base = (void *)(BDEV_IO_NUM_CHILD_IOV * 0x10000); + iov[BDEV_IO_NUM_CHILD_IOV - 1].iov_len = 256; + + bdev->optimal_io_boundary = BDEV_IO_NUM_CHILD_IOV; + g_io_done = false; + g_io_status = 0; + + rc = spdk_bdev_readv_blocks(desc, io_ch, iov, BDEV_IO_NUM_CHILD_IOV * 2, 0, + BDEV_IO_NUM_CHILD_IOV * 2, io_done, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_io_done == true); + CU_ASSERT(g_io_status == SPDK_BDEV_IO_STATUS_FAILED); + + /* Test a WRITE_ZEROES that would span an I/O boundary. WRITE_ZEROES should not be + * split, so test that. + */ + bdev->optimal_io_boundary = 15; + g_io_done = false; + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE_ZEROES, 9, 36, 0); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + rc = spdk_bdev_write_zeroes_blocks(desc, io_ch, 9, 36, io_done, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_io_done == false); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + stub_complete_io(1); + CU_ASSERT(g_io_done == true); + + /* Test an UNMAP. This should also not be split. */ + bdev->optimal_io_boundary = 16; + g_io_done = false; + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_UNMAP, 15, 2, 0); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + rc = spdk_bdev_unmap_blocks(desc, io_ch, 15, 2, io_done, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_io_done == false); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + stub_complete_io(1); + CU_ASSERT(g_io_done == true); + + /* Test a FLUSH. This should also not be split. */ + bdev->optimal_io_boundary = 16; + g_io_done = false; + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_FLUSH, 15, 2, 0); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + rc = spdk_bdev_flush_blocks(desc, io_ch, 15, 2, io_done, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_io_done == false); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + stub_complete_io(1); + CU_ASSERT(g_io_done == true); + + CU_ASSERT(TAILQ_EMPTY(&g_bdev_ut_channel->expected_io)); + + spdk_put_io_channel(io_ch); + spdk_bdev_close(desc); + free_bdev(bdev); + spdk_bdev_finish(bdev_fini_cb, NULL); +} + +static void +bdev_io_split_with_io_wait(void) +{ + struct spdk_bdev *bdev; + struct spdk_bdev_desc *desc; + struct spdk_io_channel *io_ch; + struct spdk_bdev_channel *channel; + struct spdk_bdev_mgmt_channel *mgmt_ch; + struct spdk_bdev_opts bdev_opts = { + .bdev_io_pool_size = 2, + .bdev_io_cache_size = 1, + }; + struct iovec iov[3]; + struct ut_expected_io *expected_io; + int rc; + + rc = spdk_bdev_set_opts(&bdev_opts); + CU_ASSERT(rc == 0); + spdk_bdev_initialize(bdev_init_cb, NULL); + + bdev = allocate_bdev("bdev0"); + + rc = spdk_bdev_open(bdev, true, NULL, NULL, &desc); + CU_ASSERT(rc == 0); + CU_ASSERT(desc != NULL); + io_ch = spdk_bdev_get_io_channel(desc); + CU_ASSERT(io_ch != NULL); + channel = spdk_io_channel_get_ctx(io_ch); + mgmt_ch = channel->shared_resource->mgmt_ch; + + bdev->optimal_io_boundary = 16; + bdev->split_on_optimal_io_boundary = true; + + rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL); + CU_ASSERT(rc == 0); + + /* Now test that a single-vector command is split correctly. + * Offset 14, length 8, payload 0xF000 + * Child - Offset 14, length 2, payload 0xF000 + * Child - Offset 16, length 6, payload 0xF000 + 2 * 512 + * + * Set up the expected values before calling spdk_bdev_read_blocks + */ + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 14, 2, 1); + ut_expected_io_set_iov(expected_io, 0, (void *)0xF000, 2 * 512); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 16, 6, 1); + ut_expected_io_set_iov(expected_io, 0, (void *)(0xF000 + 2 * 512), 6 * 512); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + /* The following children will be submitted sequentially due to the capacity of + * spdk_bdev_io. + */ + + /* The first child I/O will be queued to wait until an spdk_bdev_io becomes available */ + rc = spdk_bdev_read_blocks(desc, io_ch, (void *)0xF000, 14, 8, io_done, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(!TAILQ_EMPTY(&mgmt_ch->io_wait_queue)); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + + /* Completing the first read I/O will submit the first child */ + stub_complete_io(1); + CU_ASSERT(TAILQ_EMPTY(&mgmt_ch->io_wait_queue)); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + + /* Completing the first child will submit the second child */ + stub_complete_io(1); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + + /* Complete the second child I/O. This should result in our callback getting + * invoked since the parent I/O is now complete. + */ + stub_complete_io(1); + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 0); + + /* Now set up a more complex, multi-vector command that needs to be split, + * including splitting iovecs. + */ + iov[0].iov_base = (void *)0x10000; + iov[0].iov_len = 512; + iov[1].iov_base = (void *)0x20000; + iov[1].iov_len = 20 * 512; + iov[2].iov_base = (void *)0x30000; + iov[2].iov_len = 11 * 512; + + g_io_done = false; + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 14, 2, 2); + ut_expected_io_set_iov(expected_io, 0, (void *)0x10000, 512); + ut_expected_io_set_iov(expected_io, 1, (void *)0x20000, 512); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 16, 16, 1); + ut_expected_io_set_iov(expected_io, 0, (void *)(0x20000 + 512), 16 * 512); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 32, 14, 2); + ut_expected_io_set_iov(expected_io, 0, (void *)(0x20000 + 17 * 512), 3 * 512); + ut_expected_io_set_iov(expected_io, 1, (void *)0x30000, 11 * 512); + TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link); + + rc = spdk_bdev_writev_blocks(desc, io_ch, iov, 3, 14, 32, io_done, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_io_done == false); + + /* The following children will be submitted sequentially due to the capacity of + * spdk_bdev_io. + */ + + /* Completing the first child will submit the second child */ + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + stub_complete_io(1); + CU_ASSERT(g_io_done == false); + + /* Completing the second child will submit the third child */ + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + stub_complete_io(1); + CU_ASSERT(g_io_done == false); + + /* Completing the third child will result in our callback getting invoked + * since the parent I/O is now complete. + */ + CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1); + stub_complete_io(1); + CU_ASSERT(g_io_done == true); + + CU_ASSERT(TAILQ_EMPTY(&g_bdev_ut_channel->expected_io)); + + spdk_put_io_channel(io_ch); + spdk_bdev_close(desc); + free_bdev(bdev); + spdk_bdev_finish(bdev_fini_cb, NULL); +} + +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("bdev", null_init, null_clean); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "bytes_to_blocks_test", bytes_to_blocks_test) == NULL || + CU_add_test(suite, "num_blocks_test", num_blocks_test) == NULL || + CU_add_test(suite, "io_valid", io_valid_test) == NULL || + CU_add_test(suite, "open_write", open_write_test) == NULL || + CU_add_test(suite, "alias_add_del", alias_add_del_test) == NULL || + CU_add_test(suite, "get_device_stat", get_device_stat_test) == NULL || + CU_add_test(suite, "bdev_io_wait", bdev_io_wait_test) == NULL || + CU_add_test(suite, "bdev_io_spans_boundary", bdev_io_spans_boundary_test) == NULL || + CU_add_test(suite, "bdev_io_split", bdev_io_split) == NULL || + CU_add_test(suite, "bdev_io_split_with_io_wait", bdev_io_split_with_io_wait) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + spdk_allocate_thread(_bdev_send_msg, NULL, NULL, NULL, "thread0"); + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + spdk_free_thread(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/bdev/bdev_raid.c/.gitignore b/src/spdk/test/unit/lib/bdev/bdev_raid.c/.gitignore new file mode 100644 index 00000000..98d1a166 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/bdev_raid.c/.gitignore @@ -0,0 +1 @@ +bdev_raid_ut diff --git a/src/spdk/test/unit/lib/bdev/bdev_raid.c/Makefile b/src/spdk/test/unit/lib/bdev/bdev_raid.c/Makefile new file mode 100644 index 00000000..9739cb44 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/bdev_raid.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = bdev_raid_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/bdev/bdev_raid.c/bdev_raid_ut.c b/src/spdk/test/unit/lib/bdev/bdev_raid.c/bdev_raid_ut.c new file mode 100644 index 00000000..ffa466da --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/bdev_raid.c/bdev_raid_ut.c @@ -0,0 +1,2236 @@ +/*- + * 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 "bdev/raid/bdev_raid.c" +#include "bdev/raid/bdev_raid_rpc.c" + +#define MAX_BASE_DRIVES 255 +#define MAX_RAIDS 31 +#define INVALID_IO_SUBMIT 0xFFFF + +/* Data structure to capture the output of IO for verification */ +struct io_output { + struct spdk_bdev_desc *desc; + struct spdk_io_channel *ch; + uint64_t offset_blocks; + uint64_t num_blocks; + spdk_bdev_io_completion_cb cb; + void *cb_arg; + enum spdk_bdev_io_type iotype; +}; + +/* Different test options, more options to test can be added here */ +uint32_t g_blklen_opts[] = {512, 4096}; +uint32_t g_strip_opts[] = {64, 128, 256, 512, 1024, 2048}; +uint32_t g_iosize_opts[] = {256, 512, 1024}; +uint32_t g_max_qd_opts[] = {64, 128, 256, 512, 1024, 2048}; + +/* Globals */ +int g_bdev_io_submit_status; +struct io_output *g_io_output = NULL; +uint32_t g_io_output_index; +uint32_t g_io_comp_status; +bool g_child_io_status_flag; +void *rpc_req; +uint32_t rpc_req_size; +TAILQ_HEAD(bdev, spdk_bdev); +struct bdev g_bdev_list; +TAILQ_HEAD(waitq, spdk_bdev_io_wait_entry); +struct waitq g_io_waitq; +uint32_t g_block_len; +uint32_t g_strip_size; +uint32_t g_max_io_size; +uint32_t g_max_qd; +uint8_t g_max_base_drives; +uint8_t g_max_raids; +uint8_t g_ignore_io_output; +uint8_t g_rpc_err; +char *g_get_raids_output[MAX_RAIDS]; +uint32_t g_get_raids_count; +uint8_t g_json_beg_res_ret_err; +uint8_t g_json_decode_obj_err; +uint8_t g_json_decode_obj_construct; +uint8_t g_config_level_create = 0; +uint8_t g_test_multi_raids; + +/* Set randomly test options, in every run it is different */ +static void +set_test_opts(void) +{ + uint32_t seed = time(0); + + /* Generate random test options */ + srand(seed); + g_max_base_drives = (rand() % MAX_BASE_DRIVES) + 1; + g_max_raids = (rand() % MAX_RAIDS) + 1; + g_block_len = g_blklen_opts[rand() % SPDK_COUNTOF(g_blklen_opts)]; + g_strip_size = g_strip_opts[rand() % SPDK_COUNTOF(g_strip_opts)]; + g_max_io_size = g_iosize_opts[rand() % SPDK_COUNTOF(g_iosize_opts)]; + g_max_qd = g_max_qd_opts[rand() % SPDK_COUNTOF(g_max_qd_opts)]; + + printf("Test Options, seed = %u\n", seed); + printf("blocklen = %u, strip_size = %u, max_io_size = %u, max_qd = %u, g_max_base_drives = %u, g_max_raids = %u\n", + g_block_len, g_strip_size, g_max_io_size, g_max_qd, g_max_base_drives, g_max_raids); +} + +/* Set globals before every test run */ +static void +set_globals(void) +{ + uint32_t max_splits; + + g_bdev_io_submit_status = 0; + if (g_max_io_size < g_strip_size) { + max_splits = 2; + } else { + max_splits = (g_max_io_size / g_strip_size) + 1; + } + g_io_output = calloc(max_splits, sizeof(struct io_output)); + SPDK_CU_ASSERT_FATAL(g_io_output != NULL); + g_io_output_index = 0; + memset(g_get_raids_output, 0, sizeof(g_get_raids_output)); + g_get_raids_count = 0; + g_io_comp_status = 0; + g_ignore_io_output = 0; + g_config_level_create = 0; + g_rpc_err = 0; + g_test_multi_raids = 0; + g_child_io_status_flag = true; + TAILQ_INIT(&g_bdev_list); + TAILQ_INIT(&g_io_waitq); + rpc_req = NULL; + rpc_req_size = 0; + g_json_beg_res_ret_err = 0; + g_json_decode_obj_err = 0; + g_json_decode_obj_construct = 0; +} + +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); + } + } +} + +static void +check_and_remove_raid_bdev(struct raid_bdev_config *raid_cfg) +{ + struct raid_bdev *raid_bdev; + + /* Get the raid structured allocated if exists */ + raid_bdev = raid_cfg->raid_bdev; + if (raid_bdev == NULL) { + return; + } + + for (uint32_t i = 0; i < raid_bdev->num_base_bdevs; i++) { + assert(raid_bdev->base_bdev_info != NULL); + if (raid_bdev->base_bdev_info[i].bdev) { + raid_bdev_free_base_bdev_resource(raid_bdev, i); + } + } + assert(raid_bdev->num_base_bdevs_discovered == 0); + raid_bdev_cleanup(raid_bdev); +} + +/* Reset globals */ +static void +reset_globals(void) +{ + if (g_io_output) { + free(g_io_output); + g_io_output = NULL; + } + rpc_req = NULL; + rpc_req_size = 0; +} + +void +spdk_bdev_io_get_buf(struct spdk_bdev_io *bdev_io, spdk_bdev_io_get_buf_cb cb, + uint64_t len) +{ + CU_ASSERT(false); +} + +/* Store the IO completion status in global variable to verify by various tests */ +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); +} + +/* It will cache the split IOs for verification */ +int +spdk_bdev_writev_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + struct io_output *p = &g_io_output[g_io_output_index]; + struct spdk_bdev_io *child_io; + + if (g_ignore_io_output) { + return 0; + } + + if (g_max_io_size < g_strip_size) { + SPDK_CU_ASSERT_FATAL(g_io_output_index < 2); + } else { + SPDK_CU_ASSERT_FATAL(g_io_output_index < (g_max_io_size / g_strip_size) + 1); + } + if (g_bdev_io_submit_status == 0) { + p->desc = desc; + p->ch = ch; + p->offset_blocks = offset_blocks; + p->num_blocks = num_blocks; + p->cb = cb; + p->cb_arg = cb_arg; + p->iotype = SPDK_BDEV_IO_TYPE_WRITE; + g_io_output_index++; + child_io = calloc(1, sizeof(struct spdk_bdev_io)); + SPDK_CU_ASSERT_FATAL(child_io != NULL); + cb(child_io, g_child_io_status_flag, cb_arg); + } + + return g_bdev_io_submit_status; +} + +int +spdk_bdev_reset(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return 0; +} + +void +spdk_bdev_unregister(struct spdk_bdev *bdev, spdk_bdev_unregister_cb cb_fn, void *cb_arg) +{ + bdev->fn_table->destruct(bdev->ctxt); +} + +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 *)0x1; + return 0; +} + +void +spdk_put_io_channel(struct spdk_io_channel *ch) +{ + CU_ASSERT(ch == (void *)1); +} + +struct spdk_io_channel * +spdk_get_io_channel(void *io_device) +{ + return NULL; +} + +void +spdk_poller_unregister(struct spdk_poller **ppoller) +{ +} + +struct spdk_poller * +spdk_poller_register(spdk_poller_fn fn, + void *arg, + uint64_t period_microseconds) +{ + return (void *)1; +} + +void +spdk_io_device_unregister(void *io_device, spdk_io_device_unregister_cb unregister_cb) +{ +} + +char * +spdk_sprintf_alloc(const char *format, ...) +{ + return strdup(format); +} + +void +spdk_io_device_register(void *io_device, spdk_io_channel_create_cb create_cb, + spdk_io_channel_destroy_cb destroy_cb, uint32_t ctx_size, + const char *name) +{ +} + +int +spdk_json_write_name(struct spdk_json_write_ctx *w, const char *name) +{ + return 0; +} + +int spdk_json_write_named_uint32(struct spdk_json_write_ctx *w, const char *name, uint32_t val) +{ + struct rpc_construct_raid_bdev *req = rpc_req; + if (strcmp(name, "strip_size") == 0) { + CU_ASSERT(req->strip_size * 1024 / g_block_len == val); + } else if (strcmp(name, "blocklen_shift") == 0) { + CU_ASSERT(spdk_u32log2(g_block_len) == val); + } else if (strcmp(name, "raid_level") == 0) { + CU_ASSERT(req->raid_level == val); + } else if (strcmp(name, "num_base_bdevs") == 0) { + CU_ASSERT(req->base_bdevs.num_base_bdevs == val); + } else if (strcmp(name, "state") == 0) { + CU_ASSERT(val == RAID_BDEV_STATE_ONLINE); + } else if (strcmp(name, "destruct_called") == 0) { + CU_ASSERT(val == 0); + } else if (strcmp(name, "num_base_bdevs_discovered") == 0) { + CU_ASSERT(req->base_bdevs.num_base_bdevs == val); + } + return 0; +} + +int spdk_json_write_named_string(struct spdk_json_write_ctx *w, const char *name, const char *val) +{ + return 0; +} + +int +spdk_json_write_object_begin(struct spdk_json_write_ctx *w) +{ + return 0; +} + +int +spdk_json_write_named_object_begin(struct spdk_json_write_ctx *w, const char *name) +{ + return 0; +} + +int +spdk_json_write_named_array_begin(struct spdk_json_write_ctx *w, const char *name) +{ + return 0; +} + +int +spdk_json_write_array_end(struct spdk_json_write_ctx *w) +{ + return 0; +} + +int +spdk_json_write_object_end(struct spdk_json_write_ctx *w) +{ + return 0; +} + +int +spdk_json_write_bool(struct spdk_json_write_ctx *w, bool val) +{ + return 0; +} + +int spdk_json_write_null(struct spdk_json_write_ctx *w) +{ + return 0; +} + +struct spdk_io_channel * +spdk_bdev_get_io_channel(struct spdk_bdev_desc *desc) +{ + return (void *)1; +} + +void +spdk_for_each_thread(spdk_thread_fn fn, void *ctx, spdk_thread_fn cpl) +{ + fn(ctx); + cpl(ctx); +} + +struct spdk_thread * +spdk_get_thread(void) +{ + return NULL; +} + +void +spdk_thread_send_msg(const struct spdk_thread *thread, spdk_thread_fn fn, void *ctx) +{ + fn(ctx); +} + +uint32_t +spdk_env_get_current_core(void) +{ + return 0; +} + +void +spdk_bdev_free_io(struct spdk_bdev_io *bdev_io) +{ + if (bdev_io) { + free(bdev_io); + } +} + +/* It will cache split IOs for verification */ +int +spdk_bdev_readv_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + struct io_output *p = &g_io_output[g_io_output_index]; + struct spdk_bdev_io *child_io; + + if (g_ignore_io_output) { + return 0; + } + + SPDK_CU_ASSERT_FATAL(g_io_output_index <= (g_max_io_size / g_strip_size) + 1); + if (g_bdev_io_submit_status == 0) { + p->desc = desc; + p->ch = ch; + p->offset_blocks = offset_blocks; + p->num_blocks = num_blocks; + p->cb = cb; + p->cb_arg = cb_arg; + p->iotype = SPDK_BDEV_IO_TYPE_READ; + g_io_output_index++; + child_io = calloc(1, sizeof(struct spdk_bdev_io)); + SPDK_CU_ASSERT_FATAL(child_io != NULL); + cb(child_io, g_child_io_status_flag, cb_arg); + } + + return g_bdev_io_submit_status; +} + +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_module_examine_done(struct spdk_bdev_module *module) +{ +} + +struct spdk_conf_section * +spdk_conf_first_section(struct spdk_conf *cp) +{ + if (g_config_level_create) { + return (void *) 0x1; + } + + return NULL; +} + +bool +spdk_conf_section_match_prefix(const struct spdk_conf_section *sp, const char *name_prefix) +{ + if (g_config_level_create) { + return true; + } + + return false; +} + +char * +spdk_conf_section_get_val(struct spdk_conf_section *sp, const char *key) +{ + struct rpc_construct_raid_bdev *req = rpc_req; + + if (g_config_level_create) { + if (strcmp(key, "Name") == 0) { + return req->name; + } + } + + return NULL; +} + +int +spdk_conf_section_get_intval(struct spdk_conf_section *sp, const char *key) +{ + struct rpc_construct_raid_bdev *req = rpc_req; + + if (g_config_level_create) { + if (strcmp(key, "StripSize") == 0) { + return req->strip_size; + } else if (strcmp(key, "NumDevices") == 0) { + return req->base_bdevs.num_base_bdevs; + } else if (strcmp(key, "RaidLevel") == 0) { + return req->raid_level; + } + } + + return 0; +} + +struct spdk_conf_section * +spdk_conf_next_section(struct spdk_conf_section *sp) +{ + return NULL; +} + +char * +spdk_conf_section_get_nmval(struct spdk_conf_section *sp, const char *key, int idx1, int idx2) +{ + struct rpc_construct_raid_bdev *req = rpc_req; + + if (g_config_level_create) { + if (strcmp(key, "Devices") == 0) { + if (idx2 >= g_max_base_drives) { + return NULL; + } + return req->base_bdevs.base_bdevs[idx2]; + } + } + + return NULL; +} + +void +spdk_bdev_close(struct spdk_bdev_desc *desc) +{ +} + +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; +} + +int +spdk_bdev_register(struct spdk_bdev *bdev) +{ + return 0; +} + +uint32_t +spdk_env_get_last_core(void) +{ + return 0; +} + +int +spdk_json_decode_string(const struct spdk_json_val *val, void *out) +{ + return 0; +} + +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_raid_bdev *req, *_out; + size_t i; + + if (g_json_decode_obj_err) { + return -1; + } else if (g_json_decode_obj_construct) { + req = rpc_req; + _out = out; + + _out->name = strdup(req->name); + SPDK_CU_ASSERT_FATAL(_out->name != NULL); + _out->strip_size = req->strip_size; + _out->raid_level = req->raid_level; + _out->base_bdevs.num_base_bdevs = req->base_bdevs.num_base_bdevs; + for (i = 0; i < req->base_bdevs.num_base_bdevs; i++) { + _out->base_bdevs.base_bdevs[i] = strdup(req->base_bdevs.base_bdevs[i]); + SPDK_CU_ASSERT_FATAL(_out->base_bdevs.base_bdevs[i]); + } + } else { + memcpy(out, rpc_req, rpc_req_size); + } + + return 0; +} + +struct spdk_json_write_ctx * +spdk_jsonrpc_begin_result(struct spdk_jsonrpc_request *request) +{ + if (g_json_beg_res_ret_err) { + return NULL; + } else { + return (void *)1; + } +} + +int +spdk_json_write_array_begin(struct spdk_json_write_ctx *w) +{ + return 0; +} + +int +spdk_json_write_string(struct spdk_json_write_ctx *w, const char *val) +{ + if (g_test_multi_raids) { + g_get_raids_output[g_get_raids_count] = strdup(val); + SPDK_CU_ASSERT_FATAL(g_get_raids_output[g_get_raids_count] != NULL); + g_get_raids_count++; + } + + return 0; +} + +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; +} + +void +spdk_jsonrpc_end_result(struct spdk_jsonrpc_request *request, struct spdk_json_write_ctx *w) +{ +} + +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; +} + +const char * +spdk_strerror(int errnum) +{ + return NULL; +} + +int +spdk_json_decode_array(const struct spdk_json_val *values, spdk_json_decode_fn decode_func, + void *out, size_t max_size, size_t *out_size, size_t stride) +{ + return 0; +} + +void +spdk_rpc_register_method(const char *method, spdk_rpc_method_handler func, uint32_t state_mask) +{ +} + +int +spdk_json_decode_uint32(const struct spdk_json_val *val, void *out) +{ + return 0; +} + + +void +spdk_bdev_module_list_add(struct spdk_bdev_module *bdev_module) +{ +} + +static void +bdev_io_cleanup(struct spdk_bdev_io *bdev_io) +{ + if (bdev_io->u.bdev.iovs) { + if (bdev_io->u.bdev.iovs->iov_base) { + free(bdev_io->u.bdev.iovs->iov_base); + bdev_io->u.bdev.iovs->iov_base = NULL; + } + free(bdev_io->u.bdev.iovs); + bdev_io->u.bdev.iovs = NULL; + } +} + +static void +bdev_io_initialize(struct spdk_bdev_io *bdev_io, struct spdk_bdev *bdev, + uint64_t lba, uint64_t blocks, int16_t iotype) +{ + bdev_io->bdev = bdev; + bdev_io->u.bdev.offset_blocks = lba; + bdev_io->u.bdev.num_blocks = blocks; + bdev_io->type = iotype; + bdev_io->u.bdev.iovcnt = 1; + bdev_io->u.bdev.iovs = calloc(1, sizeof(struct iovec)); + SPDK_CU_ASSERT_FATAL(bdev_io->u.bdev.iovs != NULL); + bdev_io->u.bdev.iovs->iov_base = calloc(1, bdev_io->u.bdev.num_blocks * g_block_len); + SPDK_CU_ASSERT_FATAL(bdev_io->u.bdev.iovs->iov_base != NULL); + bdev_io->u.bdev.iovs->iov_len = bdev_io->u.bdev.num_blocks * g_block_len; + bdev_io->u.bdev.iovs = bdev_io->u.bdev.iovs; +} + +static void +verify_io(struct spdk_bdev_io *bdev_io, uint8_t num_base_drives, + struct raid_bdev_io_channel *ch_ctx, struct raid_bdev *raid_bdev, uint32_t io_status) +{ + uint32_t strip_shift = spdk_u32log2(g_strip_size); + uint64_t start_strip = bdev_io->u.bdev.offset_blocks >> strip_shift; + uint64_t end_strip = (bdev_io->u.bdev.offset_blocks + bdev_io->u.bdev.num_blocks - 1) >> + strip_shift; + uint32_t splits_reqd = (end_strip - start_strip + 1); + uint32_t strip; + uint64_t pd_strip; + uint64_t pd_idx; + uint32_t offset_in_strip; + uint64_t pd_lba; + uint64_t pd_blocks; + uint32_t index = 0; + uint8_t *buf = bdev_io->u.bdev.iovs->iov_base; + + if (io_status == INVALID_IO_SUBMIT) { + CU_ASSERT(g_io_comp_status == false); + return; + } + SPDK_CU_ASSERT_FATAL(raid_bdev != NULL); + SPDK_CU_ASSERT_FATAL(num_base_drives != 0); + + CU_ASSERT(splits_reqd == g_io_output_index); + for (strip = start_strip; strip <= end_strip; strip++, index++) { + pd_strip = strip / num_base_drives; + pd_idx = strip % num_base_drives; + if (strip == start_strip) { + offset_in_strip = bdev_io->u.bdev.offset_blocks & (g_strip_size - 1); + pd_lba = (pd_strip << strip_shift) + offset_in_strip; + if (strip == end_strip) { + pd_blocks = bdev_io->u.bdev.num_blocks; + } else { + pd_blocks = g_strip_size - offset_in_strip; + } + } else if (strip == end_strip) { + pd_lba = pd_strip << strip_shift; + pd_blocks = ((bdev_io->u.bdev.offset_blocks + bdev_io->u.bdev.num_blocks - 1) & + (g_strip_size - 1)) + 1; + } else { + pd_lba = pd_strip << raid_bdev->strip_size_shift; + pd_blocks = raid_bdev->strip_size; + } + CU_ASSERT(pd_lba == g_io_output[index].offset_blocks); + CU_ASSERT(pd_blocks == g_io_output[index].num_blocks); + CU_ASSERT(ch_ctx->base_channel[pd_idx] == g_io_output[index].ch); + CU_ASSERT(raid_bdev->base_bdev_info[pd_idx].desc == g_io_output[index].desc); + CU_ASSERT(bdev_io->type == g_io_output[index].iotype); + buf += (pd_blocks << spdk_u32log2(g_block_len)); + } + CU_ASSERT(g_io_comp_status == io_status); +} + +static void +verify_raid_config_present(const char *name, bool presence) +{ + struct raid_bdev_config *raid_cfg; + bool cfg_found; + + cfg_found = false; + + TAILQ_FOREACH(raid_cfg, &g_spdk_raid_config.raid_bdev_config_head, link) { + if (raid_cfg->name != NULL) { + if (strcmp(name, raid_cfg->name) == 0) { + cfg_found = true; + break; + } + } + } + + if (presence == true) { + CU_ASSERT(cfg_found == true); + } else { + CU_ASSERT(cfg_found == false); + } +} + +static void +verify_raid_bdev_present(const char *name, bool presence) +{ + struct raid_bdev *pbdev; + bool pbdev_found; + + pbdev_found = false; + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) { + if (strcmp(pbdev->bdev.name, name) == 0) { + pbdev_found = true; + break; + } + } + if (presence == true) { + CU_ASSERT(pbdev_found == true); + } else { + CU_ASSERT(pbdev_found == false); + } +} +static void +verify_raid_config(struct rpc_construct_raid_bdev *r, bool presence) +{ + struct raid_bdev_config *raid_cfg = NULL; + uint32_t i; + int val; + + TAILQ_FOREACH(raid_cfg, &g_spdk_raid_config.raid_bdev_config_head, link) { + if (strcmp(r->name, raid_cfg->name) == 0) { + if (presence == false) { + break; + } + CU_ASSERT(raid_cfg->raid_bdev != NULL); + CU_ASSERT(raid_cfg->strip_size == r->strip_size); + CU_ASSERT(raid_cfg->num_base_bdevs == r->base_bdevs.num_base_bdevs); + CU_ASSERT(raid_cfg->raid_level == r->raid_level); + if (raid_cfg->base_bdev != NULL) { + for (i = 0; i < raid_cfg->num_base_bdevs; i++) { + val = strcmp(raid_cfg->base_bdev[i].name, r->base_bdevs.base_bdevs[i]); + CU_ASSERT(val == 0); + } + } + break; + } + } + + if (presence == true) { + CU_ASSERT(raid_cfg != NULL); + } else { + CU_ASSERT(raid_cfg == NULL); + } +} + +static void +verify_raid_bdev(struct rpc_construct_raid_bdev *r, bool presence, uint32_t raid_state) +{ + struct raid_bdev *pbdev; + uint32_t i; + struct spdk_bdev *bdev = NULL; + bool pbdev_found; + uint64_t min_blockcnt = 0xFFFFFFFFFFFFFFFF; + + pbdev_found = false; + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) { + if (strcmp(pbdev->bdev.name, r->name) == 0) { + pbdev_found = true; + if (presence == false) { + break; + } + CU_ASSERT(pbdev->config->raid_bdev == pbdev); + CU_ASSERT(pbdev->base_bdev_info != NULL); + CU_ASSERT(pbdev->strip_size == ((r->strip_size * 1024) / g_block_len)); + CU_ASSERT(pbdev->strip_size_shift == spdk_u32log2(((r->strip_size * 1024) / g_block_len))); + CU_ASSERT(pbdev->blocklen_shift == spdk_u32log2(g_block_len)); + CU_ASSERT(pbdev->state == raid_state); + CU_ASSERT(pbdev->num_base_bdevs == r->base_bdevs.num_base_bdevs); + CU_ASSERT(pbdev->num_base_bdevs_discovered == r->base_bdevs.num_base_bdevs); + CU_ASSERT(pbdev->raid_level == r->raid_level); + CU_ASSERT(pbdev->destruct_called == false); + for (i = 0; i < pbdev->num_base_bdevs; i++) { + if (pbdev->base_bdev_info && pbdev->base_bdev_info[i].bdev) { + bdev = spdk_bdev_get_by_name(pbdev->base_bdev_info[i].bdev->name); + CU_ASSERT(bdev != NULL); + CU_ASSERT(pbdev->base_bdev_info[i].remove_scheduled == false); + } else { + CU_ASSERT(0); + } + + if (bdev && bdev->blockcnt < min_blockcnt) { + min_blockcnt = bdev->blockcnt; + } + } + CU_ASSERT((((min_blockcnt / (r->strip_size * 1024 / g_block_len)) * (r->strip_size * 1024 / + g_block_len)) * r->base_bdevs.num_base_bdevs) == pbdev->bdev.blockcnt); + CU_ASSERT(strcmp(pbdev->bdev.product_name, "Pooled Device") == 0); + CU_ASSERT(pbdev->bdev.write_cache == 0); + CU_ASSERT(pbdev->bdev.blocklen == g_block_len); + if (pbdev->num_base_bdevs > 1) { + CU_ASSERT(pbdev->bdev.optimal_io_boundary == pbdev->strip_size); + CU_ASSERT(pbdev->bdev.split_on_optimal_io_boundary == true); + } else { + CU_ASSERT(pbdev->bdev.optimal_io_boundary == 0); + CU_ASSERT(pbdev->bdev.split_on_optimal_io_boundary == false); + } + CU_ASSERT(pbdev->bdev.ctxt == pbdev); + CU_ASSERT(pbdev->bdev.fn_table == &g_raid_bdev_fn_table); + CU_ASSERT(pbdev->bdev.module == &g_raid_if); + break; + } + } + if (presence == true) { + CU_ASSERT(pbdev_found == true); + } else { + CU_ASSERT(pbdev_found == false); + } + pbdev_found = false; + if (raid_state == RAID_BDEV_STATE_ONLINE) { + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_configured_list, state_link) { + if (strcmp(pbdev->bdev.name, r->name) == 0) { + pbdev_found = true; + break; + } + } + } else if (raid_state == RAID_BDEV_STATE_CONFIGURING) { + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_configuring_list, state_link) { + if (strcmp(pbdev->bdev.name, r->name) == 0) { + pbdev_found = true; + break; + } + } + } else if (raid_state == RAID_BDEV_STATE_OFFLINE) { + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_offline_list, state_link) { + if (strcmp(pbdev->bdev.name, r->name) == 0) { + pbdev_found = true; + break; + } + } + } + if (presence == true) { + CU_ASSERT(pbdev_found == true); + } else { + CU_ASSERT(pbdev_found == false); + } +} + +int +spdk_bdev_queue_io_wait(struct spdk_bdev *bdev, struct spdk_io_channel *ch, + struct spdk_bdev_io_wait_entry *entry) +{ + CU_ASSERT(bdev == entry->bdev); + CU_ASSERT(entry->cb_fn != NULL); + CU_ASSERT(entry->cb_arg != NULL); + TAILQ_INSERT_TAIL(&g_io_waitq, entry, link); + return 0; +} + + +static uint32_t +get_num_elts_in_waitq(void) +{ + struct spdk_bdev_io_wait_entry *ele; + uint32_t count = 0; + + TAILQ_FOREACH(ele, &g_io_waitq, link) { + count++; + } + + return count; +} + +static void +process_io_waitq(void) +{ + struct spdk_bdev_io_wait_entry *ele; + struct spdk_bdev_io_wait_entry *next_ele; + + TAILQ_FOREACH_SAFE(ele, &g_io_waitq, link, next_ele) { + TAILQ_REMOVE(&g_io_waitq, ele, link); + ele->cb_fn(ele->cb_arg); + } +} + +static void +verify_get_raids(struct rpc_construct_raid_bdev *construct_req, + uint8_t g_max_raids, + char **g_get_raids_output, uint32_t g_get_raids_count) +{ + uint32_t i, j; + bool found; + + CU_ASSERT(g_max_raids == g_get_raids_count); + if (g_max_raids == g_get_raids_count) { + for (i = 0; i < g_max_raids; i++) { + found = false; + for (j = 0; j < g_max_raids; j++) { + if (construct_req[i].name && strcmp(construct_req[i].name, g_get_raids_output[i]) == 0) { + found = true; + break; + } + } + CU_ASSERT(found == true); + } + } +} + +static void +create_base_bdevs(uint32_t bbdev_start_idx) +{ + uint32_t i; + struct spdk_bdev *base_bdev; + char name[16]; + uint16_t num_chars; + + for (i = 0; i < g_max_base_drives; i++, bbdev_start_idx++) { + num_chars = snprintf(name, 16, "%s%u%s", "Nvme", bbdev_start_idx, "n1"); + name[num_chars] = '\0'; + 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 = g_block_len; + base_bdev->blockcnt = (uint64_t)1024 * 1024 * 1024 * 1024; + TAILQ_INSERT_TAIL(&g_bdev_list, base_bdev, internal.link); + } +} + +static void +create_test_req(struct rpc_construct_raid_bdev *r, const char *raid_name, uint32_t bbdev_start_idx, + bool create_base_bdev) +{ + uint32_t i; + char name[16]; + uint16_t num_chars; + uint32_t bbdev_idx = bbdev_start_idx; + + r->name = strdup(raid_name); + SPDK_CU_ASSERT_FATAL(r->name != NULL); + r->strip_size = (g_strip_size * g_block_len) / 1024; + r->raid_level = 0; + r->base_bdevs.num_base_bdevs = g_max_base_drives; + for (i = 0; i < g_max_base_drives; i++, bbdev_idx++) { + num_chars = snprintf(name, 16, "%s%u%s", "Nvme", bbdev_idx, "n1"); + name[num_chars] = '\0'; + r->base_bdevs.base_bdevs[i] = strdup(name); + SPDK_CU_ASSERT_FATAL(r->base_bdevs.base_bdevs[i] != NULL); + } + if (create_base_bdev == true) { + create_base_bdevs(bbdev_start_idx); + } +} + +static void +free_test_req(struct rpc_construct_raid_bdev *r) +{ + uint8_t i; + + free(r->name); + for (i = 0; i < r->base_bdevs.num_base_bdevs; i++) { + free(r->base_bdevs.base_bdevs[i]); + } +} + +static void +test_construct_raid(void) +{ + struct rpc_construct_raid_bdev req; + struct rpc_destroy_raid_bdev destroy_req; + + set_globals(); + create_test_req(&req, "raid1", 0, true); + rpc_req = &req; + rpc_req_size = sizeof(req); + CU_ASSERT(raid_bdev_init() == 0); + + verify_raid_config_present(req.name, false); + verify_raid_bdev_present(req.name, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&req, true); + verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE); + free_test_req(&req); + + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +static void +test_destroy_raid(void) +{ + struct rpc_construct_raid_bdev construct_req; + struct rpc_destroy_raid_bdev destroy_req; + + set_globals(); + create_test_req(&construct_req, "raid1", 0, true); + rpc_req = &construct_req; + rpc_req_size = sizeof(construct_req); + CU_ASSERT(raid_bdev_init() == 0); + verify_raid_config_present(construct_req.name, false); + verify_raid_bdev_present(construct_req.name, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&construct_req, true); + verify_raid_bdev(&construct_req, true, RAID_BDEV_STATE_ONLINE); + free_test_req(&construct_req); + + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +static void +test_construct_raid_invalid_args(void) +{ + struct rpc_construct_raid_bdev req; + struct rpc_destroy_raid_bdev destroy_req; + struct raid_bdev_config *raid_cfg; + + set_globals(); + rpc_req = &req; + rpc_req_size = sizeof(req); + CU_ASSERT(raid_bdev_init() == 0); + + create_test_req(&req, "raid1", 0, true); + verify_raid_config_present(req.name, false); + verify_raid_bdev_present(req.name, false); + req.raid_level = 1; + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + free_test_req(&req); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + create_test_req(&req, "raid1", 0, false); + verify_raid_config_present(req.name, false); + verify_raid_bdev_present(req.name, false); + g_rpc_err = 0; + g_json_decode_obj_err = 1; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + g_json_decode_obj_err = 0; + free_test_req(&req); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + create_test_req(&req, "raid1", 0, false); + req.strip_size = 1231; + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + free_test_req(&req); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + create_test_req(&req, "raid1", 0, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&req, true); + verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE); + free_test_req(&req); + + create_test_req(&req, "raid1", 0, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + free_test_req(&req); + + create_test_req(&req, "raid2", 0, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + free_test_req(&req); + verify_raid_config_present("raid2", false); + verify_raid_bdev_present("raid2", false); + + create_test_req(&req, "raid2", g_max_base_drives, true); + free(req.base_bdevs.base_bdevs[g_max_base_drives - 1]); + req.base_bdevs.base_bdevs[g_max_base_drives - 1] = strdup("Nvme0n1"); + SPDK_CU_ASSERT_FATAL(req.base_bdevs.base_bdevs[g_max_base_drives - 1] != NULL); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + free_test_req(&req); + verify_raid_config_present("raid2", false); + verify_raid_bdev_present("raid2", false); + + create_test_req(&req, "raid2", g_max_base_drives, true); + free(req.base_bdevs.base_bdevs[g_max_base_drives - 1]); + req.base_bdevs.base_bdevs[g_max_base_drives - 1] = strdup("Nvme100000n1"); + SPDK_CU_ASSERT_FATAL(req.base_bdevs.base_bdevs[g_max_base_drives - 1] != NULL); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + free_test_req(&req); + verify_raid_config_present("raid2", true); + verify_raid_bdev_present("raid2", true); + raid_cfg = raid_bdev_config_find_by_name("raid2"); + SPDK_CU_ASSERT_FATAL(raid_cfg != NULL); + check_and_remove_raid_bdev(raid_cfg); + raid_bdev_config_cleanup(raid_cfg); + + create_test_req(&req, "raid2", g_max_base_drives, false); + g_rpc_err = 0; + g_json_beg_res_ret_err = 1; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + free_test_req(&req); + verify_raid_config_present("raid2", true); + verify_raid_bdev_present("raid2", true); + verify_raid_config_present("raid1", true); + verify_raid_bdev_present("raid1", true); + g_json_beg_res_ret_err = 0; + + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + destroy_req.name = strdup("raid2"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +static void +test_destroy_raid_invalid_args(void) +{ + struct rpc_construct_raid_bdev construct_req; + struct rpc_destroy_raid_bdev destroy_req; + + set_globals(); + create_test_req(&construct_req, "raid1", 0, true); + rpc_req = &construct_req; + rpc_req_size = sizeof(construct_req); + CU_ASSERT(raid_bdev_init() == 0); + verify_raid_config_present(construct_req.name, false); + verify_raid_bdev_present(construct_req.name, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&construct_req, true); + verify_raid_bdev(&construct_req, true, RAID_BDEV_STATE_ONLINE); + free_test_req(&construct_req); + + destroy_req.name = strdup("raid2"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + + destroy_req.name = strdup("raid1"); + g_rpc_err = 0; + g_json_decode_obj_err = 1; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + g_json_decode_obj_err = 0; + g_rpc_err = 0; + free(destroy_req.name); + verify_raid_config_present("raid1", true); + verify_raid_bdev_present("raid1", true); + + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +static void +test_io_channel(void) +{ + struct rpc_construct_raid_bdev req; + struct rpc_destroy_raid_bdev destroy_req; + struct raid_bdev *pbdev; + struct raid_bdev_io_channel *ch_ctx; + uint32_t i; + + set_globals(); + create_test_req(&req, "raid1", 0, true); + rpc_req = &req; + rpc_req_size = sizeof(req); + CU_ASSERT(raid_bdev_init() == 0); + + verify_raid_config_present(req.name, false); + verify_raid_bdev_present(req.name, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&req, true); + verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE); + + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) { + if (strcmp(pbdev->bdev.name, req.name) == 0) { + break; + } + } + CU_ASSERT(pbdev != NULL); + ch_ctx = calloc(1, sizeof(struct raid_bdev_io_channel)); + SPDK_CU_ASSERT_FATAL(ch_ctx != NULL); + + CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0); + for (i = 0; i < req.base_bdevs.num_base_bdevs; i++) { + CU_ASSERT(ch_ctx->base_channel && ch_ctx->base_channel[i] == (void *)0x1); + } + raid_bdev_destroy_cb(pbdev, ch_ctx); + CU_ASSERT(ch_ctx->base_channel == NULL); + free_test_req(&req); + + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + free(ch_ctx); + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +static void +test_write_io(void) +{ + struct rpc_construct_raid_bdev req; + struct rpc_destroy_raid_bdev destroy_req; + struct raid_bdev *pbdev; + struct spdk_io_channel *ch; + struct raid_bdev_io_channel *ch_ctx; + uint32_t i; + struct spdk_bdev_io *bdev_io; + uint32_t count; + uint64_t io_len; + uint64_t lba; + + set_globals(); + create_test_req(&req, "raid1", 0, true); + rpc_req = &req; + rpc_req_size = sizeof(req); + CU_ASSERT(raid_bdev_init() == 0); + verify_raid_config_present(req.name, false); + verify_raid_bdev_present(req.name, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&req, true); + verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE); + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) { + if (strcmp(pbdev->bdev.name, req.name) == 0) { + break; + } + } + CU_ASSERT(pbdev != NULL); + ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct raid_bdev_io_channel)); + SPDK_CU_ASSERT_FATAL(ch != NULL); + ch_ctx = spdk_io_channel_get_ctx(ch); + SPDK_CU_ASSERT_FATAL(ch_ctx != NULL); + + CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0); + for (i = 0; i < req.base_bdevs.num_base_bdevs; i++) { + CU_ASSERT(ch_ctx->base_channel && ch_ctx->base_channel[i] == (void *)0x1); + } + + lba = 0; + for (count = 0; count < g_max_qd; count++) { + bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io)); + SPDK_CU_ASSERT_FATAL(bdev_io != NULL); + io_len = (rand() % g_strip_size) + 1; + bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, SPDK_BDEV_IO_TYPE_WRITE); + lba += g_strip_size; + memset(g_io_output, 0, (g_max_io_size / g_strip_size) + 1 * sizeof(struct io_output)); + g_io_output_index = 0; + raid_bdev_submit_request(ch, bdev_io); + verify_io(bdev_io, req.base_bdevs.num_base_bdevs, ch_ctx, pbdev, + g_child_io_status_flag); + bdev_io_cleanup(bdev_io); + free(bdev_io); + } + free_test_req(&req); + + raid_bdev_destroy_cb(pbdev, ch_ctx); + CU_ASSERT(ch_ctx->base_channel == NULL); + free(ch); + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +static void +test_read_io(void) +{ + struct rpc_construct_raid_bdev req; + struct rpc_destroy_raid_bdev destroy_req; + struct raid_bdev *pbdev; + struct spdk_io_channel *ch; + struct raid_bdev_io_channel *ch_ctx; + uint32_t i; + struct spdk_bdev_io *bdev_io; + uint32_t count; + uint64_t io_len; + uint64_t lba; + + set_globals(); + create_test_req(&req, "raid1", 0, true); + rpc_req = &req; + rpc_req_size = sizeof(req); + CU_ASSERT(raid_bdev_init() == 0); + verify_raid_config_present(req.name, false); + verify_raid_bdev_present(req.name, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&req, true); + verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE); + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) { + if (strcmp(pbdev->bdev.name, req.name) == 0) { + break; + } + } + CU_ASSERT(pbdev != NULL); + ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct raid_bdev_io_channel)); + SPDK_CU_ASSERT_FATAL(ch != NULL); + ch_ctx = spdk_io_channel_get_ctx(ch); + SPDK_CU_ASSERT_FATAL(ch_ctx != NULL); + + CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0); + for (i = 0; i < req.base_bdevs.num_base_bdevs; i++) { + CU_ASSERT(ch_ctx->base_channel && ch_ctx->base_channel[i] == (void *)0x1); + } + free_test_req(&req); + + lba = 0; + for (count = 0; count < g_max_qd; count++) { + bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io)); + SPDK_CU_ASSERT_FATAL(bdev_io != NULL); + io_len = (rand() % g_strip_size) + 1; + bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, SPDK_BDEV_IO_TYPE_READ); + lba += g_strip_size; + memset(g_io_output, 0, (g_max_io_size / g_strip_size) + 1 * sizeof(struct io_output)); + g_io_output_index = 0; + raid_bdev_submit_request(ch, bdev_io); + verify_io(bdev_io, req.base_bdevs.num_base_bdevs, ch_ctx, pbdev, + g_child_io_status_flag); + bdev_io_cleanup(bdev_io); + free(bdev_io); + } + + raid_bdev_destroy_cb(pbdev, ch_ctx); + CU_ASSERT(ch_ctx->base_channel == NULL); + free(ch); + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +/* Test IO failures */ +static void +test_io_failure(void) +{ + struct rpc_construct_raid_bdev req; + struct rpc_destroy_raid_bdev destroy_req; + struct raid_bdev *pbdev; + struct spdk_io_channel *ch; + struct raid_bdev_io_channel *ch_ctx; + uint32_t i; + struct spdk_bdev_io *bdev_io; + uint32_t count; + uint64_t io_len; + uint64_t lba; + + set_globals(); + create_test_req(&req, "raid1", 0, true); + rpc_req = &req; + rpc_req_size = sizeof(req); + CU_ASSERT(raid_bdev_init() == 0); + verify_raid_config_present(req.name, false); + verify_raid_bdev_present(req.name, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&req, true); + verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE); + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) { + if (strcmp(pbdev->bdev.name, req.name) == 0) { + break; + } + } + CU_ASSERT(pbdev != NULL); + ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct raid_bdev_io_channel)); + SPDK_CU_ASSERT_FATAL(ch != NULL); + ch_ctx = spdk_io_channel_get_ctx(ch); + SPDK_CU_ASSERT_FATAL(ch_ctx != NULL); + + CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0); + for (i = 0; i < req.base_bdevs.num_base_bdevs; i++) { + CU_ASSERT(ch_ctx->base_channel && ch_ctx->base_channel[i] == (void *)0x1); + } + free_test_req(&req); + + lba = 0; + for (count = 0; count < 1; count++) { + bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io)); + SPDK_CU_ASSERT_FATAL(bdev_io != NULL); + io_len = (rand() % g_strip_size) + 1; + bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, SPDK_BDEV_IO_TYPE_INVALID); + lba += g_strip_size; + memset(g_io_output, 0, (g_max_io_size / g_strip_size) + 1 * sizeof(struct io_output)); + g_io_output_index = 0; + raid_bdev_submit_request(ch, bdev_io); + verify_io(bdev_io, req.base_bdevs.num_base_bdevs, ch_ctx, pbdev, + INVALID_IO_SUBMIT); + bdev_io_cleanup(bdev_io); + free(bdev_io); + } + + + lba = 0; + g_child_io_status_flag = false; + for (count = 0; count < 1; count++) { + bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io)); + SPDK_CU_ASSERT_FATAL(bdev_io != NULL); + io_len = (rand() % g_strip_size) + 1; + bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, SPDK_BDEV_IO_TYPE_WRITE); + lba += g_strip_size; + memset(g_io_output, 0, (g_max_io_size / g_strip_size) + 1 * sizeof(struct io_output)); + g_io_output_index = 0; + raid_bdev_submit_request(ch, bdev_io); + verify_io(bdev_io, req.base_bdevs.num_base_bdevs, ch_ctx, pbdev, + g_child_io_status_flag); + bdev_io_cleanup(bdev_io); + free(bdev_io); + } + + raid_bdev_destroy_cb(pbdev, ch_ctx); + CU_ASSERT(ch_ctx->base_channel == NULL); + free(ch); + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +/* Test waitq logic */ +static void +test_io_waitq(void) +{ + struct rpc_construct_raid_bdev req; + struct rpc_destroy_raid_bdev destroy_req; + struct raid_bdev *pbdev; + struct spdk_io_channel *ch; + struct raid_bdev_io_channel *ch_ctx; + uint32_t i; + struct spdk_bdev_io *bdev_io; + struct spdk_bdev_io *bdev_io_next; + uint32_t count; + uint64_t io_len; + uint64_t lba; + TAILQ_HEAD(, spdk_bdev_io) head_io; + + set_globals(); + create_test_req(&req, "raid1", 0, true); + rpc_req = &req; + rpc_req_size = sizeof(req); + CU_ASSERT(raid_bdev_init() == 0); + verify_raid_config_present(req.name, false); + verify_raid_bdev_present(req.name, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&req, true); + verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE); + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) { + if (strcmp(pbdev->bdev.name, req.name) == 0) { + break; + } + } + SPDK_CU_ASSERT_FATAL(pbdev != NULL); + ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct raid_bdev_io_channel)); + SPDK_CU_ASSERT_FATAL(ch != NULL); + ch_ctx = spdk_io_channel_get_ctx(ch); + SPDK_CU_ASSERT_FATAL(ch_ctx != NULL); + + CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0); + SPDK_CU_ASSERT_FATAL(ch_ctx->base_channel != NULL); + for (i = 0; i < req.base_bdevs.num_base_bdevs; i++) { + CU_ASSERT(ch_ctx->base_channel[i] == (void *)0x1); + } + free_test_req(&req); + + lba = 0; + TAILQ_INIT(&head_io); + for (count = 0; count < g_max_qd; count++) { + bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io)); + SPDK_CU_ASSERT_FATAL(bdev_io != NULL); + TAILQ_INSERT_TAIL(&head_io, bdev_io, module_link); + io_len = (rand() % g_strip_size) + 1; + bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, SPDK_BDEV_IO_TYPE_WRITE); + g_bdev_io_submit_status = -ENOMEM; + lba += g_strip_size; + raid_bdev_submit_request(ch, bdev_io); + } + + g_ignore_io_output = 1; + + count = get_num_elts_in_waitq(); + CU_ASSERT(count == g_max_qd); + g_bdev_io_submit_status = 0; + process_io_waitq(); + CU_ASSERT(TAILQ_EMPTY(&g_io_waitq)); + + TAILQ_FOREACH_SAFE(bdev_io, &head_io, module_link, bdev_io_next) { + bdev_io_cleanup(bdev_io); + free(bdev_io); + } + + raid_bdev_destroy_cb(pbdev, ch_ctx); + CU_ASSERT(ch_ctx->base_channel == NULL); + g_ignore_io_output = 0; + free(ch); + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +/* Create multiple raids, destroy raids without IO, get_raids related tests */ +static void +test_multi_raid_no_io(void) +{ + struct rpc_construct_raid_bdev *construct_req; + struct rpc_destroy_raid_bdev destroy_req; + struct rpc_get_raid_bdevs get_raids_req; + uint32_t i; + char name[16]; + uint32_t count; + uint32_t bbdev_idx = 0; + + set_globals(); + construct_req = calloc(MAX_RAIDS, sizeof(struct rpc_construct_raid_bdev)); + SPDK_CU_ASSERT_FATAL(construct_req != NULL); + CU_ASSERT(raid_bdev_init() == 0); + for (i = 0; i < g_max_raids; i++) { + count = snprintf(name, 16, "%s%u", "raid", i); + name[count] = '\0'; + create_test_req(&construct_req[i], name, bbdev_idx, true); + verify_raid_config_present(name, false); + verify_raid_bdev_present(name, false); + bbdev_idx += g_max_base_drives; + rpc_req = &construct_req[i]; + rpc_req_size = sizeof(construct_req[0]); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&construct_req[i], true); + verify_raid_bdev(&construct_req[i], true, RAID_BDEV_STATE_ONLINE); + } + + get_raids_req.category = strdup("all"); + rpc_req = &get_raids_req; + rpc_req_size = sizeof(get_raids_req); + g_rpc_err = 0; + g_test_multi_raids = 1; + g_json_decode_obj_construct = 0; + spdk_rpc_get_raid_bdevs(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_get_raids(construct_req, g_max_raids, g_get_raids_output, g_get_raids_count); + for (i = 0; i < g_get_raids_count; i++) { + free(g_get_raids_output[i]); + } + g_get_raids_count = 0; + + get_raids_req.category = strdup("online"); + rpc_req = &get_raids_req; + rpc_req_size = sizeof(get_raids_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_get_raid_bdevs(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_get_raids(construct_req, g_max_raids, g_get_raids_output, g_get_raids_count); + for (i = 0; i < g_get_raids_count; i++) { + free(g_get_raids_output[i]); + } + g_get_raids_count = 0; + + get_raids_req.category = strdup("configuring"); + rpc_req = &get_raids_req; + rpc_req_size = sizeof(get_raids_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_get_raid_bdevs(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + CU_ASSERT(g_get_raids_count == 0); + + get_raids_req.category = strdup("offline"); + rpc_req = &get_raids_req; + rpc_req_size = sizeof(get_raids_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_get_raid_bdevs(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + CU_ASSERT(g_get_raids_count == 0); + + get_raids_req.category = strdup("invalid_category"); + rpc_req = &get_raids_req; + rpc_req_size = sizeof(get_raids_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_get_raid_bdevs(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + CU_ASSERT(g_get_raids_count == 0); + + get_raids_req.category = strdup("all"); + rpc_req = &get_raids_req; + rpc_req_size = sizeof(get_raids_req); + g_rpc_err = 0; + g_json_decode_obj_err = 1; + g_json_decode_obj_construct = 0; + spdk_rpc_get_raid_bdevs(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + g_json_decode_obj_err = 0; + free(get_raids_req.category); + CU_ASSERT(g_get_raids_count == 0); + + get_raids_req.category = strdup("all"); + rpc_req = &get_raids_req; + rpc_req_size = sizeof(get_raids_req); + g_rpc_err = 0; + g_json_beg_res_ret_err = 1; + g_json_decode_obj_construct = 0; + spdk_rpc_get_raid_bdevs(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + g_json_beg_res_ret_err = 0; + CU_ASSERT(g_get_raids_count == 0); + + for (i = 0; i < g_max_raids; i++) { + SPDK_CU_ASSERT_FATAL(construct_req[i].name != NULL); + destroy_req.name = strdup(construct_req[i].name); + count = snprintf(name, 16, "%s", destroy_req.name); + name[count] = '\0'; + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present(name, false); + verify_raid_bdev_present(name, false); + } + g_test_multi_raids = 0; + raid_bdev_exit(); + for (i = 0; i < g_max_raids; i++) { + free_test_req(&construct_req[i]); + } + free(construct_req); + base_bdevs_cleanup(); + reset_globals(); +} + +/* Create multiple raids, fire IOs randomly on various raids */ +static void +test_multi_raid_with_io(void) +{ + struct rpc_construct_raid_bdev *construct_req; + struct rpc_destroy_raid_bdev destroy_req; + uint32_t i, j; + char name[16]; + uint32_t count; + uint32_t bbdev_idx = 0; + struct raid_bdev *pbdev; + struct spdk_io_channel *ch; + struct raid_bdev_io_channel *ch_ctx; + struct spdk_bdev_io *bdev_io; + uint64_t io_len; + uint64_t lba; + struct spdk_io_channel *ch_random; + struct raid_bdev_io_channel *ch_ctx_random; + int16_t iotype; + uint32_t raid_random; + + set_globals(); + construct_req = calloc(g_max_raids, sizeof(struct rpc_construct_raid_bdev)); + SPDK_CU_ASSERT_FATAL(construct_req != NULL); + CU_ASSERT(raid_bdev_init() == 0); + ch = calloc(g_max_raids, sizeof(struct spdk_io_channel) + sizeof(struct raid_bdev_io_channel)); + SPDK_CU_ASSERT_FATAL(ch != NULL); + for (i = 0; i < g_max_raids; i++) { + count = snprintf(name, 16, "%s%u", "raid", i); + name[count] = '\0'; + create_test_req(&construct_req[i], name, bbdev_idx, true); + verify_raid_config_present(name, false); + verify_raid_bdev_present(name, false); + bbdev_idx += g_max_base_drives; + rpc_req = &construct_req[i]; + rpc_req_size = sizeof(construct_req[0]); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&construct_req[i], true); + verify_raid_bdev(&construct_req[i], true, RAID_BDEV_STATE_ONLINE); + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) { + if (strcmp(pbdev->bdev.name, construct_req[i].name) == 0) { + break; + } + } + CU_ASSERT(pbdev != NULL); + ch_ctx = spdk_io_channel_get_ctx(&ch[i]); + SPDK_CU_ASSERT_FATAL(ch_ctx != NULL); + CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0); + CU_ASSERT(ch_ctx->base_channel != NULL); + for (j = 0; j < construct_req[i].base_bdevs.num_base_bdevs; j++) { + CU_ASSERT(ch_ctx->base_channel[j] == (void *)0x1); + } + } + + lba = 0; + for (count = 0; count < g_max_qd; count++) { + bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io)); + SPDK_CU_ASSERT_FATAL(bdev_io != NULL); + io_len = (rand() % g_strip_size) + 1; + iotype = (rand() % 2) ? SPDK_BDEV_IO_TYPE_WRITE : SPDK_BDEV_IO_TYPE_READ; + memset(g_io_output, 0, (g_max_io_size / g_strip_size) + 1 * sizeof(struct io_output)); + g_io_output_index = 0; + raid_random = rand() % g_max_raids; + ch_random = &ch[raid_random]; + ch_ctx_random = spdk_io_channel_get_ctx(ch_random); + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) { + if (strcmp(pbdev->bdev.name, construct_req[raid_random].name) == 0) { + break; + } + } + bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, iotype); + lba += g_strip_size; + CU_ASSERT(pbdev != NULL); + raid_bdev_submit_request(ch_random, bdev_io); + verify_io(bdev_io, g_max_base_drives, ch_ctx_random, pbdev, + g_child_io_status_flag); + bdev_io_cleanup(bdev_io); + free(bdev_io); + } + + for (i = 0; i < g_max_raids; i++) { + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) { + if (strcmp(pbdev->bdev.name, construct_req[i].name) == 0) { + break; + } + } + CU_ASSERT(pbdev != NULL); + ch_ctx = spdk_io_channel_get_ctx(&ch[i]); + SPDK_CU_ASSERT_FATAL(ch_ctx != NULL); + raid_bdev_destroy_cb(pbdev, ch_ctx); + CU_ASSERT(ch_ctx->base_channel == NULL); + destroy_req.name = strdup(construct_req[i].name); + count = snprintf(name, 16, "%s", destroy_req.name); + name[count] = '\0'; + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present(name, false); + verify_raid_bdev_present(name, false); + } + raid_bdev_exit(); + for (i = 0; i < g_max_raids; i++) { + free_test_req(&construct_req[i]); + } + free(construct_req); + free(ch); + base_bdevs_cleanup(); + reset_globals(); +} + +static void +test_io_type_supported(void) +{ + CU_ASSERT(raid_bdev_io_type_supported(NULL, SPDK_BDEV_IO_TYPE_READ) == true); + CU_ASSERT(raid_bdev_io_type_supported(NULL, SPDK_BDEV_IO_TYPE_WRITE) == true); + CU_ASSERT(raid_bdev_io_type_supported(NULL, SPDK_BDEV_IO_TYPE_FLUSH) == true); + CU_ASSERT(raid_bdev_io_type_supported(NULL, SPDK_BDEV_IO_TYPE_INVALID) == false); +} + +static void +test_create_raid_from_config(void) +{ + struct rpc_construct_raid_bdev req; + struct spdk_bdev *bdev; + struct rpc_destroy_raid_bdev destroy_req; + bool can_claim; + struct raid_bdev_config *raid_cfg; + uint32_t base_bdev_slot; + + set_globals(); + create_test_req(&req, "raid1", 0, true); + rpc_req = &req; + rpc_req_size = sizeof(req); + g_config_level_create = 1; + CU_ASSERT(raid_bdev_init() == 0); + g_config_level_create = 0; + + verify_raid_config_present("raid1", true); + verify_raid_bdev_present("raid1", true); + + TAILQ_FOREACH(bdev, &g_bdev_list, internal.link) { + raid_bdev_examine(bdev); + } + + can_claim = raid_bdev_can_claim_bdev("Invalid", &raid_cfg, &base_bdev_slot); + CU_ASSERT(can_claim == false); + + verify_raid_config(&req, true); + verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE); + + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + raid_bdev_exit(); + free_test_req(&req); + base_bdevs_cleanup(); + reset_globals(); +} + +static void +test_create_raid_from_config_invalid_params(void) +{ + struct rpc_construct_raid_bdev req; + uint8_t count; + + set_globals(); + rpc_req = &req; + rpc_req_size = sizeof(req); + g_config_level_create = 1; + + create_test_req(&req, "raid1", 0, true); + free(req.name); + req.name = NULL; + CU_ASSERT(raid_bdev_init() != 0); + free_test_req(&req); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + create_test_req(&req, "raid1", 0, false); + req.strip_size = 1234; + CU_ASSERT(raid_bdev_init() != 0); + free_test_req(&req); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + create_test_req(&req, "raid1", 0, false); + req.raid_level = 1; + CU_ASSERT(raid_bdev_init() != 0); + free_test_req(&req); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + create_test_req(&req, "raid1", 0, false); + req.raid_level = 1; + CU_ASSERT(raid_bdev_init() != 0); + free_test_req(&req); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + create_test_req(&req, "raid1", 0, false); + req.base_bdevs.num_base_bdevs++; + CU_ASSERT(raid_bdev_init() != 0); + req.base_bdevs.num_base_bdevs--; + free_test_req(&req); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + create_test_req(&req, "raid1", 0, false); + req.base_bdevs.num_base_bdevs--; + CU_ASSERT(raid_bdev_init() != 0); + req.base_bdevs.num_base_bdevs++; + free_test_req(&req); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + if (g_max_base_drives > 1) { + create_test_req(&req, "raid1", 0, false); + count = snprintf(req.base_bdevs.base_bdevs[g_max_base_drives - 1], 15, "%s", "Nvme0n1"); + req.base_bdevs.base_bdevs[g_max_base_drives - 1][count] = '\0'; + CU_ASSERT(raid_bdev_init() != 0); + free_test_req(&req); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + } + + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +static void +test_raid_json_dump_info(void) +{ + struct rpc_construct_raid_bdev req; + struct rpc_destroy_raid_bdev destroy_req; + struct raid_bdev *pbdev; + + set_globals(); + create_test_req(&req, "raid1", 0, true); + rpc_req = &req; + rpc_req_size = sizeof(req); + CU_ASSERT(raid_bdev_init() == 0); + + verify_raid_config_present(req.name, false); + verify_raid_bdev_present(req.name, false); + g_rpc_err = 0; + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE); + + TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) { + if (strcmp(pbdev->bdev.name, req.name) == 0) { + break; + } + } + CU_ASSERT(pbdev != NULL); + + CU_ASSERT(raid_bdev_dump_info_json(pbdev, NULL) == 0); + + free_test_req(&req); + + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +static void +test_context_size(void) +{ + CU_ASSERT(raid_bdev_get_ctx_size() == sizeof(struct raid_bdev_io)); +} + +static void +test_asym_base_drives_blockcnt(void) +{ + struct rpc_construct_raid_bdev construct_req; + struct rpc_destroy_raid_bdev destroy_req; + struct spdk_bdev *bbdev; + uint32_t i; + + set_globals(); + create_test_req(&construct_req, "raid1", 0, true); + rpc_req = &construct_req; + rpc_req_size = sizeof(construct_req); + CU_ASSERT(raid_bdev_init() == 0); + verify_raid_config_present(construct_req.name, false); + verify_raid_bdev_present(construct_req.name, false); + g_rpc_err = 0; + for (i = 0; i < construct_req.base_bdevs.num_base_bdevs; i++) { + bbdev = spdk_bdev_get_by_name(construct_req.base_bdevs.base_bdevs[i]); + SPDK_CU_ASSERT_FATAL(bbdev != NULL); + bbdev->blockcnt = rand() + 1; + } + g_json_decode_obj_construct = 1; + spdk_rpc_construct_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config(&construct_req, true); + verify_raid_bdev(&construct_req, true, RAID_BDEV_STATE_ONLINE); + free_test_req(&construct_req); + + destroy_req.name = strdup("raid1"); + rpc_req = &destroy_req; + rpc_req_size = sizeof(destroy_req); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; + spdk_rpc_destroy_raid_bdev(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_raid_config_present("raid1", false); + verify_raid_bdev_present("raid1", false); + + raid_bdev_exit(); + base_bdevs_cleanup(); + reset_globals(); +} + +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("raid", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_construct_raid", test_construct_raid) == NULL || + CU_add_test(suite, "test_destroy_raid", test_destroy_raid) == NULL || + CU_add_test(suite, "test_construct_raid_invalid_args", test_construct_raid_invalid_args) == NULL || + CU_add_test(suite, "test_destroy_raid_invalid_args", test_destroy_raid_invalid_args) == NULL || + CU_add_test(suite, "test_io_channel", test_io_channel) == NULL || + CU_add_test(suite, "test_write_io", test_write_io) == NULL || + CU_add_test(suite, "test_read_io", test_read_io) == NULL || + CU_add_test(suite, "test_io_failure", test_io_failure) == NULL || + CU_add_test(suite, "test_io_waitq", test_io_waitq) == NULL || + CU_add_test(suite, "test_multi_raid_no_io", test_multi_raid_no_io) == NULL || + CU_add_test(suite, "test_multi_raid_with_io", test_multi_raid_with_io) == NULL || + CU_add_test(suite, "test_io_type_supported", test_io_type_supported) == NULL || + CU_add_test(suite, "test_create_raid_from_config", test_create_raid_from_config) == NULL || + CU_add_test(suite, "test_create_raid_from_config_invalid_params", + test_create_raid_from_config_invalid_params) == NULL || + CU_add_test(suite, "test_raid_json_dump_info", test_raid_json_dump_info) == NULL || + CU_add_test(suite, "test_context_size", test_context_size) == NULL || + CU_add_test(suite, "test_asym_base_drives_blockcnt", test_asym_base_drives_blockcnt) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + set_test_opts(); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/.gitignore b/src/spdk/test/unit/lib/bdev/crypto.c/.gitignore new file mode 100644 index 00000000..b2777562 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/crypto.c/.gitignore @@ -0,0 +1 @@ +crypto_ut diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/Makefile b/src/spdk/test/unit/lib/bdev/crypto.c/Makefile new file mode 100644 index 00000000..3241464b --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/crypto.c/Makefile @@ -0,0 +1,41 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = crypto_ut.c +CFLAGS += $(ENV_CFLAGS) + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/crypto_ut.c b/src/spdk/test/unit/lib/bdev/crypto.c/crypto_ut.c new file mode 100644 index 00000000..f01aba19 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/crypto.c/crypto_ut.c @@ -0,0 +1,908 @@ +/*- + * 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_cunit.h" + +#include "common/lib/test_env.c" +#include "spdk_internal/mock.h" +#include "unit/lib/json_mock.c" + +/* these rte_ headers are our local copies of the DPDK headers hacked to mock some functions + * included in them that can't be done with our mock library. + */ +#include "rte_crypto.h" +#include "rte_cryptodev.h" +DEFINE_STUB_V(rte_crypto_op_free, (struct rte_crypto_op *op)); +#include "bdev/crypto/vbdev_crypto.c" + +/* SPDK stubs */ +DEFINE_STUB(spdk_conf_find_section, struct spdk_conf_section *, + (struct spdk_conf *cp, const char *name), NULL); +DEFINE_STUB(spdk_conf_section_get_nval, char *, + (struct spdk_conf_section *sp, const char *key, int idx), NULL); +DEFINE_STUB(spdk_conf_section_get_nmval, char *, + (struct spdk_conf_section *sp, const char *key, int idx1, int idx2), NULL); + +DEFINE_STUB_V(spdk_bdev_module_list_add, (struct spdk_bdev_module *bdev_module)); +DEFINE_STUB_V(spdk_bdev_free_io, (struct spdk_bdev_io *g_bdev_io)); +DEFINE_STUB(spdk_bdev_io_type_supported, bool, (struct spdk_bdev *bdev, + enum spdk_bdev_io_type io_type), 0); +DEFINE_STUB_V(spdk_bdev_module_release_bdev, (struct spdk_bdev *bdev)); +DEFINE_STUB_V(spdk_bdev_close, (struct spdk_bdev_desc *desc)); +DEFINE_STUB(spdk_bdev_get_name, const char *, (const struct spdk_bdev *bdev), 0); +DEFINE_STUB(spdk_env_get_current_core, uint32_t, (void), 0); +DEFINE_STUB(spdk_bdev_get_io_channel, struct spdk_io_channel *, (struct spdk_bdev_desc *desc), 0); +DEFINE_STUB_V(spdk_bdev_unregister, (struct spdk_bdev *bdev, spdk_bdev_unregister_cb cb_fn, + void *cb_arg)); +DEFINE_STUB(spdk_bdev_open, int, (struct spdk_bdev *bdev, bool write, + spdk_bdev_remove_cb_t remove_cb, + void *remove_ctx, struct spdk_bdev_desc **_desc), 0); +DEFINE_STUB(spdk_bdev_module_claim_bdev, int, (struct spdk_bdev *bdev, struct spdk_bdev_desc *desc, + struct spdk_bdev_module *module), 0); +DEFINE_STUB_V(spdk_bdev_module_examine_done, (struct spdk_bdev_module *module)); +DEFINE_STUB(spdk_vbdev_register, int, (struct spdk_bdev *vbdev, struct spdk_bdev **base_bdevs, + int base_bdev_count), 0); +DEFINE_STUB(spdk_bdev_get_by_name, struct spdk_bdev *, (const char *bdev_name), NULL); +DEFINE_STUB(spdk_env_get_socket_id, uint32_t, (uint32_t core), 0); + +/* DPDK stubs */ +DEFINE_STUB(rte_cryptodev_count, uint8_t, (void), 0); +DEFINE_STUB(rte_eal_get_configuration, struct rte_config *, (void), NULL); +DEFINE_STUB_V(rte_mempool_free, (struct rte_mempool *mp)); +DEFINE_STUB(rte_socket_id, unsigned, (void), 0); +DEFINE_STUB(rte_crypto_op_pool_create, struct rte_mempool *, + (const char *name, enum rte_crypto_op_type type, unsigned nb_elts, + unsigned cache_size, uint16_t priv_size, int socket_id), (struct rte_mempool *)1); +DEFINE_STUB(rte_cryptodev_device_count_by_driver, uint8_t, (uint8_t driver_id), 0); +DEFINE_STUB(rte_cryptodev_socket_id, int, (uint8_t dev_id), 0); +DEFINE_STUB(rte_cryptodev_configure, int, (uint8_t dev_id, struct rte_cryptodev_config *config), 0); +DEFINE_STUB(rte_cryptodev_queue_pair_setup, int, (uint8_t dev_id, uint16_t queue_pair_id, + const struct rte_cryptodev_qp_conf *qp_conf, + int socket_id, struct rte_mempool *session_pool), 0); +DEFINE_STUB(rte_cryptodev_start, int, (uint8_t dev_id), 0) +DEFINE_STUB_V(rte_cryptodev_stop, (uint8_t dev_id)); +DEFINE_STUB(rte_cryptodev_sym_session_create, struct rte_cryptodev_sym_session *, + (struct rte_mempool *mempool), (struct rte_cryptodev_sym_session *)1); +DEFINE_STUB(rte_cryptodev_sym_session_clear, int, (uint8_t dev_id, + struct rte_cryptodev_sym_session *sess), 0); +DEFINE_STUB(rte_cryptodev_sym_session_free, int, (struct rte_cryptodev_sym_session *sess), 0); +DEFINE_STUB(rte_cryptodev_sym_session_init, int, (uint8_t dev_id, + struct rte_cryptodev_sym_session *sess, + struct rte_crypto_sym_xform *xforms, struct rte_mempool *mempool), 0); +DEFINE_STUB(rte_vdev_init, int, (const char *name, const char *args), 0); +void __attribute__((noreturn)) __rte_panic(const char *funcname, const char *format, ...) +{ + abort(); +} +struct rte_mempool_ops_table rte_mempool_ops_table; +struct rte_cryptodev *rte_cryptodevs; +__thread unsigned per_lcore__lcore_id = 0; + +/* global vars and setup/cleanup functions used for all test functions */ +struct spdk_bdev_io *g_bdev_io; +struct crypto_bdev_io *g_io_ctx; +struct crypto_io_channel *g_crypto_ch; +struct spdk_io_channel *g_io_ch; +struct vbdev_dev g_device; +struct vbdev_crypto g_crypto_bdev; +struct rte_config *g_test_config; +struct device_qp g_dev_qp; + +#define MAX_TEST_BLOCKS 8192 +struct rte_crypto_op *g_test_crypto_ops[MAX_TEST_BLOCKS]; +struct rte_crypto_op *g_test_dequeued_ops[MAX_TEST_BLOCKS]; +struct rte_crypto_op *g_test_dev_full_ops[MAX_TEST_BLOCKS]; + +/* These globals are externs in our local rte_ header files so we can control + * specific functions for mocking. + */ +uint16_t g_dequeue_mock; +uint16_t g_enqueue_mock; +unsigned ut_rte_crypto_op_bulk_alloc; +int ut_rte_crypto_op_attach_sym_session = 0; + +int ut_rte_cryptodev_info_get = 0; +bool ut_rte_cryptodev_info_get_mocked = false; +void +rte_cryptodev_info_get(uint8_t dev_id, struct rte_cryptodev_info *dev_info) +{ + dev_info->max_nb_queue_pairs = ut_rte_cryptodev_info_get; +} + +unsigned int +rte_cryptodev_sym_get_private_session_size(uint8_t dev_id) +{ + return (unsigned int)dev_id; +} + +void +spdk_bdev_io_get_buf(struct spdk_bdev_io *bdev_io, spdk_bdev_io_get_buf_cb cb, uint64_t len) +{ + cb(g_io_ch, g_bdev_io); +} + +/* Mock these functions to call the callback and then return the value we require */ +int ut_spdk_bdev_readv_blocks = 0; +bool ut_spdk_bdev_readv_blocks_mocked = false; +int +spdk_bdev_readv_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + cb(g_bdev_io, !ut_spdk_bdev_readv_blocks, cb_arg); + return ut_spdk_bdev_readv_blocks; +} + +int ut_spdk_bdev_writev_blocks = 0; +bool ut_spdk_bdev_writev_blocks_mocked = false; +int +spdk_bdev_writev_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + cb(g_bdev_io, !ut_spdk_bdev_writev_blocks, cb_arg); + return ut_spdk_bdev_writev_blocks; +} + +int ut_spdk_bdev_unmap_blocks = 0; +bool ut_spdk_bdev_unmap_blocks_mocked = false; +int +spdk_bdev_unmap_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + cb(g_bdev_io, !ut_spdk_bdev_unmap_blocks, cb_arg); + return ut_spdk_bdev_unmap_blocks; +} + +int ut_spdk_bdev_flush_blocks = 0; +bool ut_spdk_bdev_flush_blocks_mocked = false; +int +spdk_bdev_flush_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb, + void *cb_arg) +{ + cb(g_bdev_io, !ut_spdk_bdev_flush_blocks, cb_arg); + return ut_spdk_bdev_flush_blocks; +} + +int ut_spdk_bdev_reset = 0; +bool ut_spdk_bdev_reset_mocked = false; +int +spdk_bdev_reset(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + cb(g_bdev_io, !ut_spdk_bdev_reset, cb_arg); + return ut_spdk_bdev_reset; +} + +bool g_completion_called = false; +void +spdk_bdev_io_complete(struct spdk_bdev_io *bdev_io, enum spdk_bdev_io_status status) +{ + bdev_io->internal.status = status; + g_completion_called = true; +} + +/* Used in testing device full condition */ +static inline uint16_t +rte_cryptodev_enqueue_burst(uint8_t dev_id, uint16_t qp_id, + struct rte_crypto_op **ops, uint16_t nb_ops) +{ + int i; + + CU_ASSERT(nb_ops > 0); + + for (i = 0; i < nb_ops; i++) { + /* Use this empty (til now) array of pointers to store + * enqueued operations for assertion in dev_full test. + */ + g_test_dev_full_ops[i] = *ops++; + } + + return g_enqueue_mock; +} + +/* This is pretty ugly but in order to complete an IO via the + * poller in the submit path, we need to first call to this func + * to return the dequeued value and also decrement it. On the subsequent + * call it needs to return 0 to indicate to the caller that there are + * no more IOs to drain. + */ +int g_test_overflow = 0; +static inline uint16_t +rte_cryptodev_dequeue_burst(uint8_t dev_id, uint16_t qp_id, + struct rte_crypto_op **ops, uint16_t nb_ops) +{ + CU_ASSERT(nb_ops > 0); + + /* A crypto device can be full on enqueue, the driver is designed to drain + * the device at the time by calling the poller until it's empty, then + * submitting the remaining crypto ops. + */ + if (g_test_overflow) { + if (g_dequeue_mock == 0) { + return 0; + } + *ops = g_test_crypto_ops[g_enqueue_mock]; + (*ops)->status = RTE_CRYPTO_OP_STATUS_SUCCESS; + g_dequeue_mock -= 1; + } + return (g_dequeue_mock + 1); +} + +/* Instead of allocating real memory, assign the allocations to our + * test array for assertion in tests. + */ +static inline unsigned +rte_crypto_op_bulk_alloc(struct rte_mempool *mempool, + enum rte_crypto_op_type type, + struct rte_crypto_op **ops, uint16_t nb_ops) +{ + int i; + + for (i = 0; i < nb_ops; i++) { + *ops++ = g_test_crypto_ops[i]; + } + return ut_rte_crypto_op_bulk_alloc; +} + +static __rte_always_inline void +rte_mempool_put_bulk(struct rte_mempool *mp, void *const *obj_table, + unsigned int n) +{ + return; +} + +static inline void *rte_mempool_get_priv(struct rte_mempool *mp) +{ + return NULL; +} + + +static inline int +rte_crypto_op_attach_sym_session(struct rte_crypto_op *op, + struct rte_cryptodev_sym_session *sess) +{ + return ut_rte_crypto_op_attach_sym_session; +} + +/* Global setup for all tests that share a bunch of preparation... */ +static int +test_setup(void) +{ + int i; + + /* Prepare essential variables for test routines */ + g_bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct crypto_bdev_io)); + g_bdev_io->u.bdev.iovs = calloc(1, sizeof(struct iovec) * 128); + g_bdev_io->bdev = &g_crypto_bdev.crypto_bdev; + g_io_ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct crypto_io_channel)); + g_crypto_ch = (struct crypto_io_channel *)((uint8_t *)g_io_ch + sizeof(struct spdk_io_channel)); + g_io_ctx = (struct crypto_bdev_io *)g_bdev_io->driver_ctx; + memset(&g_device, 0, sizeof(struct vbdev_dev)); + memset(&g_crypto_bdev, 0, sizeof(struct vbdev_crypto)); + g_dev_qp.device = &g_device; + g_io_ctx->crypto_ch = g_crypto_ch; + g_io_ctx->crypto_bdev = &g_crypto_bdev; + g_crypto_ch->device_qp = &g_dev_qp; + g_test_config = calloc(1, sizeof(struct rte_config)); + g_test_config->lcore_count = 1; + + /* Allocate a real mbuf pool so we can test error paths */ + g_mbuf_mp = spdk_mempool_create("mbuf_mp", NUM_MBUFS, sizeof(struct rte_mbuf), + SPDK_MEMPOOL_DEFAULT_CACHE_SIZE, + SPDK_ENV_SOCKET_ID_ANY); + + /* Instead of allocating real rte mempools for these, it's easier and provides the + * same coverage just calloc them here. + */ + for (i = 0; i < MAX_TEST_BLOCKS; i++) { + g_test_crypto_ops[i] = calloc(1, sizeof(struct rte_crypto_op) + + sizeof(struct rte_crypto_sym_op)); + g_test_dequeued_ops[i] = calloc(1, sizeof(struct rte_crypto_op) + + sizeof(struct rte_crypto_sym_op)); + } + return 0; +} + +/* Global teardown for all tests */ +static int +test_cleanup(void) +{ + int i; + + free(g_test_config); + spdk_mempool_free(g_mbuf_mp); + for (i = 0; i < MAX_TEST_BLOCKS; i++) { + free(g_test_crypto_ops[i]); + free(g_test_dequeued_ops[i]); + } + free(g_bdev_io->u.bdev.iovs); + free(g_bdev_io); + free(g_io_ch); + return 0; +} + +static void +test_error_paths(void) +{ + /* Single element block size write, just to test error paths + * in vbdev_crypto_submit_request(). + */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->u.bdev.iovcnt = 1; + g_bdev_io->u.bdev.num_blocks = 1; + g_bdev_io->u.bdev.iovs[0].iov_len = 512; + g_crypto_bdev.crypto_bdev.blocklen = 512; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE; + g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = 1; + + /* test failure of spdk_mempool_get_bulk() */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + MOCK_SET(spdk_mempool_get, NULL); + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + + /* same thing but switch to reads to test error path in _crypto_complete_io() */ + g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ; + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + /* Now with the read_blocks failing */ + g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ; + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + MOCK_SET(spdk_bdev_readv_blocks, -1); + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + MOCK_SET(spdk_bdev_readv_blocks, 0); + MOCK_CLEAR(spdk_mempool_get); + + /* test failure of rte_crypto_op_bulk_alloc() */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + ut_rte_crypto_op_bulk_alloc = 0; + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + ut_rte_crypto_op_bulk_alloc = 1; + + /* test failure of rte_cryptodev_sym_session_create() */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + MOCK_SET(rte_cryptodev_sym_session_create, NULL); + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + MOCK_SET(rte_cryptodev_sym_session_create, (struct rte_cryptodev_sym_session *)1); + + /* test failure of rte_cryptodev_sym_session_init() */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + MOCK_SET(rte_cryptodev_sym_session_init, -1); + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + MOCK_SET(rte_cryptodev_sym_session_init, 0); + + /* test failure of rte_crypto_op_attach_sym_session() */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + ut_rte_crypto_op_attach_sym_session = -1; + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + ut_rte_crypto_op_attach_sym_session = 0; +} + +static void +test_simple_write(void) +{ + /* Single element block size write */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->u.bdev.iovcnt = 1; + g_bdev_io->u.bdev.num_blocks = 1; + g_bdev_io->u.bdev.offset_blocks = 0; + g_bdev_io->u.bdev.iovs[0].iov_len = 512; + g_bdev_io->u.bdev.iovs[0].iov_base = &test_simple_write; + g_crypto_bdev.crypto_bdev.blocklen = 512; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE; + g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = 1; + + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(g_io_ctx->cryop_cnt_remaining == 1); + CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_ENCRYPT); + CU_ASSERT(g_io_ctx->cry_iov.iov_len == 512); + CU_ASSERT(g_io_ctx->cry_iov.iov_base != NULL); + CU_ASSERT(g_io_ctx->cry_offset_blocks == 0); + CU_ASSERT(g_io_ctx->cry_num_blocks == 1); + CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->buf_addr == &test_simple_write); + CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->data_len == 512); + CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->next == NULL); + CU_ASSERT(g_test_crypto_ops[0]->sym->cipher.data.length == 512); + CU_ASSERT(g_test_crypto_ops[0]->sym->cipher.data.offset == 0); + CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->userdata == g_bdev_io); + CU_ASSERT(g_test_crypto_ops[0]->sym->m_dst->buf_addr != NULL); + CU_ASSERT(g_test_crypto_ops[0]->sym->m_dst->data_len == 512); + + spdk_dma_free(g_io_ctx->cry_iov.iov_base); + spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[0]->sym->m_src); + spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[0]->sym->m_dst); +} + +static void +test_simple_read(void) +{ + /* Single element block size read */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->u.bdev.iovcnt = 1; + g_bdev_io->u.bdev.num_blocks = 1; + g_bdev_io->u.bdev.iovs[0].iov_len = 512; + g_bdev_io->u.bdev.iovs[0].iov_base = &test_simple_read; + g_crypto_bdev.crypto_bdev.blocklen = 512; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ; + g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = 1; + + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(g_io_ctx->cryop_cnt_remaining == 1); + CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_DECRYPT); + CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->buf_addr == &test_simple_read); + CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->data_len == 512); + CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->next == NULL); + CU_ASSERT(g_test_crypto_ops[0]->sym->cipher.data.length == 512); + CU_ASSERT(g_test_crypto_ops[0]->sym->cipher.data.offset == 0); + CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->userdata == g_bdev_io); + CU_ASSERT(g_test_crypto_ops[0]->sym->m_dst == NULL); + + spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[0]->sym->m_src); +} + +static void +test_large_rw(void) +{ + unsigned block_len = 512; + unsigned num_blocks = CRYPTO_MAX_IO / block_len; + unsigned io_len = block_len * num_blocks; + unsigned i; + + /* Multi block size read, multi-element */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->u.bdev.iovcnt = 1; + g_bdev_io->u.bdev.num_blocks = num_blocks; + g_bdev_io->u.bdev.iovs[0].iov_len = io_len; + g_bdev_io->u.bdev.iovs[0].iov_base = &test_large_rw; + g_crypto_bdev.crypto_bdev.blocklen = block_len; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ; + g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = num_blocks; + + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(g_io_ctx->cryop_cnt_remaining == (int)num_blocks); + CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_DECRYPT); + + for (i = 0; i < num_blocks; i++) { + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->buf_addr == &test_large_rw + (i * block_len)); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->data_len == block_len); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->next == NULL); + CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.length == block_len); + CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.offset == 0); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->userdata == g_bdev_io); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_dst == NULL); + spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_src); + } + + /* Multi block size write, multi-element */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->u.bdev.iovcnt = 1; + g_bdev_io->u.bdev.num_blocks = num_blocks; + g_bdev_io->u.bdev.iovs[0].iov_len = io_len; + g_bdev_io->u.bdev.iovs[0].iov_base = &test_large_rw; + g_crypto_bdev.crypto_bdev.blocklen = block_len; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE; + g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = num_blocks; + + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(g_io_ctx->cryop_cnt_remaining == (int)num_blocks); + CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_ENCRYPT); + + for (i = 0; i < num_blocks; i++) { + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->buf_addr == &test_large_rw + (i * block_len)); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->data_len == block_len); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->next == NULL); + CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.length == block_len); + CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.offset == 0); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->userdata == g_bdev_io); + CU_ASSERT(g_io_ctx->cry_iov.iov_len == io_len); + CU_ASSERT(g_io_ctx->cry_iov.iov_base != NULL); + CU_ASSERT(g_io_ctx->cry_offset_blocks == 0); + CU_ASSERT(g_io_ctx->cry_num_blocks == num_blocks); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_dst->buf_addr != NULL); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_dst->data_len == block_len); + spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_src); + spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_dst); + } + spdk_dma_free(g_io_ctx->cry_iov.iov_base); +} + +static void +test_dev_full(void) +{ + unsigned block_len = 512; + unsigned num_blocks = 2; + unsigned io_len = block_len * num_blocks; + unsigned i; + + g_test_overflow = 1; + + /* Multi block size read, multi-element */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->u.bdev.iovcnt = 1; + g_bdev_io->u.bdev.num_blocks = num_blocks; + g_bdev_io->u.bdev.iovs[0].iov_len = io_len; + g_bdev_io->u.bdev.iovs[0].iov_base = &test_dev_full; + g_crypto_bdev.crypto_bdev.blocklen = block_len; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ; + g_enqueue_mock = g_dequeue_mock = 1; + ut_rte_crypto_op_bulk_alloc = num_blocks; + + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + + /* this test only completes one of the 2 IOs (in the drain path) */ + CU_ASSERT(g_io_ctx->cryop_cnt_remaining == 1); + CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_DECRYPT); + + for (i = 0; i < num_blocks; i++) { + /* One of the src_mbufs was freed because of the device full condition so + * we can't assert its value here. + */ + CU_ASSERT(g_test_dev_full_ops[i]->sym->cipher.data.length == block_len); + CU_ASSERT(g_test_dev_full_ops[i]->sym->cipher.data.offset == 0); + CU_ASSERT(g_test_dev_full_ops[i]->sym->m_src == g_test_dev_full_ops[i]->sym->m_src); + CU_ASSERT(g_test_dev_full_ops[i]->sym->m_dst == NULL); + } + + /* Only one of the 2 blocks in the test was freed on completion by design, so + * we need to free th other one here. + */ + spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[0]->sym->m_src); + g_test_overflow = 0; +} + +static void +test_crazy_rw(void) +{ + unsigned block_len = 512; + int num_blocks = 4; + int i; + + /* Multi block size read, single element, strange IOV makeup */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->u.bdev.iovcnt = 3; + g_bdev_io->u.bdev.num_blocks = num_blocks; + g_bdev_io->u.bdev.iovs[0].iov_len = 512; + g_bdev_io->u.bdev.iovs[0].iov_base = &test_crazy_rw; + g_bdev_io->u.bdev.iovs[1].iov_len = 1024; + g_bdev_io->u.bdev.iovs[1].iov_base = &test_crazy_rw + 512; + g_bdev_io->u.bdev.iovs[2].iov_len = 512; + g_bdev_io->u.bdev.iovs[2].iov_base = &test_crazy_rw + 512 + 1024; + + g_crypto_bdev.crypto_bdev.blocklen = block_len; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ; + g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = num_blocks; + + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(g_io_ctx->cryop_cnt_remaining == num_blocks); + CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_DECRYPT); + + for (i = 0; i < num_blocks; i++) { + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->buf_addr == &test_crazy_rw + (i * block_len)); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->data_len == block_len); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->next == NULL); + CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.length == block_len); + CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.offset == 0); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->userdata == g_bdev_io); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src == g_test_crypto_ops[i]->sym->m_src); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_dst == NULL); + spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_src); + } + + /* Multi block size write, single element strange IOV makeup */ + num_blocks = 8; + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->u.bdev.iovcnt = 4; + g_bdev_io->u.bdev.num_blocks = num_blocks; + g_bdev_io->u.bdev.iovs[0].iov_len = 2048; + g_bdev_io->u.bdev.iovs[0].iov_base = &test_crazy_rw; + g_bdev_io->u.bdev.iovs[1].iov_len = 512; + g_bdev_io->u.bdev.iovs[1].iov_base = &test_crazy_rw + 2048; + g_bdev_io->u.bdev.iovs[2].iov_len = 512; + g_bdev_io->u.bdev.iovs[2].iov_base = &test_crazy_rw + 2048 + 512; + g_bdev_io->u.bdev.iovs[3].iov_len = 1024; + g_bdev_io->u.bdev.iovs[3].iov_base = &test_crazy_rw + 2048 + 512 + 512; + + g_crypto_bdev.crypto_bdev.blocklen = block_len; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE; + g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = num_blocks; + + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(g_io_ctx->cryop_cnt_remaining == num_blocks); + CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_ENCRYPT); + + for (i = 0; i < num_blocks; i++) { + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->buf_addr == &test_crazy_rw + (i * block_len)); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->data_len == block_len); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->next == NULL); + CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.length == block_len); + CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.offset == 0); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->userdata == g_bdev_io); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_src == g_test_crypto_ops[i]->sym->m_src); + CU_ASSERT(g_test_crypto_ops[i]->sym->m_dst == g_test_crypto_ops[i]->sym->m_dst); + spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_src); + spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_dst); + } + spdk_dma_free(g_io_ctx->cry_iov.iov_base); +} + +static void +test_passthru(void) +{ + /* Make sure these follow our completion callback, test success & fail. */ + g_bdev_io->type = SPDK_BDEV_IO_TYPE_UNMAP; + MOCK_SET(spdk_bdev_unmap_blocks, 0); + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + MOCK_SET(spdk_bdev_unmap_blocks, -1); + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + MOCK_CLEAR(spdk_bdev_unmap_blocks); + + g_bdev_io->type = SPDK_BDEV_IO_TYPE_FLUSH; + MOCK_SET(spdk_bdev_flush_blocks, 0); + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + MOCK_SET(spdk_bdev_flush_blocks, -1); + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + MOCK_CLEAR(spdk_bdev_flush_blocks); + + g_bdev_io->type = SPDK_BDEV_IO_TYPE_RESET; + MOCK_SET(spdk_bdev_reset, 0); + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + MOCK_SET(spdk_bdev_reset, -1); + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + MOCK_CLEAR(spdk_bdev_reset); + + /* We should never get a WZ command, we report that we don't support it. */ + g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE_ZEROES; + vbdev_crypto_submit_request(g_io_ch, g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); +} + +static void +test_initdrivers(void) +{ + int rc; + static struct spdk_mempool *orig_mbuf_mp; + static struct spdk_mempool *orig_session_mp; + + /* No drivers available, not an error though */ + MOCK_SET(rte_eal_get_configuration, g_test_config); + MOCK_SET(rte_cryptodev_count, 0); + rc = vbdev_crypto_init_crypto_drivers(); + CU_ASSERT(rc == 0); + + /* Test failure of DPDK dev init. */ + MOCK_SET(rte_cryptodev_count, 2); + MOCK_SET(rte_vdev_init, -1); + rc = vbdev_crypto_init_crypto_drivers(); + CU_ASSERT(rc == -EINVAL); + MOCK_SET(rte_vdev_init, 0); + + /* Can't create session pool. */ + MOCK_SET(spdk_mempool_create, NULL); + orig_mbuf_mp = g_mbuf_mp; + orig_session_mp = g_session_mp; + rc = vbdev_crypto_init_crypto_drivers(); + g_mbuf_mp = orig_mbuf_mp; + g_session_mp = orig_session_mp; + CU_ASSERT(rc == -ENOMEM); + MOCK_CLEAR(spdk_mempool_create); + + /* Can't create op pool. These tests will alloc and free our g_mbuf_mp + * so save that off here and restore it after each test is over. + */ + orig_mbuf_mp = g_mbuf_mp; + orig_session_mp = g_session_mp; + MOCK_SET(rte_crypto_op_pool_create, NULL); + rc = vbdev_crypto_init_crypto_drivers(); + g_mbuf_mp = orig_mbuf_mp; + g_session_mp = orig_session_mp; + CU_ASSERT(rc == -ENOMEM); + MOCK_SET(rte_crypto_op_pool_create, (struct rte_mempool *)1); + + /* Check resources are sufficient failure. */ + orig_mbuf_mp = g_mbuf_mp; + orig_session_mp = g_session_mp; + rc = vbdev_crypto_init_crypto_drivers(); + g_mbuf_mp = orig_mbuf_mp; + g_session_mp = orig_session_mp; + CU_ASSERT(rc == -EINVAL); + + /* Test crypto dev configure failure. */ + MOCK_SET(rte_cryptodev_device_count_by_driver, 2); + MOCK_SET(rte_cryptodev_info_get, 1); + MOCK_SET(rte_cryptodev_configure, -1); + orig_mbuf_mp = g_mbuf_mp; + orig_session_mp = g_session_mp; + rc = vbdev_crypto_init_crypto_drivers(); + g_mbuf_mp = orig_mbuf_mp; + g_session_mp = orig_session_mp; + MOCK_SET(rte_cryptodev_configure, 0); + CU_ASSERT(rc == -EINVAL); + + /* Test failure of qp setup. */ + MOCK_SET(rte_cryptodev_queue_pair_setup, -1); + orig_mbuf_mp = g_mbuf_mp; + orig_session_mp = g_session_mp; + rc = vbdev_crypto_init_crypto_drivers(); + g_mbuf_mp = orig_mbuf_mp; + g_session_mp = orig_session_mp; + CU_ASSERT(rc == -EINVAL); + MOCK_SET(rte_cryptodev_queue_pair_setup, 0); + + /* Test failure of dev start. */ + MOCK_SET(rte_cryptodev_start, -1); + orig_mbuf_mp = g_mbuf_mp; + orig_session_mp = g_session_mp; + rc = vbdev_crypto_init_crypto_drivers(); + g_mbuf_mp = orig_mbuf_mp; + g_session_mp = orig_session_mp; + CU_ASSERT(rc == -EINVAL); + MOCK_SET(rte_cryptodev_start, 0); + + /* Test happy path. */ + rc = vbdev_crypto_init_crypto_drivers(); + CU_ASSERT(rc == 0); +} + +static void +test_crypto_op_complete(void) +{ + /* Make sure completion code respects failure. */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_FAILED; + g_completion_called = false; + _crypto_operation_complete(g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + CU_ASSERT(g_completion_called == true); + + /* Test read completion. */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ; + g_completion_called = false; + _crypto_operation_complete(g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(g_completion_called == true); + + /* Test write completion success. */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE; + g_completion_called = false; + MOCK_SET(spdk_bdev_writev_blocks, 0); + /* Code under test will free this, if not ASAN will complain. */ + g_io_ctx->cry_iov.iov_base = spdk_dma_malloc(16, 0x10, NULL); + _crypto_operation_complete(g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(g_completion_called == true); + + /* Test write completion failed. */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE; + g_completion_called = false; + MOCK_SET(spdk_bdev_writev_blocks, -1); + /* Code under test will free this, if not ASAN will complain. */ + g_io_ctx->cry_iov.iov_base = spdk_dma_malloc(16, 0x10, NULL); + _crypto_operation_complete(g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + CU_ASSERT(g_completion_called == true); + + /* Test bogus type for this completion. */ + g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + g_bdev_io->type = SPDK_BDEV_IO_TYPE_RESET; + g_completion_called = false; + _crypto_operation_complete(g_bdev_io); + CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED); + CU_ASSERT(g_completion_called == true); +} + +static void +test_supported_io(void) +{ + void *ctx = NULL; + bool rc = true; + + /* Make sure we always report false to WZ, we need the bdev layer to + * send real 0's so we can encrypt/decrypt them. + */ + rc = vbdev_crypto_io_type_supported(ctx, SPDK_BDEV_IO_TYPE_WRITE_ZEROES); + CU_ASSERT(rc == false); +} + +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("crypto", test_setup, test_cleanup); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if (CU_add_test(suite, "test_error_paths", + test_error_paths) == NULL || + CU_add_test(suite, "test_simple_write", + test_simple_write) == NULL || + CU_add_test(suite, "test_simple_read", + test_simple_read) == NULL || + CU_add_test(suite, "test_large_rw", + test_large_rw) == NULL || + CU_add_test(suite, "test_dev_full", + test_dev_full) == NULL || + CU_add_test(suite, "test_crazy_rw", + test_crazy_rw) == NULL || + CU_add_test(suite, "test_passthru", + test_passthru) == NULL || + CU_add_test(suite, "test_initdrivers", + test_initdrivers) == NULL || + CU_add_test(suite, "test_crypto_op_complete", + test_crypto_op_complete) == NULL || + CU_add_test(suite, "test_supported_io", + test_supported_io) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/rte_crypto.h b/src/spdk/test/unit/lib/bdev/crypto.c/rte_crypto.h new file mode 100644 index 00000000..a53a71df --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/crypto.c/rte_crypto.h @@ -0,0 +1,95 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * Copyright(c) 2016 6WIND S.A. + * 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 INTERRUcryptoION) 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. + */ + +#ifndef _RTE_CRYPTO_H_ +#define _RTE_CRYPTO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* In order to mock some DPDK functions, we place headers here with the name name as the DPDK headers + * so these definitions wil be picked up. Only what's mocked is included. + */ + +#include "rte_mbuf.h" +#include "rte_mempool.h" +#include "rte_crypto_sym.h" + +enum rte_crypto_op_type { + RTE_CRYPTO_OP_TYPE_UNDEFINED, + RTE_CRYPTO_OP_TYPE_SYMMETRIC, +}; + +enum rte_crypto_op_status { + RTE_CRYPTO_OP_STATUS_SUCCESS, + RTE_CRYPTO_OP_STATUS_NOT_PROCESSED, + RTE_CRYPTO_OP_STATUS_AUTH_FAILED, + RTE_CRYPTO_OP_STATUS_INVALID_SESSION, + RTE_CRYPTO_OP_STATUS_INVALID_ARGS, + RTE_CRYPTO_OP_STATUS_ERROR, +}; + +struct rte_crypto_op { + uint8_t type; + uint8_t status; + uint8_t sess_type; + uint8_t reserved[5]; + struct rte_mempool *mempool; + rte_iova_t phys_addr; + __extension__ + union { + struct rte_crypto_sym_op sym[0]; + }; +}; + +extern struct rte_mempool * +rte_crypto_op_pool_create(const char *name, enum rte_crypto_op_type type, + unsigned nb_elts, unsigned cache_size, uint16_t priv_size, + int socket_id); + +static inline unsigned +rte_crypto_op_bulk_alloc(struct rte_mempool *mempool, + enum rte_crypto_op_type type, + struct rte_crypto_op **ops, uint16_t nb_ops); + +static inline int +rte_crypto_op_attach_sym_session(struct rte_crypto_op *op, + struct rte_cryptodev_sym_session *sess); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/rte_cryptodev.h b/src/spdk/test/unit/lib/bdev/crypto.c/rte_cryptodev.h new file mode 100644 index 00000000..b941a20d --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/crypto.c/rte_cryptodev.h @@ -0,0 +1,153 @@ +/*- + * + * Copyright(c) 2015-2017 Intel Corporation. All rights reserved. + * Copyright 2014 6WIND S.A. + * + * 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. + */ + +#ifndef _RTE_CRYPTODEV_H_ +#define _RTE_CRYPTODEV_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* In order to mock some DPDK functions, we place headers here with the name name as the DPDK headers + * so these definitions wil be picked up. Only what's mocked is included. + */ + +uint8_t dummy[16]; +#define rte_crypto_op_ctod_offset(c, t, o) &dummy[0] + +#define RTE_CRYPTODEV_FF_MBUF_SCATTER_GATHER (1ULL << 9) + +struct rte_cryptodev_info { + const char *driver_name; + uint8_t driver_id; + struct rte_pci_device *pci_dev; + uint64_t feature_flags; + const struct rte_cryptodev_capabilities *capabilities; + unsigned max_nb_queue_pairs; + struct { + unsigned max_nb_sessions; + unsigned int max_nb_sessions_per_qp; + } sym; +}; + +enum rte_cryptodev_event_type { + RTE_CRYPTODEV_EVENT_UNKNOWN, + RTE_CRYPTODEV_EVENT_ERROR, + RTE_CRYPTODEV_EVENT_MAX +}; + +struct rte_cryptodev_qp_conf { + uint32_t nb_descriptors; +}; + +struct rte_cryptodev_stats { + uint64_t enqueued_count; + uint64_t dequeued_count; + uint64_t enqueue_err_count; + uint64_t dequeue_err_count; +}; + +#define RTE_CRYPTODEV_NAME_MAX_LEN (64) + +extern uint8_t +rte_cryptodev_count(void); + +extern uint8_t +rte_cryptodev_device_count_by_driver(uint8_t driver_id); + +extern int +rte_cryptodev_socket_id(uint8_t dev_id); + +struct rte_cryptodev_config { + int socket_id; + uint16_t nb_queue_pairs; +}; + +extern int +rte_cryptodev_configure(uint8_t dev_id, struct rte_cryptodev_config *config); + +extern int +rte_cryptodev_start(uint8_t dev_id); + +extern void +rte_cryptodev_stop(uint8_t dev_id); + +extern int +rte_cryptodev_queue_pair_setup(uint8_t dev_id, uint16_t queue_pair_id, + const struct rte_cryptodev_qp_conf *qp_conf, int socket_id, + struct rte_mempool *session_pool); + +extern void +rte_cryptodev_info_get(uint8_t dev_id, struct rte_cryptodev_info *dev_info); + +static inline uint16_t +rte_cryptodev_dequeue_burst(uint8_t dev_id, uint16_t qp_id, + struct rte_crypto_op **ops, uint16_t nb_ops); + +static inline uint16_t +rte_cryptodev_enqueue_burst(uint8_t dev_id, uint16_t qp_id, + struct rte_crypto_op **ops, uint16_t nb_ops); + +struct rte_cryptodev_sym_session { + __extension__ void *sess_private_data[0]; +}; + +struct rte_cryptodev_asym_session { + __extension__ void *sess_private_data[0]; +}; + +struct rte_crypto_asym_xform; + +struct rte_cryptodev_sym_session * +rte_cryptodev_sym_session_create(struct rte_mempool *mempool); + +int +rte_cryptodev_sym_session_free(struct rte_cryptodev_sym_session *sess); + +int +rte_cryptodev_sym_session_init(uint8_t dev_id, + struct rte_cryptodev_sym_session *sess, + struct rte_crypto_sym_xform *xforms, + struct rte_mempool *mempool); + +int +rte_cryptodev_sym_session_clear(uint8_t dev_id, + struct rte_cryptodev_sym_session *sess); + +unsigned int +rte_cryptodev_sym_get_private_session_size(uint8_t dev_id); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/rte_mbuf.h b/src/spdk/test/unit/lib/bdev/crypto.c/rte_mbuf.h new file mode 100644 index 00000000..4d69f482 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/crypto.c/rte_mbuf.h @@ -0,0 +1,148 @@ +/*- + * + * Copyright(c) 2015-2017 Intel Corporation. All rights reserved. + * Copyright 2014 6WIND S.A. + * + * 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. + */ + +#ifndef _RTE_MBUF_H_ +#define _RTE_MBUF_H_ + +#include "rte_mempool.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* In order to mock some DPDK functions, we place headers here with the name name as the DPDK headers + * so these definitions wil be picked up. Only what's mocked is included. + */ + +__extension__ +typedef void *MARKER[0]; +__extension__ +typedef uint8_t MARKER8[0]; +__extension__ +typedef uint64_t MARKER64[0]; + +struct rte_mbuf { + MARKER cacheline0; + void *buf_addr; + RTE_STD_C11 + union { + rte_iova_t buf_iova; + rte_iova_t buf_physaddr; + } __rte_aligned(sizeof(rte_iova_t)); + MARKER64 rearm_data; + uint16_t data_off; + RTE_STD_C11 + union { + rte_atomic16_t refcnt_atomic; + uint16_t refcnt; + }; + uint16_t nb_segs; + uint16_t port; + uint64_t ol_flags; + MARKER rx_descriptor_fields1; + RTE_STD_C11 + union { + uint32_t packet_type; + struct { + uint32_t l2_type: 4; + uint32_t l3_type: 4; + uint32_t l4_type: 4; + uint32_t tun_type: 4; + RTE_STD_C11 + union { + uint8_t inner_esp_next_proto; + __extension__ + struct { + uint8_t inner_l2_type: 4; + uint8_t inner_l3_type: 4; + }; + }; + uint32_t inner_l4_type: 4; + }; + }; + uint32_t pkt_len; + uint16_t data_len; + uint16_t vlan_tci; + union { + uint32_t rss; + struct { + RTE_STD_C11 + union { + struct { + uint16_t hash; + uint16_t id; + }; + uint32_t lo; + }; + uint32_t hi; + } fdir; + struct { + uint32_t lo; + uint32_t hi; + } sched; + uint32_t usr; + } hash; + uint16_t vlan_tci_outer; + uint16_t buf_len; + uint64_t timestamp; + MARKER cacheline1 __rte_cache_min_aligned; + RTE_STD_C11 + union { + void *userdata; + uint64_t udata64; + }; + struct rte_mempool *pool; + struct rte_mbuf *next; + RTE_STD_C11 + union { + uint64_t tx_offload; + __extension__ + struct { + uint64_t l2_len: 7; + uint64_t l3_len: 9; + uint64_t l4_len: 8; + uint64_t tso_segsz: 16; + uint64_t outer_l3_len: 9; + uint64_t outer_l2_len: 7; + }; + }; + uint16_t priv_size; + uint16_t timesync; + uint32_t seqn; + +} __rte_cache_aligned; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/rte_mempool.h b/src/spdk/test/unit/lib/bdev/crypto.c/rte_mempool.h new file mode 100644 index 00000000..5750d30f --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/crypto.c/rte_mempool.h @@ -0,0 +1,145 @@ +/*- + * + * Copyright(c) 2015-2017 Intel Corporation. All rights reserved. + * Copyright 2014 6WIND S.A. + * + * 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. + */ + +#ifndef _RTE_MEMPOOL_H_ +#define _RTE_MEMPOOL_H_ + +/** + * @file + * RTE Mempool. + * + * A memory pool is an allocator of fixed-size object. It is + * identified by its name, and uses a ring to store free objects. It + * provides some other optional services, like a per-core object + * cache, and an alignment helper to ensure that objects are padded + * to spread them equally on all RAM channels, ranks, and so on. + * + * Objects owned by a mempool should never be added in another + * mempool. When an object is freed using rte_mempool_put() or + * equivalent, the object data is not modified; the user can save some + * meta-data in the object data and retrieve them when allocating a + * new object. + * + * Note: the mempool implementation is not preemptible. An lcore must not be + * interrupted by another task that uses the same mempool (because it uses a + * ring which is not preemptible). Also, usual mempool functions like + * rte_mempool_get() or rte_mempool_put() are designed to be called from an EAL + * thread due to the internal per-lcore cache. Due to the lack of caching, + * rte_mempool_get() or rte_mempool_put() performance will suffer when called + * by non-EAL threads. Instead, non-EAL threads should call + * rte_mempool_generic_get() or rte_mempool_generic_put() with a user cache + * created with rte_mempool_cache_create(). + */ + +#include <rte_config.h> +#include <rte_spinlock.h> +#include <rte_debug.h> +#include <rte_ring.h> +#include <rte_memcpy.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* In order to mock some DPDK functions, we place headers here with the name name as the DPDK headers + * so these definitions wil be picked up. Only what's mocked is included. + */ + +STAILQ_HEAD(rte_mempool_objhdr_list, rte_mempool_objhdr); +STAILQ_HEAD(rte_mempool_memhdr_list, rte_mempool_memhdr); +struct rte_mempool { + char name[RTE_MEMZONE_NAMESIZE]; + RTE_STD_C11 + union { + void *pool_data; + uint64_t pool_id; + }; + void *pool_config; + const struct rte_memzone *mz; + unsigned int flags; + int socket_id; + uint32_t size; + uint32_t cache_size; + uint32_t elt_size; + uint32_t header_size; + uint32_t trailer_size; + unsigned private_data_size; + int32_t ops_index; + struct rte_mempool_cache *local_cache; + uint32_t populated_size; + struct rte_mempool_objhdr_list elt_list; + uint32_t nb_mem_chunks; + struct rte_mempool_memhdr_list mem_list; +#ifdef RTE_LIBRTE_MEMPOOL_DEBUG + struct rte_mempool_debug_stats stats[RTE_MAX_LCORE]; +#endif +} __rte_cache_aligned; +#define RTE_MEMPOOL_OPS_NAMESIZE 32 +typedef int (*rte_mempool_alloc_t)(struct rte_mempool *mp); +typedef void (*rte_mempool_free_t)(struct rte_mempool *mp); +typedef int (*rte_mempool_enqueue_t)(struct rte_mempool *mp, + void *const *obj_table, unsigned int n); +typedef int (*rte_mempool_dequeue_t)(struct rte_mempool *mp, + void **obj_table, unsigned int n); +typedef unsigned(*rte_mempool_get_count)(const struct rte_mempool *mp); +typedef int (*rte_mempool_get_capabilities_t)(const struct rte_mempool *mp, + unsigned int *flags); +typedef int (*rte_mempool_ops_register_memory_area_t) +(const struct rte_mempool *mp, char *vaddr, rte_iova_t iova, size_t len); +struct rte_mempool_ops { + char name[RTE_MEMPOOL_OPS_NAMESIZE]; + rte_mempool_alloc_t alloc; + rte_mempool_free_t free; + rte_mempool_enqueue_t enqueue; + rte_mempool_dequeue_t dequeue; + rte_mempool_get_count get_count; + rte_mempool_get_capabilities_t get_capabilities; + rte_mempool_ops_register_memory_area_t register_memory_area; +} __rte_cache_aligned; +#define RTE_MEMPOOL_MAX_OPS_IDX 16 +struct rte_mempool_ops_table { + rte_spinlock_t sl; + uint32_t num_ops; + struct rte_mempool_ops ops[RTE_MEMPOOL_MAX_OPS_IDX]; +} __rte_cache_aligned; +extern struct rte_mempool_ops_table rte_mempool_ops_table; +void +rte_mempool_free(struct rte_mempool *mp); +static __rte_always_inline void +rte_mempool_put_bulk(struct rte_mempool *mp, void *const *obj_table, + unsigned int n); + +#ifdef __cplusplus +} +#endif + +#endif /* _RTE_MEMPOOL_H_ */ diff --git a/src/spdk/test/unit/lib/bdev/gpt/Makefile b/src/spdk/test/unit/lib/bdev/gpt/Makefile new file mode 100644 index 00000000..2fad9ba0 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/gpt/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = gpt.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/bdev/gpt/gpt.c/.gitignore b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/.gitignore new file mode 100644 index 00000000..74d476f5 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/.gitignore @@ -0,0 +1 @@ +gpt_ut diff --git a/src/spdk/test/unit/lib/bdev/gpt/gpt.c/Makefile b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/Makefile new file mode 100644 index 00000000..ad21ea2a --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/Makefile @@ -0,0 +1,40 @@ +# +# 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 the copyright holder 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)/../../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = gpt_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/bdev/gpt/gpt.c/gpt_ut.c b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/gpt_ut.c new file mode 100644 index 00000000..3182f9c4 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/gpt_ut.c @@ -0,0 +1,297 @@ +/*- + * 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 the copyright holder 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_cunit.h" + +#include "common/lib/test_env.c" + +#include "bdev/gpt/gpt.c" + +static void +test_check_mbr(void) +{ + struct spdk_gpt *gpt; + struct spdk_mbr *mbr; + unsigned char a[SPDK_GPT_BUFFER_SIZE]; + int re; + + /* spdk_gpt_check_mbr(NULL) does not exist, NULL is filtered out in spdk_gpt_parse() */ + gpt = calloc(1, sizeof(*gpt)); + SPDK_CU_ASSERT_FATAL(gpt != NULL); + + /* Set *gpt is "aaa...", all are mismatch include mbr_signature */ + memset(a, 'a', sizeof(a)); + gpt->buf = &a[0]; + re = spdk_gpt_check_mbr(gpt); + CU_ASSERT(re == -1); + + /* Set mbr->mbr_signature matched, start lba mismatch */ + mbr = (struct spdk_mbr *)gpt->buf; + mbr->mbr_signature = 0xAA55; + re = spdk_gpt_check_mbr(gpt); + CU_ASSERT(re == -1); + + /* Set mbr->partitions[0].start lba matched, os_type mismatch */ + mbr->partitions[0].start_lba = 1; + re = spdk_gpt_check_mbr(gpt); + CU_ASSERT(re == -1); + + /* Set mbr->partitions[0].os_type matched, size_lba mismatch */ + mbr->partitions[0].os_type = 0xEE; + re = spdk_gpt_check_mbr(gpt); + CU_ASSERT(re == -1); + + /* Set mbr->partitions[0].size_lba matched, passing case */ + mbr->partitions[0].size_lba = 0xFFFFFFFF; + re = spdk_gpt_check_mbr(gpt); + CU_ASSERT(re == 0); + + free(gpt); +} + +static void +test_read_header(void) +{ + struct spdk_gpt *gpt; + struct spdk_gpt_header *head; + unsigned char a[SPDK_GPT_BUFFER_SIZE]; + int re; + + /* spdk_gpt_read_header(NULL) does not exist, NULL is filtered out in spdk_gpt_parse() */ + gpt = calloc(1, sizeof(*gpt)); + SPDK_CU_ASSERT_FATAL(gpt != NULL); + + /* Set *gpt is "aaa..." */ + memset(a, 'a', sizeof(a)); + gpt->buf = &a[0]; + + /* Set header_size mismatch */ + gpt->sector_size = 512; + head = (struct spdk_gpt_header *)(gpt->buf + GPT_PRIMARY_PARTITION_TABLE_LBA * gpt->sector_size); + to_le32(&head->header_size, 0x258); + re = spdk_gpt_read_header(gpt); + CU_ASSERT(re == -1); + + /* Set head->header_size matched, header_crc32 mismatch */ + head->header_size = sizeof(*head); + to_le32(&head->header_crc32, 0x22D18C80); + re = spdk_gpt_read_header(gpt); + CU_ASSERT(re == -1); + + /* Set head->header_crc32 matched, gpt_signature mismatch */ + to_le32(&head->header_crc32, 0xC5B2117E); + re = spdk_gpt_read_header(gpt); + CU_ASSERT(re == -1); + + /* Set head->gpt_signature matched, lba_end usable_lba mismatch */ + to_le32(&head->header_crc32, 0xD637335A); + head->gpt_signature[0] = 'E'; + head->gpt_signature[1] = 'F'; + head->gpt_signature[2] = 'I'; + head->gpt_signature[3] = ' '; + head->gpt_signature[4] = 'P'; + head->gpt_signature[5] = 'A'; + head->gpt_signature[6] = 'R'; + head->gpt_signature[7] = 'T'; + re = spdk_gpt_read_header(gpt); + CU_ASSERT(re == -1); + + /* Set gpt->lba_end usable_lba matched, passing case */ + to_le32(&head->header_crc32, 0x30CB7378); + to_le64(&gpt->lba_start, 0x0); + to_le64(&gpt->lba_end, 0x2E935FFE); + to_le64(&head->first_usable_lba, 0xA); + to_le64(&head->last_usable_lba, 0xF4240); + re = spdk_gpt_read_header(gpt); + CU_ASSERT(re == 0); + + free(gpt); +} + +static void +test_read_partitions(void) +{ + struct spdk_gpt *gpt; + struct spdk_gpt_header *head; + unsigned char a[SPDK_GPT_BUFFER_SIZE]; + int re; + + /* spdk_gpt_read_partitions(NULL) does not exist, NULL is filtered out in spdk_gpt_parse() */ + gpt = calloc(1, sizeof(*gpt)); + SPDK_CU_ASSERT_FATAL(gpt != NULL); + + /* Set *gpt is "aaa..." */ + memset(a, 'a', sizeof(a)); + gpt->buf = &a[0]; + + /* Set num_partition_entries exceeds Max value of entries GPT supported */ + gpt->sector_size = 512; + head = (struct spdk_gpt_header *)(gpt->buf + GPT_PRIMARY_PARTITION_TABLE_LBA * gpt->sector_size); + gpt->header = head; + to_le32(&head->num_partition_entries, 0x100); + re = spdk_gpt_read_partitions(gpt); + CU_ASSERT(re == -1); + + /* Set num_partition_entries within Max value, size_of_partition_entry mismatch */ + to_le32(&head->header_crc32, 0x573857BE); + to_le32(&head->num_partition_entries, 0x40); + to_le32(&head->size_of_partition_entry, 0x0); + re = spdk_gpt_read_partitions(gpt); + CU_ASSERT(re == -1); + + /* Set size_of_partition_entry matched, partition_entry_lba mismatch */ + to_le32(&head->header_crc32, 0x5279B712); + to_le32(&head->size_of_partition_entry, 0x80); + to_le64(&head->partition_entry_lba, 0x64); + re = spdk_gpt_read_partitions(gpt); + CU_ASSERT(re == -1); + + /* Set partition_entry_lba matched, partition_entry_array_crc32 mismatch */ + to_le32(&head->header_crc32, 0xEC093B43); + to_le64(&head->partition_entry_lba, 0x20); + to_le32(&head->partition_entry_array_crc32, 0x0); + re = spdk_gpt_read_partitions(gpt); + CU_ASSERT(re == -1); + + /* Set partition_entry_array_crc32 matched, passing case */ + to_le32(&head->header_crc32, 0xE1A08822); + to_le32(&head->partition_entry_array_crc32, 0xEBEE44FB); + to_le32(&head->num_partition_entries, 0x80); + re = spdk_gpt_read_partitions(gpt); + CU_ASSERT(re == 0); + + free(gpt); +} + +static void +test_parse(void) +{ + struct spdk_gpt *gpt; + struct spdk_mbr *mbr; + struct spdk_gpt_header *head; + unsigned char a[SPDK_GPT_BUFFER_SIZE]; + int re; + + /* Set gpt is NULL */ + re = spdk_gpt_parse(NULL); + CU_ASSERT(re == -1); + + /* Set gpt->buf is NULL */ + gpt = calloc(1, sizeof(*gpt)); + SPDK_CU_ASSERT_FATAL(gpt != NULL); + re = spdk_gpt_parse(gpt); + CU_ASSERT(re == -1); + + /* Set *gpt is "aaa...", check_mbr failed */ + memset(a, 'a', sizeof(a)); + gpt->buf = &a[0]; + re = spdk_gpt_parse(gpt); + CU_ASSERT(re == -1); + + /* Set check_mbr passed, read_header failed */ + mbr = (struct spdk_mbr *)gpt->buf; + mbr->mbr_signature = 0xAA55; + mbr->partitions[0].start_lba = 1; + mbr->partitions[0].os_type = 0xEE; + mbr->partitions[0].size_lba = 0xFFFFFFFF; + re = spdk_gpt_parse(gpt); + CU_ASSERT(re == -1); + + /* Set read_header passed, read_partitions failed */ + gpt->sector_size = 512; + head = (struct spdk_gpt_header *)(gpt->buf + GPT_PRIMARY_PARTITION_TABLE_LBA * gpt->sector_size); + head->header_size = sizeof(*head); + head->gpt_signature[0] = 'E'; + head->gpt_signature[1] = 'F'; + head->gpt_signature[2] = 'I'; + head->gpt_signature[3] = ' '; + head->gpt_signature[4] = 'P'; + head->gpt_signature[5] = 'A'; + head->gpt_signature[6] = 'R'; + head->gpt_signature[7] = 'T'; + to_le32(&head->header_crc32, 0x30CB7378); + to_le64(&gpt->lba_start, 0x0); + to_le64(&gpt->lba_end, 0x2E935FFE); + to_le64(&head->first_usable_lba, 0xA); + to_le64(&head->last_usable_lba, 0xF4240); + re = spdk_gpt_parse(gpt); + CU_ASSERT(re == -1); + + /* Set read_partitions passed, all passed */ + to_le32(&head->size_of_partition_entry, 0x80); + to_le64(&head->partition_entry_lba, 0x20); + to_le32(&head->header_crc32, 0xE1A08822); + to_le32(&head->partition_entry_array_crc32, 0xEBEE44FB); + to_le32(&head->num_partition_entries, 0x80); + re = spdk_gpt_parse(gpt); + CU_ASSERT(re == 0); + + free(gpt); +} + +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("gpt_parse", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "parse", + test_parse) == NULL || + CU_add_test(suite, "check mbr", + test_check_mbr) == NULL || + CU_add_test(suite, "read header", + test_read_header) == NULL || + CU_add_test(suite, "read partitions", + test_read_partitions) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/bdev/mt/Makefile b/src/spdk/test/unit/lib/bdev/mt/Makefile new file mode 100644 index 00000000..a19b345a --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/mt/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = bdev.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/bdev/mt/bdev.c/.gitignore b/src/spdk/test/unit/lib/bdev/mt/bdev.c/.gitignore new file mode 100644 index 00000000..a5a22d0d --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/mt/bdev.c/.gitignore @@ -0,0 +1 @@ +bdev_ut diff --git a/src/spdk/test/unit/lib/bdev/mt/bdev.c/Makefile b/src/spdk/test/unit/lib/bdev/mt/bdev.c/Makefile new file mode 100644 index 00000000..96b48574 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/mt/bdev.c/Makefile @@ -0,0 +1,41 @@ +# +# 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)/../../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.mock.unittest.mk + +TEST_FILE = bdev_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/bdev/mt/bdev.c/bdev_ut.c b/src/spdk/test/unit/lib/bdev/mt/bdev.c/bdev_ut.c new file mode 100644 index 00000000..09740fa9 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/mt/bdev.c/bdev_ut.c @@ -0,0 +1,1360 @@ +/*- + * 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_cunit.h" + +#include "common/lib/test_env.c" +#include "common/lib/ut_multithread.c" +#include "unit/lib/json_mock.c" + +#include "spdk/config.h" +/* HACK: disable VTune integration so the unit test doesn't need VTune headers and libs to build */ +#undef SPDK_CONFIG_VTUNE + +#include "bdev/bdev.c" + +#define BDEV_UT_NUM_THREADS 3 + +DEFINE_STUB_V(spdk_scsi_nvme_translate, (const struct spdk_bdev_io *bdev_io, + int *sc, int *sk, int *asc, int *ascq)); + +DEFINE_STUB(spdk_conf_find_section, struct spdk_conf_section *, (struct spdk_conf *cp, + const char *name), NULL); +DEFINE_STUB(spdk_conf_section_get_nmval, char *, + (struct spdk_conf_section *sp, const char *key, int idx1, int idx2), NULL); +DEFINE_STUB(spdk_conf_section_get_intval, int, (struct spdk_conf_section *sp, const char *key), -1); + +struct spdk_trace_histories *g_trace_histories; +DEFINE_STUB_V(spdk_trace_add_register_fn, (struct spdk_trace_register_fn *reg_fn)); +DEFINE_STUB_V(spdk_trace_register_owner, (uint8_t type, char id_prefix)); +DEFINE_STUB_V(spdk_trace_register_object, (uint8_t type, char id_prefix)); +DEFINE_STUB_V(spdk_trace_register_description, (const char *name, const char *short_name, + uint16_t tpoint_id, uint8_t owner_type, + uint8_t object_type, uint8_t new_object, + uint8_t arg1_is_ptr, const char *arg1_name)); +DEFINE_STUB_V(_spdk_trace_record, (uint64_t tsc, uint16_t tpoint_id, uint16_t poller_id, + uint32_t size, uint64_t object_id, uint64_t arg1)); + +struct ut_bdev { + struct spdk_bdev bdev; + void *io_target; +}; + +struct ut_bdev_channel { + TAILQ_HEAD(, spdk_bdev_io) outstanding_io; + uint32_t outstanding_cnt; + uint32_t avail_cnt; +}; + +int g_io_device; +struct ut_bdev g_bdev; +struct spdk_bdev_desc *g_desc; +bool g_teardown_done = false; +bool g_get_io_channel = true; +bool g_create_ch = true; +bool g_init_complete_called = false; +bool g_fini_start_called = true; + +static int +stub_create_ch(void *io_device, void *ctx_buf) +{ + struct ut_bdev_channel *ch = ctx_buf; + + if (g_create_ch == false) { + return -1; + } + + TAILQ_INIT(&ch->outstanding_io); + ch->outstanding_cnt = 0; + /* + * When avail gets to 0, the submit_request function will return ENOMEM. + * Most tests to not want ENOMEM to occur, so by default set this to a + * big value that won't get hit. The ENOMEM tests can then override this + * value to something much smaller to induce ENOMEM conditions. + */ + ch->avail_cnt = 2048; + return 0; +} + +static void +stub_destroy_ch(void *io_device, void *ctx_buf) +{ +} + +static struct spdk_io_channel * +stub_get_io_channel(void *ctx) +{ + struct ut_bdev *ut_bdev = ctx; + + if (g_get_io_channel == true) { + return spdk_get_io_channel(ut_bdev->io_target); + } else { + return NULL; + } +} + +static int +stub_destruct(void *ctx) +{ + return 0; +} + +static void +stub_submit_request(struct spdk_io_channel *_ch, struct spdk_bdev_io *bdev_io) +{ + struct ut_bdev_channel *ch = spdk_io_channel_get_ctx(_ch); + + if (bdev_io->type == SPDK_BDEV_IO_TYPE_RESET) { + struct spdk_bdev_io *io; + + while (!TAILQ_EMPTY(&ch->outstanding_io)) { + io = TAILQ_FIRST(&ch->outstanding_io); + TAILQ_REMOVE(&ch->outstanding_io, io, module_link); + ch->outstanding_cnt--; + spdk_bdev_io_complete(io, SPDK_BDEV_IO_STATUS_FAILED); + ch->avail_cnt++; + } + } + + if (ch->avail_cnt > 0) { + TAILQ_INSERT_TAIL(&ch->outstanding_io, bdev_io, module_link); + ch->outstanding_cnt++; + ch->avail_cnt--; + } else { + spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_NOMEM); + } +} + +static uint32_t +stub_complete_io(void *io_target, uint32_t num_to_complete) +{ + struct spdk_io_channel *_ch = spdk_get_io_channel(io_target); + struct ut_bdev_channel *ch = spdk_io_channel_get_ctx(_ch); + struct spdk_bdev_io *io; + bool complete_all = (num_to_complete == 0); + uint32_t num_completed = 0; + + while (complete_all || num_completed < num_to_complete) { + if (TAILQ_EMPTY(&ch->outstanding_io)) { + break; + } + io = TAILQ_FIRST(&ch->outstanding_io); + TAILQ_REMOVE(&ch->outstanding_io, io, module_link); + ch->outstanding_cnt--; + spdk_bdev_io_complete(io, SPDK_BDEV_IO_STATUS_SUCCESS); + ch->avail_cnt++; + num_completed++; + } + + spdk_put_io_channel(_ch); + return num_completed; +} + +static struct spdk_bdev_fn_table fn_table = { + .get_io_channel = stub_get_io_channel, + .destruct = stub_destruct, + .submit_request = stub_submit_request, +}; + +static int +module_init(void) +{ + return 0; +} + +static void +module_fini(void) +{ +} + +static void +init_complete(void) +{ + g_init_complete_called = true; +} + +static void +fini_start(void) +{ + g_fini_start_called = true; +} + +struct spdk_bdev_module bdev_ut_if = { + .name = "bdev_ut", + .module_init = module_init, + .module_fini = module_fini, + .init_complete = init_complete, + .fini_start = fini_start, +}; + +SPDK_BDEV_MODULE_REGISTER(&bdev_ut_if) + +static void +register_bdev(struct ut_bdev *ut_bdev, char *name, void *io_target) +{ + memset(ut_bdev, 0, sizeof(*ut_bdev)); + + ut_bdev->io_target = io_target; + ut_bdev->bdev.ctxt = ut_bdev; + ut_bdev->bdev.name = name; + ut_bdev->bdev.fn_table = &fn_table; + ut_bdev->bdev.module = &bdev_ut_if; + ut_bdev->bdev.blocklen = 4096; + ut_bdev->bdev.blockcnt = 1024; + + spdk_bdev_register(&ut_bdev->bdev); +} + +static void +unregister_bdev(struct ut_bdev *ut_bdev) +{ + /* Handle any deferred messages. */ + poll_threads(); + spdk_bdev_unregister(&ut_bdev->bdev, NULL, NULL); +} + +static void +bdev_init_cb(void *done, int rc) +{ + CU_ASSERT(rc == 0); + *(bool *)done = true; +} + +static void +setup_test(void) +{ + bool done = false; + + allocate_threads(BDEV_UT_NUM_THREADS); + set_thread(0); + spdk_bdev_initialize(bdev_init_cb, &done); + spdk_io_device_register(&g_io_device, stub_create_ch, stub_destroy_ch, + sizeof(struct ut_bdev_channel), NULL); + register_bdev(&g_bdev, "ut_bdev", &g_io_device); + spdk_bdev_open(&g_bdev.bdev, true, NULL, NULL, &g_desc); +} + +static void +finish_cb(void *cb_arg) +{ + g_teardown_done = true; +} + +static void +teardown_test(void) +{ + set_thread(0); + g_teardown_done = false; + spdk_bdev_close(g_desc); + g_desc = NULL; + unregister_bdev(&g_bdev); + spdk_io_device_unregister(&g_io_device, NULL); + spdk_bdev_finish(finish_cb, NULL); + poll_threads(); + memset(&g_bdev, 0, sizeof(g_bdev)); + CU_ASSERT(g_teardown_done == true); + g_teardown_done = false; + free_threads(); +} + +static uint32_t +bdev_io_tailq_cnt(bdev_io_tailq_t *tailq) +{ + struct spdk_bdev_io *io; + uint32_t cnt = 0; + + TAILQ_FOREACH(io, tailq, internal.link) { + cnt++; + } + + return cnt; +} + +static void +basic(void) +{ + g_init_complete_called = false; + setup_test(); + CU_ASSERT(g_init_complete_called == true); + + set_thread(0); + + g_get_io_channel = false; + g_ut_threads[0].ch = spdk_bdev_get_io_channel(g_desc); + CU_ASSERT(g_ut_threads[0].ch == NULL); + + g_get_io_channel = true; + g_create_ch = false; + g_ut_threads[0].ch = spdk_bdev_get_io_channel(g_desc); + CU_ASSERT(g_ut_threads[0].ch == NULL); + + g_get_io_channel = true; + g_create_ch = true; + g_ut_threads[0].ch = spdk_bdev_get_io_channel(g_desc); + CU_ASSERT(g_ut_threads[0].ch != NULL); + spdk_put_io_channel(g_ut_threads[0].ch); + + g_fini_start_called = false; + teardown_test(); + CU_ASSERT(g_fini_start_called == true); +} + +static void +_bdev_removed(void *done) +{ + *(bool *)done = true; +} + +static void +_bdev_unregistered(void *done, int rc) +{ + CU_ASSERT(rc == 0); + *(bool *)done = true; +} + +static void +unregister_and_close(void) +{ + bool done, remove_notify; + struct spdk_bdev_desc *desc; + + setup_test(); + set_thread(0); + + /* setup_test() automatically opens the bdev, + * but this test needs to do that in a different + * way. */ + spdk_bdev_close(g_desc); + poll_threads(); + + remove_notify = false; + spdk_bdev_open(&g_bdev.bdev, true, _bdev_removed, &remove_notify, &desc); + CU_ASSERT(remove_notify == false); + CU_ASSERT(desc != NULL); + + /* There is an open descriptor on the device. Unregister it, + * which can't proceed until the descriptor is closed. */ + done = false; + spdk_bdev_unregister(&g_bdev.bdev, _bdev_unregistered, &done); + /* No polling has occurred, so neither of these should execute */ + CU_ASSERT(remove_notify == false); + CU_ASSERT(done == false); + + /* Prior to the unregister completing, close the descriptor */ + spdk_bdev_close(desc); + + /* Poll the threads to allow all events to be processed */ + poll_threads(); + + /* Remove notify should not have been called because the + * descriptor is already closed. */ + CU_ASSERT(remove_notify == false); + + /* The unregister should have completed */ + CU_ASSERT(done == true); + + spdk_bdev_finish(finish_cb, NULL); + poll_threads(); + free_threads(); +} + +static void +reset_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + bool *done = cb_arg; + + CU_ASSERT(success == true); + *done = true; + spdk_bdev_free_io(bdev_io); +} + +static void +put_channel_during_reset(void) +{ + struct spdk_io_channel *io_ch; + bool done = false; + + setup_test(); + + set_thread(0); + io_ch = spdk_bdev_get_io_channel(g_desc); + CU_ASSERT(io_ch != NULL); + + /* + * Start a reset, but then put the I/O channel before + * the deferred messages for the reset get a chance to + * execute. + */ + spdk_bdev_reset(g_desc, io_ch, reset_done, &done); + spdk_put_io_channel(io_ch); + poll_threads(); + stub_complete_io(g_bdev.io_target, 0); + + teardown_test(); +} + +static void +aborted_reset_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + enum spdk_bdev_io_status *status = cb_arg; + + *status = success ? SPDK_BDEV_IO_STATUS_SUCCESS : SPDK_BDEV_IO_STATUS_FAILED; + spdk_bdev_free_io(bdev_io); +} + +static void +aborted_reset(void) +{ + struct spdk_io_channel *io_ch[2]; + enum spdk_bdev_io_status status1 = SPDK_BDEV_IO_STATUS_PENDING, + status2 = SPDK_BDEV_IO_STATUS_PENDING; + + setup_test(); + + set_thread(0); + io_ch[0] = spdk_bdev_get_io_channel(g_desc); + CU_ASSERT(io_ch[0] != NULL); + spdk_bdev_reset(g_desc, io_ch[0], aborted_reset_done, &status1); + poll_threads(); + CU_ASSERT(g_bdev.bdev.internal.reset_in_progress != NULL); + + /* + * First reset has been submitted on ch0. Now submit a second + * reset on ch1 which will get queued since there is already a + * reset in progress. + */ + set_thread(1); + io_ch[1] = spdk_bdev_get_io_channel(g_desc); + CU_ASSERT(io_ch[1] != NULL); + spdk_bdev_reset(g_desc, io_ch[1], aborted_reset_done, &status2); + poll_threads(); + CU_ASSERT(g_bdev.bdev.internal.reset_in_progress != NULL); + + /* + * Now destroy ch1. This will abort the queued reset. Check that + * the second reset was completed with failed status. Also check + * that bdev->internal.reset_in_progress != NULL, since the + * original reset has not been completed yet. This ensures that + * the bdev code is correctly noticing that the failed reset is + * *not* the one that had been submitted to the bdev module. + */ + set_thread(1); + spdk_put_io_channel(io_ch[1]); + poll_threads(); + CU_ASSERT(status2 == SPDK_BDEV_IO_STATUS_FAILED); + CU_ASSERT(g_bdev.bdev.internal.reset_in_progress != NULL); + + /* + * Now complete the first reset, verify that it completed with SUCCESS + * status and that bdev->internal.reset_in_progress is also set back to NULL. + */ + set_thread(0); + spdk_put_io_channel(io_ch[0]); + stub_complete_io(g_bdev.io_target, 0); + poll_threads(); + CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(g_bdev.bdev.internal.reset_in_progress == NULL); + + teardown_test(); +} + +static void +io_during_io_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + enum spdk_bdev_io_status *status = cb_arg; + + *status = success ? SPDK_BDEV_IO_STATUS_SUCCESS : SPDK_BDEV_IO_STATUS_FAILED; + spdk_bdev_free_io(bdev_io); +} + +static void +io_during_reset(void) +{ + struct spdk_io_channel *io_ch[2]; + struct spdk_bdev_channel *bdev_ch[2]; + enum spdk_bdev_io_status status0, status1, status_reset; + int rc; + + setup_test(); + + /* + * First test normal case - submit an I/O on each of two channels (with no resets) + * and verify they complete successfully. + */ + set_thread(0); + io_ch[0] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]); + CU_ASSERT(bdev_ch[0]->flags == 0); + status0 = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &status0); + CU_ASSERT(rc == 0); + + set_thread(1); + io_ch[1] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]); + CU_ASSERT(bdev_ch[1]->flags == 0); + status1 = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &status1); + CU_ASSERT(rc == 0); + + poll_threads(); + CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_PENDING); + CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_PENDING); + + set_thread(0); + stub_complete_io(g_bdev.io_target, 0); + CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_SUCCESS); + + set_thread(1); + stub_complete_io(g_bdev.io_target, 0); + CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_SUCCESS); + + /* + * Now submit a reset, and leave it pending while we submit I/O on two different + * channels. These I/O should be failed by the bdev layer since the reset is in + * progress. + */ + set_thread(0); + status_reset = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_reset(g_desc, io_ch[0], io_during_io_done, &status_reset); + CU_ASSERT(rc == 0); + + CU_ASSERT(bdev_ch[0]->flags == 0); + CU_ASSERT(bdev_ch[1]->flags == 0); + poll_threads(); + CU_ASSERT(bdev_ch[0]->flags == BDEV_CH_RESET_IN_PROGRESS); + CU_ASSERT(bdev_ch[1]->flags == BDEV_CH_RESET_IN_PROGRESS); + + set_thread(0); + status0 = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &status0); + CU_ASSERT(rc == 0); + + set_thread(1); + status1 = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &status1); + CU_ASSERT(rc == 0); + + /* + * A reset is in progress so these read I/O should complete with failure. Note that we + * need to poll_threads() since I/O completed inline have their completion deferred. + */ + poll_threads(); + CU_ASSERT(status_reset == SPDK_BDEV_IO_STATUS_PENDING); + CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_FAILED); + CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_FAILED); + + /* + * Complete the reset + */ + set_thread(0); + stub_complete_io(g_bdev.io_target, 0); + + /* + * Only poll thread 0. We should not get a completion. + */ + poll_thread(0); + CU_ASSERT(status_reset == SPDK_BDEV_IO_STATUS_PENDING); + + /* + * Poll both thread 0 and 1 so the messages can propagate and we + * get a completion. + */ + poll_threads(); + CU_ASSERT(status_reset == SPDK_BDEV_IO_STATUS_SUCCESS); + + spdk_put_io_channel(io_ch[0]); + set_thread(1); + spdk_put_io_channel(io_ch[1]); + poll_threads(); + + teardown_test(); +} + +static void +basic_qos(void) +{ + struct spdk_io_channel *io_ch[2]; + struct spdk_bdev_channel *bdev_ch[2]; + struct spdk_bdev *bdev; + enum spdk_bdev_io_status status; + int rc; + + setup_test(); + + /* Enable QoS */ + bdev = &g_bdev.bdev; + bdev->internal.qos = calloc(1, sizeof(*bdev->internal.qos)); + SPDK_CU_ASSERT_FATAL(bdev->internal.qos != NULL); + TAILQ_INIT(&bdev->internal.qos->queued); + /* + * Enable both IOPS and bandwidth rate limits. + * In this case, both rate limits will take equal effect. + */ + /* 2000 I/O per second, or 2 per millisecond */ + bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT].limit = 2000; + /* 8K byte per millisecond with 4K block size */ + bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT].limit = 8192000; + + g_get_io_channel = true; + + set_thread(0); + io_ch[0] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]); + CU_ASSERT(bdev_ch[0]->flags == BDEV_CH_QOS_ENABLED); + + set_thread(1); + io_ch[1] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]); + CU_ASSERT(bdev_ch[1]->flags == BDEV_CH_QOS_ENABLED); + + /* + * Send an I/O on thread 0, which is where the QoS thread is running. + */ + set_thread(0); + status = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &status); + CU_ASSERT(rc == 0); + CU_ASSERT(status == SPDK_BDEV_IO_STATUS_PENDING); + poll_threads(); + stub_complete_io(g_bdev.io_target, 0); + poll_threads(); + CU_ASSERT(status == SPDK_BDEV_IO_STATUS_SUCCESS); + + /* Send an I/O on thread 1. The QoS thread is not running here. */ + status = SPDK_BDEV_IO_STATUS_PENDING; + set_thread(1); + rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &status); + CU_ASSERT(rc == 0); + CU_ASSERT(status == SPDK_BDEV_IO_STATUS_PENDING); + poll_threads(); + /* Complete I/O on thread 1. This should not complete the I/O we submitted */ + stub_complete_io(g_bdev.io_target, 0); + poll_threads(); + CU_ASSERT(status == SPDK_BDEV_IO_STATUS_PENDING); + /* Now complete I/O on thread 0 */ + set_thread(0); + poll_threads(); + stub_complete_io(g_bdev.io_target, 0); + poll_threads(); + CU_ASSERT(status == SPDK_BDEV_IO_STATUS_SUCCESS); + + /* Tear down the channels */ + set_thread(0); + spdk_put_io_channel(io_ch[0]); + set_thread(1); + spdk_put_io_channel(io_ch[1]); + poll_threads(); + set_thread(0); + + /* Close the descriptor, which should stop the qos channel */ + spdk_bdev_close(g_desc); + poll_threads(); + CU_ASSERT(bdev->internal.qos->ch == NULL); + + spdk_bdev_open(bdev, true, NULL, NULL, &g_desc); + + /* Create the channels in reverse order. */ + set_thread(1); + io_ch[1] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]); + CU_ASSERT(bdev_ch[1]->flags == BDEV_CH_QOS_ENABLED); + + set_thread(0); + io_ch[0] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]); + CU_ASSERT(bdev_ch[0]->flags == BDEV_CH_QOS_ENABLED); + + /* Confirm that the qos thread is now thread 1 */ + CU_ASSERT(bdev->internal.qos->ch == bdev_ch[1]); + + /* Tear down the channels */ + set_thread(0); + spdk_put_io_channel(io_ch[0]); + set_thread(1); + spdk_put_io_channel(io_ch[1]); + poll_threads(); + + set_thread(0); + + teardown_test(); +} + +static void +io_during_qos_queue(void) +{ + struct spdk_io_channel *io_ch[2]; + struct spdk_bdev_channel *bdev_ch[2]; + struct spdk_bdev *bdev; + enum spdk_bdev_io_status status0, status1; + int rc; + + setup_test(); + reset_time(); + + /* Enable QoS */ + bdev = &g_bdev.bdev; + bdev->internal.qos = calloc(1, sizeof(*bdev->internal.qos)); + SPDK_CU_ASSERT_FATAL(bdev->internal.qos != NULL); + TAILQ_INIT(&bdev->internal.qos->queued); + /* + * Enable both IOPS and bandwidth rate limits. + * In this case, IOPS rate limit will take effect first. + */ + /* 1000 I/O per second, or 1 per millisecond */ + bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT].limit = 1000; + /* 8K byte per millisecond with 4K block size */ + bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT].limit = 8192000; + + g_get_io_channel = true; + + /* Create channels */ + set_thread(0); + io_ch[0] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]); + CU_ASSERT(bdev_ch[0]->flags == BDEV_CH_QOS_ENABLED); + + set_thread(1); + io_ch[1] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]); + CU_ASSERT(bdev_ch[1]->flags == BDEV_CH_QOS_ENABLED); + + /* Send two I/O */ + status1 = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &status1); + CU_ASSERT(rc == 0); + CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_PENDING); + set_thread(0); + status0 = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &status0); + CU_ASSERT(rc == 0); + CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_PENDING); + + /* Complete any I/O that arrived at the disk */ + poll_threads(); + set_thread(1); + stub_complete_io(g_bdev.io_target, 0); + set_thread(0); + stub_complete_io(g_bdev.io_target, 0); + poll_threads(); + + /* Only one of the I/O should complete. (logical XOR) */ + if (status0 == SPDK_BDEV_IO_STATUS_SUCCESS) { + CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_PENDING); + } else { + CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_SUCCESS); + } + + /* Advance in time by a millisecond */ + increment_time(1000); + + /* Complete more I/O */ + poll_threads(); + set_thread(1); + stub_complete_io(g_bdev.io_target, 0); + set_thread(0); + stub_complete_io(g_bdev.io_target, 0); + poll_threads(); + + /* Now the second I/O should be done */ + CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_SUCCESS); + + /* Tear down the channels */ + set_thread(1); + spdk_put_io_channel(io_ch[1]); + set_thread(0); + spdk_put_io_channel(io_ch[0]); + poll_threads(); + + teardown_test(); +} + +static void +io_during_qos_reset(void) +{ + struct spdk_io_channel *io_ch[2]; + struct spdk_bdev_channel *bdev_ch[2]; + struct spdk_bdev *bdev; + enum spdk_bdev_io_status status0, status1, reset_status; + int rc; + + setup_test(); + reset_time(); + + /* Enable QoS */ + bdev = &g_bdev.bdev; + bdev->internal.qos = calloc(1, sizeof(*bdev->internal.qos)); + SPDK_CU_ASSERT_FATAL(bdev->internal.qos != NULL); + TAILQ_INIT(&bdev->internal.qos->queued); + /* + * Enable both IOPS and bandwidth rate limits. + * In this case, bandwidth rate limit will take effect first. + */ + /* 2000 I/O per second, or 2 per millisecond */ + bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT].limit = 2000; + /* 4K byte per millisecond with 4K block size */ + bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT].limit = 4096000; + + g_get_io_channel = true; + + /* Create channels */ + set_thread(0); + io_ch[0] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]); + CU_ASSERT(bdev_ch[0]->flags == BDEV_CH_QOS_ENABLED); + + set_thread(1); + io_ch[1] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]); + CU_ASSERT(bdev_ch[1]->flags == BDEV_CH_QOS_ENABLED); + + /* Send two I/O. One of these gets queued by QoS. The other is sitting at the disk. */ + status1 = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &status1); + CU_ASSERT(rc == 0); + set_thread(0); + status0 = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &status0); + CU_ASSERT(rc == 0); + + poll_threads(); + CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_PENDING); + CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_PENDING); + + /* Reset the bdev. */ + reset_status = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_reset(g_desc, io_ch[0], io_during_io_done, &reset_status); + CU_ASSERT(rc == 0); + + /* Complete any I/O that arrived at the disk */ + poll_threads(); + set_thread(1); + stub_complete_io(g_bdev.io_target, 0); + set_thread(0); + stub_complete_io(g_bdev.io_target, 0); + poll_threads(); + + CU_ASSERT(reset_status == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_FAILED); + CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_FAILED); + + /* Tear down the channels */ + set_thread(1); + spdk_put_io_channel(io_ch[1]); + set_thread(0); + spdk_put_io_channel(io_ch[0]); + poll_threads(); + + teardown_test(); +} + +static void +enomem_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + enum spdk_bdev_io_status *status = cb_arg; + + *status = success ? SPDK_BDEV_IO_STATUS_SUCCESS : SPDK_BDEV_IO_STATUS_FAILED; + spdk_bdev_free_io(bdev_io); +} + +static void +enomem(void) +{ + struct spdk_io_channel *io_ch; + struct spdk_bdev_channel *bdev_ch; + struct spdk_bdev_shared_resource *shared_resource; + 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], status_reset; + uint32_t nomem_cnt, i; + struct spdk_bdev_io *first_io; + int rc; + + setup_test(); + + set_thread(0); + io_ch = spdk_bdev_get_io_channel(g_desc); + bdev_ch = spdk_io_channel_get_ctx(io_ch); + shared_resource = bdev_ch->shared_resource; + ut_ch = spdk_io_channel_get_ctx(bdev_ch->channel); + ut_ch->avail_cnt = AVAIL; + + /* First submit a number of IOs equal to what the channel can support. */ + 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(&shared_resource->nomem_io)); + + /* + * Next, submit one additional I/O. This one should fail with ENOMEM and then go onto + * the enomem_io 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(&shared_resource->nomem_io)); + first_io = TAILQ_FIRST(&shared_resource->nomem_io); + + /* + * Now submit a bunch more I/O. These should all fail with ENOMEM and get queued behind + * the first_io above. + */ + for (i = AVAIL + 1; i < IO_ARRAY_SIZE; 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); + } + + /* Assert that first_io is still at the head of the list. */ + CU_ASSERT(TAILQ_FIRST(&shared_resource->nomem_io) == first_io); + CU_ASSERT(bdev_io_tailq_cnt(&shared_resource->nomem_io) == (IO_ARRAY_SIZE - AVAIL)); + nomem_cnt = bdev_io_tailq_cnt(&shared_resource->nomem_io); + CU_ASSERT(shared_resource->nomem_threshold == (AVAIL - NOMEM_THRESHOLD_COUNT)); + + /* + * Complete 1 I/O only. The key check here is bdev_io_tailq_cnt - this should not have + * changed since completing just 1 I/O should not trigger retrying the queued nomem_io + * list. + */ + stub_complete_io(g_bdev.io_target, 1); + CU_ASSERT(bdev_io_tailq_cnt(&shared_resource->nomem_io) == nomem_cnt); + + /* + * Complete enough I/O to hit the nomem_theshold. This should trigger retrying nomem_io, + * and we should see I/O get resubmitted to the test bdev module. + */ + stub_complete_io(g_bdev.io_target, NOMEM_THRESHOLD_COUNT - 1); + CU_ASSERT(bdev_io_tailq_cnt(&shared_resource->nomem_io) < nomem_cnt); + nomem_cnt = bdev_io_tailq_cnt(&shared_resource->nomem_io); + + /* Complete 1 I/O only. This should not trigger retrying the queued nomem_io. */ + stub_complete_io(g_bdev.io_target, 1); + CU_ASSERT(bdev_io_tailq_cnt(&shared_resource->nomem_io) == nomem_cnt); + + /* + * Send a reset and confirm that all I/O are completed, including the ones that + * were queued on the nomem_io list. + */ + status_reset = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_reset(g_desc, io_ch, enomem_done, &status_reset); + poll_threads(); + CU_ASSERT(rc == 0); + /* This will complete the reset. */ + stub_complete_io(g_bdev.io_target, 0); + + CU_ASSERT(bdev_io_tailq_cnt(&shared_resource->nomem_io) == 0); + CU_ASSERT(shared_resource->io_outstanding == 0); + + spdk_put_io_channel(io_ch); + poll_threads(); + teardown_test(); +} + +static void +enomem_multi_bdev(void) +{ + struct spdk_io_channel *io_ch; + struct spdk_bdev_channel *bdev_ch; + struct spdk_bdev_shared_resource *shared_resource; + 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; + struct ut_bdev *second_bdev; + struct spdk_bdev_desc *second_desc = NULL; + struct spdk_bdev_channel *second_bdev_ch; + struct spdk_io_channel *second_ch; + int rc; + + setup_test(); + + /* Register second bdev with the same io_target */ + second_bdev = calloc(1, sizeof(*second_bdev)); + SPDK_CU_ASSERT_FATAL(second_bdev != NULL); + register_bdev(second_bdev, "ut_bdev2", g_bdev.io_target); + spdk_bdev_open(&second_bdev->bdev, true, NULL, NULL, &second_desc); + SPDK_CU_ASSERT_FATAL(second_desc != NULL); + + set_thread(0); + io_ch = spdk_bdev_get_io_channel(g_desc); + bdev_ch = spdk_io_channel_get_ctx(io_ch); + shared_resource = bdev_ch->shared_resource; + ut_ch = spdk_io_channel_get_ctx(bdev_ch->channel); + ut_ch->avail_cnt = AVAIL; + + second_ch = spdk_bdev_get_io_channel(second_desc); + second_bdev_ch = spdk_io_channel_get_ctx(second_ch); + SPDK_CU_ASSERT_FATAL(shared_resource == second_bdev_ch->shared_resource); + + /* 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(&shared_resource->nomem_io)); + + /* + * Now submit I/O through the second bdev. This should fail with ENOMEM + * and then go onto the nomem_io list. + */ + 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(&shared_resource->nomem_io)); + + /* Complete first bdev's I/O. This should retry sending second bdev's nomem_io */ + stub_complete_io(g_bdev.io_target, AVAIL); + + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&shared_resource->nomem_io)); + CU_ASSERT(shared_resource->io_outstanding == 1); + + /* Now complete our retried I/O */ + stub_complete_io(g_bdev.io_target, 1); + SPDK_CU_ASSERT_FATAL(shared_resource->io_outstanding == 0); + + spdk_put_io_channel(io_ch); + spdk_put_io_channel(second_ch); + spdk_bdev_close(second_desc); + unregister_bdev(second_bdev); + poll_threads(); + free(second_bdev); + 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 = NULL; + 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), NULL); + 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); + SPDK_CU_ASSERT_FATAL(second_desc != NULL); + + 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 shared_resource */ + 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->shared_resource != second_bdev_ch->shared_resource); + + /* 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->shared_resource->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->shared_resource->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->shared_resource->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->shared_resource->nomem_io)); + /* Complete the ENOMEM I/O */ + stub_complete_io(g_bdev.io_target, 1); + CU_ASSERT(bdev_ch->shared_resource->io_outstanding == 0); + + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&bdev_ch->shared_resource->nomem_io)); + CU_ASSERT(bdev_ch->shared_resource->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) +{ + int *rc = cb_arg; + *rc = status; +} + +static void +qos_dynamic_enable(void) +{ + struct spdk_io_channel *io_ch[2]; + struct spdk_bdev_channel *bdev_ch[2]; + struct spdk_bdev *bdev; + enum spdk_bdev_io_status bdev_io_status[2]; + uint64_t limits[SPDK_BDEV_QOS_NUM_RATE_LIMIT_TYPES] = {}; + int status, second_status, rc, i; + + setup_test(); + reset_time(); + + for (i = 0; i < SPDK_BDEV_QOS_NUM_RATE_LIMIT_TYPES; i++) { + limits[i] = UINT64_MAX; + } + + bdev = &g_bdev.bdev; + + g_get_io_channel = true; + + /* Create channels */ + set_thread(0); + io_ch[0] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]); + CU_ASSERT(bdev_ch[0]->flags == 0); + + set_thread(1); + io_ch[1] = spdk_bdev_get_io_channel(g_desc); + bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]); + CU_ASSERT(bdev_ch[1]->flags == 0); + + set_thread(0); + + /* + * Enable QoS: IOPS and byte per second rate limits. + * More than 10 I/Os allowed per timeslice. + */ + status = -1; + limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 10000; + limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT] = 100; + spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status); + poll_threads(); + CU_ASSERT(status == 0); + CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) != 0); + CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) != 0); + + /* + * Submit and complete 10 I/O to fill the QoS allotment for this timeslice. + * Additional I/O will then be queued. + */ + set_thread(0); + for (i = 0; i < 10; i++) { + bdev_io_status[0] = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &bdev_io_status[0]); + CU_ASSERT(rc == 0); + CU_ASSERT(bdev_io_status[0] == SPDK_BDEV_IO_STATUS_PENDING); + poll_thread(0); + stub_complete_io(g_bdev.io_target, 0); + CU_ASSERT(bdev_io_status[0] == SPDK_BDEV_IO_STATUS_SUCCESS); + } + + /* + * Send two more I/O. These I/O will be queued since the current timeslice allotment has been + * filled already. We want to test that when QoS is disabled that these two I/O: + * 1) are not aborted + * 2) are sent back to their original thread for resubmission + */ + bdev_io_status[0] = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &bdev_io_status[0]); + CU_ASSERT(rc == 0); + CU_ASSERT(bdev_io_status[0] == SPDK_BDEV_IO_STATUS_PENDING); + set_thread(1); + bdev_io_status[1] = SPDK_BDEV_IO_STATUS_PENDING; + rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &bdev_io_status[1]); + CU_ASSERT(rc == 0); + CU_ASSERT(bdev_io_status[1] == SPDK_BDEV_IO_STATUS_PENDING); + poll_threads(); + + /* Disable QoS: IOPS rate limit */ + status = -1; + limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 0; + spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status); + poll_threads(); + CU_ASSERT(status == 0); + CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) != 0); + CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) != 0); + + /* Disable QoS: Byte per second rate limit */ + status = -1; + limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT] = 0; + spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status); + poll_threads(); + CU_ASSERT(status == 0); + CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) == 0); + CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) == 0); + + /* + * All I/O should have been resubmitted back on their original thread. Complete + * all I/O on thread 0, and ensure that only the thread 0 I/O was completed. + */ + set_thread(0); + stub_complete_io(g_bdev.io_target, 0); + poll_threads(); + CU_ASSERT(bdev_io_status[0] == SPDK_BDEV_IO_STATUS_SUCCESS); + CU_ASSERT(bdev_io_status[1] == SPDK_BDEV_IO_STATUS_PENDING); + + /* Now complete all I/O on thread 1 and ensure the thread 1 I/O was completed. */ + set_thread(1); + stub_complete_io(g_bdev.io_target, 0); + poll_threads(); + CU_ASSERT(bdev_io_status[1] == SPDK_BDEV_IO_STATUS_SUCCESS); + + /* Disable QoS again */ + status = -1; + limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 0; + spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status); + poll_threads(); + CU_ASSERT(status == 0); /* This should succeed */ + CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) == 0); + CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) == 0); + + /* Enable QoS on thread 0 */ + status = -1; + limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 10000; + spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status); + poll_threads(); + CU_ASSERT(status == 0); + CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) != 0); + CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) != 0); + + /* Disable QoS on thread 1 */ + set_thread(1); + status = -1; + limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 0; + spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status); + /* Don't poll yet. This should leave the channels with QoS enabled */ + CU_ASSERT(status == -1); + CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) != 0); + CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) != 0); + + /* Enable QoS. This should immediately fail because the previous disable QoS hasn't completed. */ + second_status = 0; + limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT] = 10; + spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &second_status); + poll_threads(); + CU_ASSERT(status == 0); /* The disable should succeed */ + CU_ASSERT(second_status < 0); /* The enable should fail */ + CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) == 0); + CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) == 0); + + /* Enable QoS on thread 1. This should succeed now that the disable has completed. */ + status = -1; + limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 10000; + spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status); + poll_threads(); + CU_ASSERT(status == 0); + CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) != 0); + CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) != 0); + + /* Tear down the channels */ + set_thread(0); + spdk_put_io_channel(io_ch[0]); + set_thread(1); + spdk_put_io_channel(io_ch[1]); + poll_threads(); + + set_thread(0); + teardown_test(); +} + +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("bdev", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "basic", basic) == NULL || + CU_add_test(suite, "unregister_and_close", unregister_and_close) == NULL || + CU_add_test(suite, "basic_qos", basic_qos) == NULL || + CU_add_test(suite, "put_channel_during_reset", put_channel_during_reset) == NULL || + CU_add_test(suite, "aborted_reset", aborted_reset) == NULL || + CU_add_test(suite, "io_during_reset", io_during_reset) == NULL || + CU_add_test(suite, "io_during_qos_queue", io_during_qos_queue) == NULL || + 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(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/bdev/part.c/.gitignore b/src/spdk/test/unit/lib/bdev/part.c/.gitignore new file mode 100644 index 00000000..c8302779 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/part.c/.gitignore @@ -0,0 +1 @@ +part_ut diff --git a/src/spdk/test/unit/lib/bdev/part.c/Makefile b/src/spdk/test/unit/lib/bdev/part.c/Makefile new file mode 100644 index 00000000..9073c5cd --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/part.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = part_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/bdev/part.c/part_ut.c b/src/spdk/test/unit/lib/bdev/part.c/part_ut.c new file mode 100644 index 00000000..fd251f4c --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/part.c/part_ut.c @@ -0,0 +1,179 @@ +/*- + * 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_cunit.h" + +#include "common/lib/test_env.c" +#include "unit/lib/json_mock.c" + +#include "spdk/config.h" +/* HACK: disable VTune integration so the unit test doesn't need VTune headers and libs to build */ +#undef SPDK_CONFIG_VTUNE + +#include "bdev/bdev.c" +#include "bdev/part.c" + +DEFINE_STUB(spdk_conf_find_section, struct spdk_conf_section *, (struct spdk_conf *cp, + const char *name), NULL); +DEFINE_STUB(spdk_conf_section_get_nmval, char *, + (struct spdk_conf_section *sp, const char *key, int idx1, int idx2), NULL); +DEFINE_STUB(spdk_conf_section_get_intval, int, (struct spdk_conf_section *sp, const char *key), -1); + +struct spdk_trace_histories *g_trace_histories; +DEFINE_STUB_V(spdk_trace_add_register_fn, (struct spdk_trace_register_fn *reg_fn)); +DEFINE_STUB_V(spdk_trace_register_owner, (uint8_t type, char id_prefix)); +DEFINE_STUB_V(spdk_trace_register_object, (uint8_t type, char id_prefix)); +DEFINE_STUB_V(spdk_trace_register_description, (const char *name, const char *short_name, + uint16_t tpoint_id, uint8_t owner_type, + uint8_t object_type, uint8_t new_object, + uint8_t arg1_is_ptr, const char *arg1_name)); +DEFINE_STUB_V(_spdk_trace_record, (uint64_t tsc, uint16_t tpoint_id, uint16_t poller_id, + uint32_t size, uint64_t object_id, uint64_t arg1)); + +static void +_part_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx) +{ + fn(ctx); +} + +static void +_part_cleanup(struct spdk_bdev_part *part) +{ + free(part->internal.bdev.name); + free(part->internal.bdev.product_name); +} + +void +spdk_scsi_nvme_translate(const struct spdk_bdev_io *bdev_io, + int *sc, int *sk, int *asc, int *ascq) +{ +} + +struct spdk_bdev_module bdev_ut_if = { + .name = "bdev_ut", +}; + +static void vbdev_ut_examine(struct spdk_bdev *bdev); + +struct spdk_bdev_module vbdev_ut_if = { + .name = "vbdev_ut", + .examine_config = vbdev_ut_examine, +}; + +SPDK_BDEV_MODULE_REGISTER(&bdev_ut_if) +SPDK_BDEV_MODULE_REGISTER(&vbdev_ut_if) + +static void +vbdev_ut_examine(struct spdk_bdev *bdev) +{ + spdk_bdev_module_examine_done(&vbdev_ut_if); +} + +static int +__destruct(void *ctx) +{ + return 0; +} + +static struct spdk_bdev_fn_table base_fn_table = { + .destruct = __destruct, +}; +static struct spdk_bdev_fn_table part_fn_table = { + .destruct = __destruct, +}; + +static void +part_test(void) +{ + struct spdk_bdev_part_base *base; + struct spdk_bdev_part part1 = {}; + struct spdk_bdev_part part2 = {}; + struct spdk_bdev bdev_base = {}; + SPDK_BDEV_PART_TAILQ tailq = TAILQ_HEAD_INITIALIZER(tailq); + int rc; + + bdev_base.name = "base"; + bdev_base.fn_table = &base_fn_table; + bdev_base.module = &bdev_ut_if; + rc = spdk_bdev_register(&bdev_base); + CU_ASSERT(rc == 0); + base = spdk_bdev_part_base_construct(&bdev_base, NULL, &vbdev_ut_if, + &part_fn_table, &tailq, NULL, + NULL, 0, NULL, NULL); + + SPDK_CU_ASSERT_FATAL(base != NULL); + + rc = spdk_bdev_part_construct(&part1, base, "test1", 0, 100, "test"); + SPDK_CU_ASSERT_FATAL(rc == 0); + rc = spdk_bdev_part_construct(&part2, base, "test2", 100, 100, "test"); + SPDK_CU_ASSERT_FATAL(rc == 0); + + spdk_bdev_part_base_hotremove(&bdev_base, &tailq); + + spdk_bdev_part_base_free(base); + _part_cleanup(&part1); + _part_cleanup(&part2); + spdk_bdev_unregister(&bdev_base, NULL, NULL); +} + +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("bdev_part", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "part", part_test) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + spdk_allocate_thread(_part_send_msg, NULL, NULL, NULL, "thread0"); + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + spdk_free_thread(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/bdev/pmem/.gitignore b/src/spdk/test/unit/lib/bdev/pmem/.gitignore new file mode 100644 index 00000000..b2e0df1e --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/pmem/.gitignore @@ -0,0 +1 @@ +bdev_pmem_ut diff --git a/src/spdk/test/unit/lib/bdev/pmem/Makefile b/src/spdk/test/unit/lib/bdev/pmem/Makefile new file mode 100644 index 00000000..9c0e7dc1 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/pmem/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = bdev_pmem_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/bdev/pmem/bdev_pmem_ut.c b/src/spdk/test/unit/lib/bdev/pmem/bdev_pmem_ut.c new file mode 100644 index 00000000..742ec638 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/pmem/bdev_pmem_ut.c @@ -0,0 +1,783 @@ +/*- + * 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_cunit.h" + +#include "common/lib/test_env.c" +#include "unit/lib/json_mock.c" + +#include "bdev/pmem/bdev_pmem.c" + +DEFINE_STUB(spdk_conf_find_section, struct spdk_conf_section *, + (struct spdk_conf *cp, const char *name), NULL); +DEFINE_STUB(spdk_conf_section_get_nval, char *, + (struct spdk_conf_section *sp, const char *key, int idx), NULL); +DEFINE_STUB(spdk_conf_section_get_nmval, char *, + (struct spdk_conf_section *sp, const char *key, int idx1, int idx2), NULL); + +static struct spdk_bdev_module *g_bdev_pmem_module; +static int g_bdev_module_cnt; + +struct pmemblk { + const char *name; + bool is_open; + bool is_consistent; + size_t bsize; + long long nblock; + + uint8_t *buffer; +}; + +static const char *g_bdev_name = "pmem0"; + +/* PMEMblkpool is a typedef of struct pmemblk */ +static PMEMblkpool g_pool_ok = { + .name = "/pools/ok_pool", + .is_open = false, + .is_consistent = true, + .bsize = 4096, + .nblock = 150 +}; + +static PMEMblkpool g_pool_nblock_0 = { + .name = "/pools/nblock_0", + .is_open = false, + .is_consistent = true, + .bsize = 4096, + .nblock = 0 +}; + +static PMEMblkpool g_pool_bsize_0 = { + .name = "/pools/nblock_0", + .is_open = false, + .is_consistent = true, + .bsize = 0, + .nblock = 100 +}; + +static PMEMblkpool g_pool_inconsistent = { + .name = "/pools/inconsistent", + .is_open = false, + .is_consistent = false, + .bsize = 512, + .nblock = 1 +}; + +static int g_opened_pools; +static struct spdk_bdev *g_bdev; +static const char *g_check_version_msg; +static bool g_pmemblk_open_allow_open = true; + +static void +_pmem_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx) +{ + fn(ctx); +} + +static PMEMblkpool * +find_pmemblk_pool(const char *path) +{ + if (path == NULL) { + errno = EINVAL; + return NULL; + } else if (strcmp(g_pool_ok.name, path) == 0) { + return &g_pool_ok; + } else if (strcmp(g_pool_nblock_0.name, path) == 0) { + return &g_pool_nblock_0; + } else if (strcmp(g_pool_bsize_0.name, path) == 0) { + return &g_pool_bsize_0; + } else if (strcmp(g_pool_inconsistent.name, path) == 0) { + return &g_pool_inconsistent; + } + + errno = ENOENT; + return NULL; +} + +PMEMblkpool * +pmemblk_open(const char *path, size_t bsize) +{ + PMEMblkpool *pool; + + if (!g_pmemblk_open_allow_open) { + errno = EIO; + return NULL; + } + + pool = find_pmemblk_pool(path); + if (!pool) { + errno = ENOENT; + return NULL; + } + + CU_ASSERT_TRUE_FATAL(pool->is_consistent); + CU_ASSERT_FALSE(pool->is_open); + if (pool->is_open == false) { + pool->is_open = true; + g_opened_pools++; + } else { + errno = EBUSY; + pool = NULL; + } + + return pool; +} +void +spdk_bdev_io_get_buf(struct spdk_bdev_io *bdev_io, spdk_bdev_io_get_buf_cb cb, uint64_t len) +{ + cb(NULL, bdev_io); +} + +static void +check_open_pool_fatal(PMEMblkpool *pool) +{ + SPDK_CU_ASSERT_FATAL(pool != NULL); + SPDK_CU_ASSERT_FATAL(find_pmemblk_pool(pool->name) == pool); + SPDK_CU_ASSERT_FATAL(pool->is_open == true); +} + +void +pmemblk_close(PMEMblkpool *pool) +{ + check_open_pool_fatal(pool); + pool->is_open = false; + CU_ASSERT(g_opened_pools > 0); + g_opened_pools--; +} + +size_t +pmemblk_bsize(PMEMblkpool *pool) +{ + check_open_pool_fatal(pool); + return pool->bsize; +} + +size_t +pmemblk_nblock(PMEMblkpool *pool) +{ + check_open_pool_fatal(pool); + return pool->nblock; +} + +int +pmemblk_read(PMEMblkpool *pool, void *buf, long long blockno) +{ + check_open_pool_fatal(pool); + if (blockno >= pool->nblock) { + errno = EINVAL; + return -1; + } + + memcpy(buf, &pool->buffer[blockno * pool->bsize], pool->bsize); + return 0; +} + +int +pmemblk_write(PMEMblkpool *pool, const void *buf, long long blockno) +{ + check_open_pool_fatal(pool); + if (blockno >= pool->nblock) { + errno = EINVAL; + return -1; + } + + memcpy(&pool->buffer[blockno * pool->bsize], buf, pool->bsize); + return 0; +} + +int +pmemblk_set_zero(PMEMblkpool *pool, long long blockno) +{ + check_open_pool_fatal(pool); + if (blockno >= pool->nblock) { + + errno = EINVAL; + return -1; + } + + memset(&pool->buffer[blockno * pool->bsize], 0, pool->bsize); + return 0; +} + +const char * +pmemblk_errormsg(void) +{ + return strerror(errno); +} + +const char * +pmemblk_check_version(unsigned major_required, unsigned minor_required) +{ + return g_check_version_msg; +} + +int +pmemblk_check(const char *path, size_t bsize) +{ + PMEMblkpool *pool = find_pmemblk_pool(path); + + if (!pool) { + errno = ENOENT; + return -1; + } + + if (!pool->is_consistent) { + /* errno ? */ + return 0; + } + + if (bsize != 0 && pool->bsize != bsize) { + /* errno ? */ + return 0; + } + + return 1; +} + +void +spdk_bdev_io_complete(struct spdk_bdev_io *bdev_io, enum spdk_bdev_io_status status) +{ + bdev_io->internal.status = status; +} + +int +spdk_bdev_register(struct spdk_bdev *bdev) +{ + CU_ASSERT_PTR_NULL(g_bdev); + g_bdev = bdev; + + return 0; +} + +void +spdk_bdev_unregister(struct spdk_bdev *bdev, spdk_bdev_unregister_cb cb_fn, void *cb_arg) +{ +} + +void +spdk_bdev_module_finish_done(void) +{ +} + +int +spdk_bdev_notify_blockcnt_change(struct spdk_bdev *bdev, uint64_t size) +{ + bdev->blockcnt = size; + return 0; +} + +static void +ut_bdev_pmem_destruct(struct spdk_bdev *bdev) +{ + SPDK_CU_ASSERT_FATAL(g_bdev != NULL); + CU_ASSERT_EQUAL(bdev_pmem_destruct(bdev->ctxt), 0); + g_bdev = NULL; +} + +void +spdk_bdev_module_list_add(struct spdk_bdev_module *bdev_module) +{ + g_bdev_pmem_module = bdev_module; + g_bdev_module_cnt++; +} + +static int +bdev_submit_request(struct spdk_bdev *bdev, int16_t io_type, uint64_t offset_blocks, + uint64_t num_blocks, struct iovec *iovs, size_t iov_cnt) +{ + struct spdk_bdev_io bio = { 0 }; + + switch (io_type) { + case SPDK_BDEV_IO_TYPE_READ: + bio.u.bdev.iovs = iovs; + bio.u.bdev.iovcnt = iov_cnt; + bio.u.bdev.offset_blocks = offset_blocks; + bio.u.bdev.num_blocks = num_blocks; + break; + case SPDK_BDEV_IO_TYPE_WRITE: + bio.u.bdev.iovs = iovs; + bio.u.bdev.iovcnt = iov_cnt; + bio.u.bdev.offset_blocks = offset_blocks; + bio.u.bdev.num_blocks = num_blocks; + break; + case SPDK_BDEV_IO_TYPE_FLUSH: + bio.u.bdev.offset_blocks = offset_blocks; + bio.u.bdev.num_blocks = num_blocks; + break; + case SPDK_BDEV_IO_TYPE_RESET: + break; + case SPDK_BDEV_IO_TYPE_UNMAP: + bio.u.bdev.offset_blocks = offset_blocks; + bio.u.bdev.num_blocks = num_blocks; + break; + case SPDK_BDEV_IO_TYPE_WRITE_ZEROES: + bio.u.bdev.offset_blocks = offset_blocks; + bio.u.bdev.num_blocks = num_blocks; + break; + default: + CU_FAIL_FATAL("BUG:Unexpected IO type"); + break; + } + + /* + * Set status to value that shouldn't be returned + */ + bio.type = io_type; + bio.internal.status = SPDK_BDEV_IO_STATUS_PENDING; + bio.bdev = bdev; + bdev_pmem_submit_request(NULL, &bio); + return bio.internal.status; +} + + +static int +ut_pmem_blk_clean(void) +{ + free(g_pool_ok.buffer); + g_pool_ok.buffer = NULL; + + /* Unload module to free IO channel */ + g_bdev_pmem_module->module_fini(); + + spdk_free_thread(); + + return 0; +} + +static int +ut_pmem_blk_init(void) +{ + errno = 0; + + spdk_allocate_thread(_pmem_send_msg, NULL, NULL, NULL, NULL); + + g_pool_ok.buffer = calloc(g_pool_ok.nblock, g_pool_ok.bsize); + if (g_pool_ok.buffer == NULL) { + ut_pmem_blk_clean(); + return -1; + } + + return 0; +} + +static void +ut_pmem_init(void) +{ + SPDK_CU_ASSERT_FATAL(g_bdev_pmem_module != NULL); + CU_ASSERT_EQUAL(g_bdev_module_cnt, 1); + + /* Make pmemblk_check_version fail with provided error message */ + g_check_version_msg = "TEST FAIL MESSAGE"; + CU_ASSERT_NOT_EQUAL(g_bdev_pmem_module->module_init(), 0); + + /* This init must success */ + g_check_version_msg = NULL; + CU_ASSERT_EQUAL(g_bdev_pmem_module->module_init(), 0); +} + +static void +ut_pmem_open_close(void) +{ + struct spdk_bdev *bdev = NULL; + int pools_cnt; + int rc; + + pools_cnt = g_opened_pools; + + /* Try opening with NULL name */ + rc = spdk_create_pmem_disk(NULL, NULL, &bdev); + CU_ASSERT_PTR_NULL(bdev); + CU_ASSERT_EQUAL(pools_cnt, g_opened_pools); + CU_ASSERT_NOT_EQUAL(rc, 0); + + /* Open non-existent pool */ + rc = spdk_create_pmem_disk("non existent pool", NULL, &bdev); + CU_ASSERT_PTR_NULL(bdev); + CU_ASSERT_EQUAL(pools_cnt, g_opened_pools); + CU_ASSERT_NOT_EQUAL(rc, 0); + + /* Open inconsistent pool */ + rc = spdk_create_pmem_disk(g_pool_inconsistent.name, NULL, &bdev); + CU_ASSERT_PTR_NULL(bdev); + CU_ASSERT_EQUAL(pools_cnt, g_opened_pools); + CU_ASSERT_NOT_EQUAL(rc, 0); + + /* Open consistent pool fail the open from unknown reason. */ + g_pmemblk_open_allow_open = false; + rc = spdk_create_pmem_disk(g_pool_inconsistent.name, NULL, &bdev); + g_pmemblk_open_allow_open = true; + CU_ASSERT_PTR_NULL(bdev); + CU_ASSERT_EQUAL(pools_cnt, g_opened_pools); + CU_ASSERT_NOT_EQUAL(rc, 0); + + /* Open pool with nblocks = 0 */ + rc = spdk_create_pmem_disk(g_pool_nblock_0.name, NULL, &bdev); + CU_ASSERT_PTR_NULL(bdev); + CU_ASSERT_EQUAL(pools_cnt, g_opened_pools); + CU_ASSERT_NOT_EQUAL(rc, 0); + + /* Open pool with bsize = 0 */ + rc = spdk_create_pmem_disk(g_pool_bsize_0.name, NULL, &bdev); + CU_ASSERT_PTR_NULL(bdev); + CU_ASSERT_EQUAL(pools_cnt, g_opened_pools); + CU_ASSERT_NOT_EQUAL(rc, 0); + + /* Open pool with NULL name */ + rc = spdk_create_pmem_disk(g_pool_ok.name, NULL, &bdev); + CU_ASSERT_PTR_NULL(bdev); + CU_ASSERT_EQUAL(pools_cnt, g_opened_pools); + CU_ASSERT_NOT_EQUAL(rc, 0); + + /* Open good pool */ + rc = spdk_create_pmem_disk(g_pool_ok.name, g_bdev_name, &bdev); + SPDK_CU_ASSERT_FATAL(bdev != NULL); + CU_ASSERT_TRUE(g_pool_ok.is_open); + CU_ASSERT_EQUAL(pools_cnt + 1, g_opened_pools); + CU_ASSERT_EQUAL(rc, 0); + + /* Now remove this bdev */ + ut_bdev_pmem_destruct(bdev); + CU_ASSERT_FALSE(g_pool_ok.is_open); + CU_ASSERT_EQUAL(pools_cnt, g_opened_pools); +} + +static void +ut_pmem_write_read(void) +{ + uint8_t *write_buf, *read_buf; + struct spdk_bdev *bdev; + int rc; + size_t unaligned_aligned_size = 100; + size_t buf_size = g_pool_ok.bsize * g_pool_ok.nblock; + size_t i; + const uint64_t nblock_offset = 10; + uint64_t offset; + size_t io_size, nblock, total_io_size, bsize; + + bsize = 4096; + struct iovec iov[] = { + { 0, 2 * bsize }, + { 0, 3 * bsize }, + { 0, 4 * bsize }, + }; + + rc = spdk_create_pmem_disk(g_pool_ok.name, g_bdev_name, &bdev); + CU_ASSERT_EQUAL(rc, 0); + + SPDK_CU_ASSERT_FATAL(g_pool_ok.nblock > 40); + + write_buf = calloc(1, buf_size); + read_buf = calloc(1, buf_size); + + SPDK_CU_ASSERT_FATAL(bdev != NULL); + SPDK_CU_ASSERT_FATAL(write_buf != NULL); + SPDK_CU_ASSERT_FATAL(read_buf != NULL); + + total_io_size = 0; + offset = nblock_offset * g_pool_ok.bsize; + for (i = 0; i < 3; i++) { + iov[i].iov_base = &write_buf[offset + total_io_size]; + total_io_size += iov[i].iov_len; + } + + for (i = 0; i < total_io_size + unaligned_aligned_size; i++) { + write_buf[offset + i] = 0x42 + i; + } + + SPDK_CU_ASSERT_FATAL(total_io_size < buf_size); + + /* + * Write outside pool. + */ + rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_WRITE, g_pool_ok.nblock, 1, &iov[0], 2); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED); + + /* + * Write with insufficient IOV buffers length. + */ + rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_WRITE, 0, g_pool_ok.nblock, &iov[0], 2); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED); + + /* + * Try to write two IOV with first one iov_len % bsize != 0. + */ + io_size = iov[0].iov_len + iov[1].iov_len; + nblock = io_size / g_pool_ok.bsize; + iov[0].iov_len += unaligned_aligned_size; + rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_WRITE, 0, nblock, &iov[0], 2); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED); + iov[0].iov_len -= unaligned_aligned_size; + + /* + * Try to write one IOV. + */ + nblock = iov[0].iov_len / g_pool_ok.bsize; + rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_WRITE, nblock_offset, nblock, &iov[0], 1); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS); + + /* + * Try to write 2 IOV. + * Sum of IOV length is larger than IO size and last IOV is larger and iov_len % bsize != 0 + */ + offset = iov[0].iov_len / g_pool_ok.bsize; + io_size = iov[1].iov_len + iov[2].iov_len; + nblock = io_size / g_pool_ok.bsize; + iov[2].iov_len += unaligned_aligned_size; + rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_WRITE, nblock_offset + offset, nblock, + &iov[1], 2); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS); + iov[2].iov_len -= unaligned_aligned_size; + + /* + * Examine pool state: + * 1. Written area should have expected values. + * 2. Anything else should contain zeros. + */ + offset = nblock_offset * g_pool_ok.bsize + total_io_size; + rc = memcmp(&g_pool_ok.buffer[0], write_buf, offset); + CU_ASSERT_EQUAL(rc, 0); + + for (i = offset; i < buf_size; i++) { + if (g_pool_ok.buffer[i] != 0) { + CU_ASSERT_EQUAL(g_pool_ok.buffer[i], 0); + break; + } + } + + /* Setup IOV for reads */ + memset(read_buf, 0xAB, buf_size); + offset = nblock_offset * g_pool_ok.bsize; + for (i = 0; i < 3; i++) { + iov[i].iov_base = &read_buf[offset]; + offset += iov[i].iov_len; + } + + /* + * Write outside pool. + */ + rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_READ, g_pool_ok.nblock, 1, &iov[0], 2); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED); + + /* + * Read with insufficient IOV buffers length. + */ + rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_READ, 0, g_pool_ok.nblock, &iov[0], 2); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED); + + /* + * Try to read two IOV with first one iov_len % bsize != 0. + */ + io_size = iov[0].iov_len + iov[1].iov_len; + nblock = io_size / g_pool_ok.bsize; + iov[0].iov_len += unaligned_aligned_size; + rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_READ, 0, nblock, &iov[0], 2); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED); + iov[0].iov_len -= unaligned_aligned_size; + + /* + * Try to write one IOV. + */ + nblock = iov[0].iov_len / g_pool_ok.bsize; + rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_READ, nblock_offset, nblock, &iov[0], 1); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS); + + /* + * Try to read 2 IOV. + * Sum of IOV length is larger than IO size and last IOV is larger and iov_len % bsize != 0 + */ + offset = iov[0].iov_len / g_pool_ok.bsize; + io_size = iov[1].iov_len + iov[2].iov_len; + nblock = io_size / g_pool_ok.bsize; + iov[2].iov_len += unaligned_aligned_size; + rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_READ, nblock_offset + offset, nblock, + &iov[1], 2); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS); + iov[2].iov_len -= unaligned_aligned_size; + + + /* + * Examine what we read state: + * 1. Written area should have expected values. + * 2. Anything else should contain zeros. + */ + offset = nblock_offset * g_pool_ok.bsize; + for (i = 0; i < offset; i++) { + if (read_buf[i] != 0xAB) { + CU_ASSERT_EQUAL(read_buf[i], 0xAB); + break; + } + } + + rc = memcmp(&read_buf[offset], &write_buf[offset], total_io_size); + CU_ASSERT_EQUAL(rc, 0); + + offset += total_io_size; + for (i = offset; i < buf_size; i++) { + if (read_buf[i] != 0xAB) { + CU_ASSERT_EQUAL(read_buf[i], 0xAB); + break; + } + } + + memset(g_pool_ok.buffer, 0, g_pool_ok.bsize * g_pool_ok.nblock); + free(write_buf); + free(read_buf); + + /* Now remove this bdev */ + ut_bdev_pmem_destruct(bdev); + CU_ASSERT_FALSE(g_pool_ok.is_open); + CU_ASSERT_EQUAL(g_opened_pools, 0); +} + +static void +ut_pmem_reset(void) +{ + struct spdk_bdev *bdev; + int rc; + + rc = spdk_create_pmem_disk(g_pool_ok.name, g_bdev_name, &bdev); + CU_ASSERT_EQUAL(rc, 0); + SPDK_CU_ASSERT_FATAL(bdev != NULL); + + rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_RESET, 0, 0, NULL, 0); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS); + + ut_bdev_pmem_destruct(bdev); +} + +static void +ut_pmem_unmap_write_zero(int16_t io_type) +{ + struct spdk_bdev *bdev; + size_t buff_size = g_pool_ok.nblock * g_pool_ok.bsize; + size_t i; + uint8_t *buffer; + int rc; + + CU_ASSERT(io_type == SPDK_BDEV_IO_TYPE_UNMAP || io_type == SPDK_BDEV_IO_TYPE_WRITE_ZEROES); + rc = spdk_create_pmem_disk(g_pool_ok.name, g_bdev_name, &bdev); + CU_ASSERT_EQUAL(rc, 0); + SPDK_CU_ASSERT_FATAL(bdev != NULL); + SPDK_CU_ASSERT_FATAL(g_pool_ok.nblock > 40); + + buffer = calloc(1, buff_size); + SPDK_CU_ASSERT_FATAL(buffer != NULL); + + for (i = 10 * g_pool_ok.bsize; i < 30 * g_pool_ok.bsize; i++) { + buffer[i] = 0x30 + io_type + i; + } + memcpy(g_pool_ok.buffer, buffer, buff_size); + + /* + * Block outside of pool. + */ + rc = bdev_submit_request(bdev, io_type, g_pool_ok.nblock, 1, NULL, 0); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED); + + rc = memcmp(buffer, g_pool_ok.buffer, buff_size); + CU_ASSERT_EQUAL(rc, 0); + + /* + * Blocks 15 to 25 + */ + memset(&buffer[15 * g_pool_ok.bsize], 0, 10 * g_pool_ok.bsize); + rc = bdev_submit_request(bdev, io_type, 15, 10, NULL, 0); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS); + + rc = memcmp(buffer, g_pool_ok.buffer, buff_size); + CU_ASSERT_EQUAL(rc, 0); + + /* + * All blocks. + */ + memset(buffer, 0, buff_size); + rc = bdev_submit_request(bdev, io_type, 0, g_pool_ok.nblock, NULL, 0); + CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS); + + rc = memcmp(buffer, g_pool_ok.buffer, buff_size); + CU_ASSERT_EQUAL(rc, 0); + + /* Now remove this bdev */ + ut_bdev_pmem_destruct(bdev); + CU_ASSERT_FALSE(g_pool_ok.is_open); + CU_ASSERT_EQUAL(g_opened_pools, 0); + + free(buffer); +} + +static void +ut_pmem_write_zero(void) +{ + ut_pmem_unmap_write_zero(SPDK_BDEV_IO_TYPE_WRITE_ZEROES); +} + +static void +ut_pmem_unmap(void) +{ + ut_pmem_unmap_write_zero(SPDK_BDEV_IO_TYPE_UNMAP); +} + +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("bdev_pmem", ut_pmem_blk_init, ut_pmem_blk_clean); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "ut_pmem_init", ut_pmem_init) == NULL || + CU_add_test(suite, "ut_pmem_open_close", ut_pmem_open_close) == NULL || + CU_add_test(suite, "ut_pmem_write_read", ut_pmem_write_read) == NULL || + CU_add_test(suite, "ut_pmem_reset", ut_pmem_reset) == NULL || + CU_add_test(suite, "ut_pmem_write_zero", ut_pmem_write_zero) == NULL || + CU_add_test(suite, "ut_pmem_unmap", ut_pmem_unmap) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/bdev/scsi_nvme.c/.gitignore b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/.gitignore new file mode 100644 index 00000000..75800527 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/.gitignore @@ -0,0 +1 @@ +scsi_nvme_ut diff --git a/src/spdk/test/unit/lib/bdev/scsi_nvme.c/Makefile b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/Makefile new file mode 100644 index 00000000..0c908148 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/Makefile @@ -0,0 +1,39 @@ +# +# BSD LICENSE +# +# Copyright (c) 2016 FUJITSU LIMITED, 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 the copyright holder 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = scsi_nvme_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/bdev/scsi_nvme.c/scsi_nvme_ut.c b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/scsi_nvme_ut.c new file mode 100644 index 00000000..9b2eff35 --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/scsi_nvme_ut.c @@ -0,0 +1,142 @@ +/*- + * BSD LICENSE + * + * Copyright (c) 2016 FUJITSU LIMITED, 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 the copyright holder 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_cunit.h" + +#include "bdev/scsi_nvme.c" + +static int +null_init(void) +{ + return 0; +} + +static int +null_clean(void) +{ + return 0; +} + +static void +scsi_nvme_translate_test(void) +{ + struct spdk_bdev_io bdev_io; + int sc, sk, asc, ascq; + + /* SPDK_NVME_SCT_GENERIC */ + bdev_io.internal.error.nvme.sct = SPDK_NVME_SCT_GENERIC; + bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_ABORTED_POWER_LOSS; + spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq); + CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_TASK_ABORTED); + CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ABORTED_COMMAND); + CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_WARNING); + CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_POWER_LOSS_EXPECTED); + + bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_INVALID_NUM_SGL_DESCIRPTORS; + spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq); + CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ILLEGAL_REQUEST); + CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE); + CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + + /* SPDK_NVME_SCT_COMMAND_SPECIFIC */ + bdev_io.internal.error.nvme.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC; + bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_INVALID_FORMAT; + spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq); + CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ILLEGAL_REQUEST); + CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_FORMAT_COMMAND_FAILED); + CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_FORMAT_COMMAND_FAILED); + + bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_OVERLAPPING_RANGE; + spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq); + CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ILLEGAL_REQUEST); + CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE); + CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + + /* SPDK_NVME_SCT_MEDIA_ERROR */ + bdev_io.internal.error.nvme.sct = SPDK_NVME_SCT_MEDIA_ERROR; + bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_GUARD_CHECK_ERROR; + spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq); + CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_MEDIUM_ERROR); + CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_LOGICAL_BLOCK_GUARD_CHECK_FAILED); + CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_LOGICAL_BLOCK_GUARD_CHECK_FAILED); + + bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_DEALLOCATED_OR_UNWRITTEN_BLOCK; + spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq); + CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ILLEGAL_REQUEST); + CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE); + CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + + /* SPDK_NVME_SCT_VENDOR_SPECIFIC */ + bdev_io.internal.error.nvme.sct = SPDK_NVME_SCT_VENDOR_SPECIFIC; + bdev_io.internal.error.nvme.sc = 0xff; + spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq); + CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ILLEGAL_REQUEST); + CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE); + CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); +} + +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("scsi_nvme_suite", null_init, null_clean); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "scsi_nvme - translate nvme error to scsi error", + scsi_nvme_translate_test) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/.gitignore b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/.gitignore new file mode 100644 index 00000000..5f2f6fdf --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/.gitignore @@ -0,0 +1 @@ +vbdev_lvol_ut diff --git a/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/Makefile b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/Makefile new file mode 100644 index 00000000..c2e6b99e --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../../) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = vbdev_lvol_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c new file mode 100644 index 00000000..2500378b --- /dev/null +++ b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c @@ -0,0 +1,1410 @@ +/*- + * 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_cunit.h" +#include "spdk/string.h" + +#include "bdev/lvol/vbdev_lvol.c" + +#define SPDK_BS_PAGE_SIZE 0x1000 + +int g_lvolerrno; +int g_lvserrno; +int g_cluster_size; +int g_registered_bdevs; +int g_num_lvols = 0; +struct spdk_lvol_store *g_lvs = NULL; +struct spdk_lvol *g_lvol = NULL; +struct lvol_store_bdev *g_lvs_bdev = NULL; +struct spdk_bdev *g_base_bdev = NULL; +struct spdk_bdev_io *g_io = NULL; +struct spdk_io_channel *g_ch = NULL; +struct lvol_task *g_task = NULL; + +static struct spdk_bdev g_bdev = {}; +static struct spdk_lvol_store *g_lvol_store = NULL; +bool lvol_store_initialize_fail = false; +bool lvol_store_initialize_cb_fail = false; +bool lvol_already_opened = false; +bool g_examine_done = false; +bool g_bdev_alias_already_exists = false; +bool g_lvs_with_name_already_exists = false; +bool g_lvol_deletable = true; + +int +spdk_bdev_alias_add(struct spdk_bdev *bdev, const char *alias) +{ + struct spdk_bdev_alias *tmp; + + CU_ASSERT(alias != NULL); + CU_ASSERT(bdev != NULL); + if (g_bdev_alias_already_exists) { + return -EEXIST; + } + + tmp = calloc(1, sizeof(*tmp)); + SPDK_CU_ASSERT_FATAL(tmp != NULL); + + tmp->alias = strdup(alias); + SPDK_CU_ASSERT_FATAL(tmp->alias != NULL); + + TAILQ_INSERT_TAIL(&bdev->aliases, tmp, tailq); + + return 0; +} + +int +spdk_bdev_alias_del(struct spdk_bdev *bdev, const char *alias) +{ + struct spdk_bdev_alias *tmp; + + CU_ASSERT(alias != NULL); + CU_ASSERT(bdev != NULL); + + TAILQ_FOREACH(tmp, &bdev->aliases, tailq) { + if (strncmp(alias, tmp->alias, SPDK_LVOL_NAME_MAX) == 0) { + TAILQ_REMOVE(&bdev->aliases, tmp, tailq); + free(tmp->alias); + free(tmp); + return 0; + } + } + + return -ENOENT; +} + +void +spdk_bdev_alias_del_all(struct spdk_bdev *bdev) +{ + struct spdk_bdev_alias *p, *tmp; + + TAILQ_FOREACH_SAFE(p, &bdev->aliases, tailq, tmp) { + TAILQ_REMOVE(&bdev->aliases, p, tailq); + free(p->alias); + free(p); + } +} + +void +spdk_bdev_destruct_done(struct spdk_bdev *bdev, int bdeverrno) +{ +} + +void +spdk_lvs_rename(struct spdk_lvol_store *lvs, const char *new_name, + spdk_lvs_op_complete cb_fn, void *cb_arg) +{ + if (g_lvs_with_name_already_exists) { + g_lvolerrno = -EEXIST; + } else { + snprintf(lvs->name, sizeof(lvs->name), "%s", new_name); + g_lvolerrno = 0; + } + + cb_fn(cb_arg, g_lvolerrno); +} + +void +spdk_lvol_rename(struct spdk_lvol *lvol, const char *new_name, + spdk_lvol_op_complete cb_fn, void *cb_arg) +{ + struct spdk_lvol *tmp; + + if (strncmp(lvol->name, new_name, SPDK_LVOL_NAME_MAX) == 0) { + cb_fn(cb_arg, 0); + return; + } + + TAILQ_FOREACH(tmp, &lvol->lvol_store->lvols, link) { + if (strncmp(tmp->name, new_name, SPDK_LVOL_NAME_MAX) == 0) { + SPDK_ERRLOG("Lvol %s already exists in lvol store %s\n", new_name, lvol->lvol_store->name); + cb_fn(cb_arg, -EEXIST); + return; + } + } + + snprintf(lvol->name, sizeof(lvol->name), "%s", new_name); + + cb_fn(cb_arg, g_lvolerrno); +} + +void +spdk_lvol_open(struct spdk_lvol *lvol, spdk_lvol_op_with_handle_complete cb_fn, void *cb_arg) +{ + cb_fn(cb_arg, lvol, g_lvolerrno); +} + +uint64_t +spdk_blob_get_num_clusters(struct spdk_blob *b) +{ + return 0; +} + +int +spdk_blob_get_clones(struct spdk_blob_store *bs, spdk_blob_id blobid, spdk_blob_id *ids, + size_t *count) +{ + *count = 0; + return 0; +} + +spdk_blob_id +spdk_blob_get_parent_snapshot(struct spdk_blob_store *bs, spdk_blob_id blobid) +{ + return 0; +} + +bool g_blob_is_read_only = false; + +bool +spdk_blob_is_read_only(struct spdk_blob *blob) +{ + return g_blob_is_read_only; +} + +bool +spdk_blob_is_snapshot(struct spdk_blob *blob) +{ + return false; +} + +bool +spdk_blob_is_clone(struct spdk_blob *blob) +{ + return false; +} + +bool +spdk_blob_is_thin_provisioned(struct spdk_blob *blob) +{ + return false; +} + +static struct spdk_lvol *_lvol_create(struct spdk_lvol_store *lvs); + +void +spdk_lvs_load(struct spdk_bs_dev *dev, + spdk_lvs_op_with_handle_complete cb_fn, void *cb_arg) +{ + struct spdk_lvol_store *lvs = NULL; + int i; + int lvserrno = g_lvserrno; + + if (lvserrno != 0) { + /* On error blobstore destroys bs_dev itself, + * by puttin back io channels. + * This operation is asynchronous, and completed + * after calling the callback for lvol. */ + cb_fn(cb_arg, g_lvol_store, lvserrno); + dev->destroy(dev); + return; + } + + lvs = calloc(1, sizeof(*lvs)); + SPDK_CU_ASSERT_FATAL(lvs != NULL); + TAILQ_INIT(&lvs->lvols); + TAILQ_INIT(&lvs->pending_lvols); + spdk_uuid_generate(&lvs->uuid); + lvs->bs_dev = dev; + for (i = 0; i < g_num_lvols; i++) { + _lvol_create(lvs); + } + + cb_fn(cb_arg, lvs, lvserrno); +} + +int +spdk_bs_bdev_claim(struct spdk_bs_dev *bs_dev, struct spdk_bdev_module *module) +{ + if (lvol_already_opened == true) { + return -1; + } + + lvol_already_opened = true; + + return 0; +} + +void +spdk_bdev_unregister(struct spdk_bdev *vbdev, spdk_bdev_unregister_cb cb_fn, void *cb_arg) +{ + int rc; + + SPDK_CU_ASSERT_FATAL(vbdev != NULL); + rc = vbdev->fn_table->destruct(vbdev->ctxt); + + SPDK_CU_ASSERT_FATAL(cb_fn != NULL); + cb_fn(cb_arg, rc); +} + +void +spdk_bdev_module_finish_done(void) +{ + return; +} + +uint64_t +spdk_bs_get_page_size(struct spdk_blob_store *bs) +{ + return SPDK_BS_PAGE_SIZE; +} + +uint64_t +spdk_bs_get_io_unit_size(struct spdk_blob_store *bs) +{ + return SPDK_BS_PAGE_SIZE; +} + +static void +bdev_blob_destroy(struct spdk_bs_dev *bs_dev) +{ + CU_ASSERT(bs_dev != NULL); + free(bs_dev); + lvol_already_opened = false; +} + +struct spdk_bs_dev * +spdk_bdev_create_bs_dev(struct spdk_bdev *bdev, spdk_bdev_remove_cb_t remove_cb, void *remove_ctx) +{ + struct spdk_bs_dev *bs_dev; + + if (lvol_already_opened == true || bdev == NULL) { + return NULL; + } + + bs_dev = calloc(1, sizeof(*bs_dev)); + SPDK_CU_ASSERT_FATAL(bs_dev != NULL); + bs_dev->destroy = bdev_blob_destroy; + + return bs_dev; +} + +void +spdk_lvs_opts_init(struct spdk_lvs_opts *opts) +{ +} + +int +spdk_lvs_init(struct spdk_bs_dev *bs_dev, struct spdk_lvs_opts *o, + spdk_lvs_op_with_handle_complete cb_fn, void *cb_arg) +{ + struct spdk_lvol_store *lvs; + int error = 0; + + if (lvol_store_initialize_fail) { + return -1; + } + + if (lvol_store_initialize_cb_fail) { + bs_dev->destroy(bs_dev); + lvs = NULL; + error = -1; + } else { + lvs = calloc(1, sizeof(*lvs)); + SPDK_CU_ASSERT_FATAL(lvs != NULL); + TAILQ_INIT(&lvs->lvols); + TAILQ_INIT(&lvs->pending_lvols); + spdk_uuid_generate(&lvs->uuid); + snprintf(lvs->name, sizeof(lvs->name), "%s", o->name); + lvs->bs_dev = bs_dev; + error = 0; + } + cb_fn(cb_arg, lvs, error); + + return 0; +} + +int +spdk_lvs_unload(struct spdk_lvol_store *lvs, spdk_lvs_op_complete cb_fn, void *cb_arg) +{ + struct spdk_lvol *lvol, *tmp; + + TAILQ_FOREACH_SAFE(lvol, &lvs->lvols, link, tmp) { + TAILQ_REMOVE(&lvs->lvols, lvol, link); + free(lvol->unique_id); + free(lvol); + } + g_lvol_store = NULL; + + lvs->bs_dev->destroy(lvs->bs_dev); + free(lvs); + + if (cb_fn != NULL) { + cb_fn(cb_arg, 0); + } + + return 0; +} + +int +spdk_lvs_destroy(struct spdk_lvol_store *lvs, spdk_lvs_op_complete cb_fn, + void *cb_arg) +{ + struct spdk_lvol *lvol, *tmp; + char *alias; + + TAILQ_FOREACH_SAFE(lvol, &lvs->lvols, link, tmp) { + TAILQ_REMOVE(&lvs->lvols, lvol, link); + + alias = spdk_sprintf_alloc("%s/%s", lvs->name, lvol->name); + if (alias == NULL) { + SPDK_ERRLOG("Cannot alloc memory for alias\n"); + return -1; + } + spdk_bdev_alias_del(lvol->bdev, alias); + + free(alias); + free(lvol->unique_id); + free(lvol); + } + g_lvol_store = NULL; + + lvs->bs_dev->destroy(lvs->bs_dev); + free(lvs); + + if (cb_fn != NULL) { + cb_fn(cb_arg, 0); + } + + return 0; +} + +void +spdk_lvol_resize(struct spdk_lvol *lvol, size_t sz, spdk_lvol_op_complete cb_fn, void *cb_arg) +{ + cb_fn(cb_arg, 0); +} + +int +spdk_bdev_notify_blockcnt_change(struct spdk_bdev *bdev, uint64_t size) +{ + bdev->blockcnt = size; + return 0; +} + +uint64_t +spdk_bs_get_cluster_size(struct spdk_blob_store *bs) +{ + return g_cluster_size; +} + +struct spdk_bdev * +spdk_bdev_get_by_name(const char *bdev_name) +{ + if (!strcmp(g_base_bdev->name, bdev_name)) { + return g_base_bdev; + } + + return NULL; +} + +void +spdk_lvol_close(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn, void *cb_arg) +{ + lvol->ref_count--; + + SPDK_CU_ASSERT_FATAL(cb_fn != NULL); + cb_fn(cb_arg, 0); +} + +bool +spdk_lvol_deletable(struct spdk_lvol *lvol) +{ + return g_lvol_deletable; +} + +void +spdk_lvol_destroy(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn, void *cb_arg) +{ + if (lvol->ref_count != 0) { + cb_fn(cb_arg, -ENODEV); + } + + TAILQ_REMOVE(&lvol->lvol_store->lvols, lvol, link); + + SPDK_CU_ASSERT_FATAL(cb_fn != NULL); + cb_fn(cb_arg, 0); + + g_lvol = NULL; + free(lvol->unique_id); + free(lvol); +} + +void +spdk_bdev_io_complete(struct spdk_bdev_io *bdev_io, enum spdk_bdev_io_status status) +{ +} + +struct spdk_io_channel *spdk_lvol_get_io_channel(struct spdk_lvol *lvol) +{ + CU_ASSERT(lvol == g_lvol); + return g_ch; +} + +void +spdk_bdev_io_get_buf(struct spdk_bdev_io *bdev_io, spdk_bdev_io_get_buf_cb cb, uint64_t len) +{ + CU_ASSERT(cb == lvol_read); +} + +void +spdk_blob_io_read(struct spdk_blob *blob, struct spdk_io_channel *channel, + void *payload, uint64_t offset, uint64_t length, + spdk_blob_op_complete cb_fn, void *cb_arg) +{ +} + +void +spdk_blob_io_write(struct spdk_blob *blob, struct spdk_io_channel *channel, + void *payload, uint64_t offset, uint64_t length, + spdk_blob_op_complete cb_fn, void *cb_arg) +{ +} + +void +spdk_blob_io_unmap(struct spdk_blob *blob, struct spdk_io_channel *channel, + uint64_t offset, uint64_t length, spdk_blob_op_complete cb_fn, void *cb_arg) +{ + CU_ASSERT(blob == NULL); + CU_ASSERT(channel == g_ch); + CU_ASSERT(offset == g_io->u.bdev.offset_blocks); + CU_ASSERT(length == g_io->u.bdev.num_blocks); +} + +void +spdk_blob_io_write_zeroes(struct spdk_blob *blob, struct spdk_io_channel *channel, + uint64_t offset, uint64_t length, spdk_blob_op_complete cb_fn, void *cb_arg) +{ + CU_ASSERT(blob == NULL); + CU_ASSERT(channel == g_ch); + CU_ASSERT(offset == g_io->u.bdev.offset_blocks); + CU_ASSERT(length == g_io->u.bdev.num_blocks); +} + +void +spdk_blob_io_writev(struct spdk_blob *blob, struct spdk_io_channel *channel, + struct iovec *iov, int iovcnt, uint64_t offset, uint64_t length, + spdk_blob_op_complete cb_fn, void *cb_arg) +{ + CU_ASSERT(blob == NULL); + CU_ASSERT(channel == g_ch); + CU_ASSERT(offset == g_io->u.bdev.offset_blocks); + CU_ASSERT(length == g_io->u.bdev.num_blocks); +} + +void +spdk_blob_io_readv(struct spdk_blob *blob, struct spdk_io_channel *channel, + struct iovec *iov, int iovcnt, uint64_t offset, uint64_t length, + spdk_blob_op_complete cb_fn, void *cb_arg) +{ + CU_ASSERT(blob == NULL); + CU_ASSERT(channel == g_ch); + CU_ASSERT(offset == g_io->u.bdev.offset_blocks); + CU_ASSERT(length == g_io->u.bdev.num_blocks); +} + +void +spdk_bdev_module_list_add(struct spdk_bdev_module *bdev_module) +{ +} + +int +spdk_json_write_name(struct spdk_json_write_ctx *w, const char *name) +{ + return 0; +} + +int +spdk_json_write_array_begin(struct spdk_json_write_ctx *w) +{ + return 0; +} + +int +spdk_json_write_array_end(struct spdk_json_write_ctx *w) +{ + return 0; +} + +int +spdk_json_write_string(struct spdk_json_write_ctx *w, const char *val) +{ + return 0; +} + +int +spdk_json_write_bool(struct spdk_json_write_ctx *w, bool val) +{ + return 0; +} + +int +spdk_json_write_object_begin(struct spdk_json_write_ctx *w) +{ + return 0; +} + +int +spdk_json_write_object_end(struct spdk_json_write_ctx *w) +{ + return 0; +} + +const char * +spdk_bdev_get_name(const struct spdk_bdev *bdev) +{ + return "test"; +} + +int +spdk_vbdev_register(struct spdk_bdev *vbdev, struct spdk_bdev **base_bdevs, int base_bdev_count) +{ + TAILQ_INIT(&vbdev->aliases); + + g_registered_bdevs++; + return 0; +} + +void +spdk_bdev_module_examine_done(struct spdk_bdev_module *module) +{ + SPDK_CU_ASSERT_FATAL(g_examine_done != true); + g_examine_done = true; +} + +static struct spdk_lvol * +_lvol_create(struct spdk_lvol_store *lvs) +{ + struct spdk_lvol *lvol = calloc(1, sizeof(*lvol)); + + SPDK_CU_ASSERT_FATAL(lvol != NULL); + + lvol->lvol_store = lvs; + lvol->ref_count++; + lvol->unique_id = spdk_sprintf_alloc("%s", "UNIT_TEST_UUID"); + SPDK_CU_ASSERT_FATAL(lvol->unique_id != NULL); + + TAILQ_INSERT_TAIL(&lvol->lvol_store->lvols, lvol, link); + + return lvol; +} + +int +spdk_lvol_create(struct spdk_lvol_store *lvs, const char *name, size_t sz, + bool thin_provision, spdk_lvol_op_with_handle_complete cb_fn, void *cb_arg) +{ + struct spdk_lvol *lvol; + + lvol = _lvol_create(lvs); + snprintf(lvol->name, sizeof(lvol->name), "%s", name); + cb_fn(cb_arg, lvol, 0); + + return 0; +} + +void +spdk_lvol_create_snapshot(struct spdk_lvol *lvol, const char *snapshot_name, + spdk_lvol_op_with_handle_complete cb_fn, void *cb_arg) +{ + struct spdk_lvol *snap; + + snap = _lvol_create(lvol->lvol_store); + snprintf(snap->name, sizeof(snap->name), "%s", snapshot_name); + cb_fn(cb_arg, snap, 0); +} + +void +spdk_lvol_create_clone(struct spdk_lvol *lvol, const char *clone_name, + spdk_lvol_op_with_handle_complete cb_fn, void *cb_arg) +{ + struct spdk_lvol *clone; + + clone = _lvol_create(lvol->lvol_store); + snprintf(clone->name, sizeof(clone->name), "%s", clone_name); + cb_fn(cb_arg, clone, 0); +} + +static void +lvol_store_op_complete(void *cb_arg, int lvserrno) +{ + g_lvserrno = lvserrno; + return; +} + +static void +lvol_store_op_with_handle_complete(void *cb_arg, struct spdk_lvol_store *lvs, int lvserrno) +{ + g_lvserrno = lvserrno; + g_lvol_store = lvs; + return; +} + +static void +vbdev_lvol_create_complete(void *cb_arg, struct spdk_lvol *lvol, int lvolerrno) +{ + g_lvolerrno = lvolerrno; + g_lvol = lvol; +} + +static void +vbdev_lvol_resize_complete(void *cb_arg, int lvolerrno) +{ + g_lvolerrno = lvolerrno; +} + +static void +vbdev_lvol_rename_complete(void *cb_arg, int lvolerrno) +{ + g_lvolerrno = lvolerrno; +} + +static void +ut_lvs_destroy(void) +{ + int rc = 0; + int sz = 10; + struct spdk_lvol_store *lvs; + + /* Lvol store is successfully created */ + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + + lvs = g_lvol_store; + g_lvol_store = NULL; + + spdk_uuid_generate(&lvs->uuid); + + /* Successfully create lvol, which should be unloaded with lvs later */ + g_lvolerrno = -1; + rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvolerrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + /* Unload lvol store */ + vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); +} + +static void +ut_lvol_init(void) +{ + struct spdk_lvol_store *lvs; + int sz = 10; + int rc; + + /* Lvol store is successfully created */ + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + lvs = g_lvol_store; + + /* Successful lvol create */ + g_lvolerrno = -1; + rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL); + SPDK_CU_ASSERT_FATAL(rc == 0); + CU_ASSERT(g_lvol != NULL); + CU_ASSERT(g_lvolerrno == 0); + + /* Successful lvol destroy */ + vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvol == NULL); + + /* Destroy lvol store */ + vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); +} + +static void +ut_lvol_snapshot(void) +{ + struct spdk_lvol_store *lvs; + int sz = 10; + int rc; + struct spdk_lvol *lvol = NULL; + + /* Lvol store is successfully created */ + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + lvs = g_lvol_store; + + /* Successful lvol create */ + g_lvolerrno = -1; + rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT(g_lvolerrno == 0); + + lvol = g_lvol; + + /* Successful snap create */ + vbdev_lvol_create_snapshot(lvol, "snap", vbdev_lvol_create_complete, NULL); + SPDK_CU_ASSERT_FATAL(rc == 0); + CU_ASSERT(g_lvol != NULL); + CU_ASSERT(g_lvolerrno == 0); + + /* Successful lvol destroy */ + vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvol == NULL); + + /* Successful snap destroy */ + g_lvol = lvol; + vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvol == NULL); + + /* Destroy lvol store */ + vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); +} + +static void +ut_lvol_clone(void) +{ + struct spdk_lvol_store *lvs; + int sz = 10; + int rc; + struct spdk_lvol *lvol = NULL; + struct spdk_lvol *snap = NULL; + struct spdk_lvol *clone = NULL; + + /* Lvol store is successfully created */ + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + lvs = g_lvol_store; + + /* Successful lvol create */ + g_lvolerrno = -1; + rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT(g_lvolerrno == 0); + + lvol = g_lvol; + + /* Successful snap create */ + vbdev_lvol_create_snapshot(lvol, "snap", vbdev_lvol_create_complete, NULL); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT(g_lvolerrno == 0); + + snap = g_lvol; + + /* Successful clone create */ + vbdev_lvol_create_clone(snap, "clone", vbdev_lvol_create_complete, NULL); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT(g_lvolerrno == 0); + + clone = g_lvol; + + /* Successful lvol destroy */ + g_lvol = lvol; + vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvol == NULL); + + /* Successful clone destroy */ + g_lvol = clone; + vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvol == NULL); + + /* Successful lvol destroy */ + g_lvol = snap; + vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvol == NULL); + + /* Destroy lvol store */ + vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); +} + +static void +ut_lvol_hotremove(void) +{ + int rc = 0; + + lvol_store_initialize_fail = false; + lvol_store_initialize_cb_fail = false; + lvol_already_opened = false; + + /* Lvol store is successfully created */ + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + + /* Hot remove callback with NULL - stability check */ + vbdev_lvs_hotremove_cb(NULL); + + /* Hot remove lvs on bdev removal */ + vbdev_lvs_hotremove_cb(&g_bdev); + + CU_ASSERT(g_lvol_store == NULL); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_lvol_pairs)); + +} + +static void +ut_lvs_examine_check(bool success) +{ + struct lvol_store_bdev *lvs_bdev; + + /* Examine was finished regardless of result */ + CU_ASSERT(g_examine_done == true); + g_examine_done = false; + + if (success) { + SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_spdk_lvol_pairs)); + lvs_bdev = TAILQ_FIRST(&g_spdk_lvol_pairs); + SPDK_CU_ASSERT_FATAL(lvs_bdev != NULL); + g_lvol_store = lvs_bdev->lvs; + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + } else { + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_spdk_lvol_pairs)); + g_lvol_store = NULL; + } +} + +static void +ut_lvol_examine(void) +{ + /* Examine unsuccessfully - bdev already opened */ + g_lvserrno = -1; + lvol_already_opened = true; + vbdev_lvs_examine(&g_bdev); + ut_lvs_examine_check(false); + + /* Examine unsuccessfully - fail on lvol store */ + g_lvserrno = -1; + lvol_already_opened = false; + vbdev_lvs_examine(&g_bdev); + ut_lvs_examine_check(false); + + /* Examine successfully + * - one lvol fails to load + * - lvs is loaded with no lvols present */ + g_lvserrno = 0; + g_lvolerrno = -1; + g_num_lvols = 1; + lvol_already_opened = false; + g_registered_bdevs = 0; + vbdev_lvs_examine(&g_bdev); + ut_lvs_examine_check(true); + CU_ASSERT(g_registered_bdevs == 0); + CU_ASSERT(TAILQ_EMPTY(&g_lvol_store->lvols)); + vbdev_lvs_destruct(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); + + /* Examine successfully */ + g_lvserrno = 0; + g_lvolerrno = 0; + g_registered_bdevs = 0; + lvol_already_opened = false; + vbdev_lvs_examine(&g_bdev); + ut_lvs_examine_check(true); + CU_ASSERT(g_registered_bdevs != 0); + SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_lvol_store->lvols)); + vbdev_lvs_destruct(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); +} + +static void +ut_lvol_rename(void) +{ + struct spdk_lvol_store *lvs; + struct spdk_lvol *lvol; + struct spdk_lvol *lvol2; + int sz = 10; + int rc; + + /* Lvol store is successfully created */ + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + lvs = g_lvol_store; + + /* Successful lvols create */ + g_lvolerrno = -1; + rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL); + SPDK_CU_ASSERT_FATAL(rc == 0); + CU_ASSERT(g_lvol != NULL); + CU_ASSERT(g_lvolerrno == 0); + lvol = g_lvol; + + g_lvolerrno = -1; + rc = vbdev_lvol_create(lvs, "lvol2", sz, false, vbdev_lvol_create_complete, NULL); + SPDK_CU_ASSERT_FATAL(rc == 0); + CU_ASSERT(g_lvol != NULL); + CU_ASSERT(g_lvolerrno == 0); + lvol2 = g_lvol; + + /* Successful rename lvol */ + vbdev_lvol_rename(lvol, "new_lvol_name", vbdev_lvol_rename_complete, NULL); + SPDK_CU_ASSERT_FATAL(g_lvolerrno == 0); + CU_ASSERT_STRING_EQUAL(lvol->name, "new_lvol_name"); + + /* Renaming lvol with name already existing */ + g_bdev_alias_already_exists = true; + vbdev_lvol_rename(lvol2, "new_lvol_name", vbdev_lvol_rename_complete, NULL); + g_bdev_alias_already_exists = false; + SPDK_CU_ASSERT_FATAL(g_lvolerrno != 0); + CU_ASSERT_STRING_NOT_EQUAL(lvol2->name, "new_lvol_name"); + + /* Renaming lvol with it's own name */ + vbdev_lvol_rename(lvol, "new_lvol_name", vbdev_lvol_rename_complete, NULL); + SPDK_CU_ASSERT_FATAL(g_lvolerrno == 0); + CU_ASSERT_STRING_EQUAL(lvol->name, "new_lvol_name"); + + /* Successful lvols destroy */ + vbdev_lvol_destroy(lvol, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvol == NULL); + + vbdev_lvol_destroy(lvol2, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvol == NULL); + + /* Destroy lvol store */ + vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); +} + +static void +ut_lvol_destroy(void) +{ + struct spdk_lvol_store *lvs; + struct spdk_lvol *lvol; + struct spdk_lvol *lvol2; + int sz = 10; + int rc; + + /* Lvol store is successfully created */ + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + lvs = g_lvol_store; + + /* Successful lvols create */ + g_lvolerrno = -1; + rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL); + SPDK_CU_ASSERT_FATAL(rc == 0); + CU_ASSERT(g_lvol != NULL); + CU_ASSERT(g_lvolerrno == 0); + lvol = g_lvol; + + g_lvolerrno = -1; + rc = vbdev_lvol_create(lvs, "lvol2", sz, false, vbdev_lvol_create_complete, NULL); + SPDK_CU_ASSERT_FATAL(rc == 0); + CU_ASSERT(g_lvol != NULL); + CU_ASSERT(g_lvolerrno == 0); + lvol2 = g_lvol; + + /* Unsuccessful lvols destroy */ + g_lvol_deletable = false; + vbdev_lvol_destroy(lvol, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvol != NULL); + CU_ASSERT(g_lvserrno == -EPERM); + + g_lvol_deletable = true; + /* Successful lvols destroy */ + vbdev_lvol_destroy(lvol, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvol == NULL); + CU_ASSERT(g_lvolerrno == 0); + + /* Hot remove lvol bdev */ + vbdev_lvol_unregister(lvol2); + + /* Unload lvol store */ + vbdev_lvs_unload(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); +} + +static void +ut_lvol_resize(void) +{ + struct spdk_lvol_store *lvs; + struct spdk_lvol *lvol; + int sz = 10; + int rc = 0; + + /* Lvol store is successfully created */ + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + lvs = g_lvol_store; + + /* Successful lvol create */ + g_lvolerrno = -1; + rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvolerrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + lvol = g_lvol; + + /* Successful lvol resize */ + g_lvolerrno = -1; + vbdev_lvol_resize(lvol, 20, vbdev_lvol_resize_complete, NULL); + CU_ASSERT(g_lvolerrno == 0); + CU_ASSERT(lvol->bdev->blockcnt == 20 * g_cluster_size / lvol->bdev->blocklen); + + /* Resize with NULL lvol */ + vbdev_lvol_resize(NULL, 20, vbdev_lvol_resize_complete, NULL); + CU_ASSERT(g_lvolerrno != 0); + + /* Successful lvol destroy */ + vbdev_lvol_destroy(lvol, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvol == NULL); + + /* Destroy lvol store */ + vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); +} + +static void +ut_lvs_unload(void) +{ + int rc = 0; + int sz = 10; + struct spdk_lvol_store *lvs; + + /* Lvol store is successfully created */ + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + + lvs = g_lvol_store; + g_lvol_store = NULL; + + spdk_uuid_generate(&lvs->uuid); + + /* Successfully create lvol, which should be destroyed with lvs later */ + g_lvolerrno = -1; + rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvolerrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + /* Unload lvol store */ + vbdev_lvs_unload(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); + CU_ASSERT(g_lvol != NULL); +} + +static void +ut_lvs_init(void) +{ + int rc = 0; + struct spdk_lvol_store *lvs; + + /* spdk_lvs_init() fails */ + lvol_store_initialize_fail = true; + + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc != 0); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); + + lvol_store_initialize_fail = false; + + /* spdk_lvs_init_cb() fails */ + lvol_store_initialize_cb_fail = true; + + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno != 0); + CU_ASSERT(g_lvol_store == NULL); + + lvol_store_initialize_cb_fail = false; + + /* Lvol store is successfully created */ + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + + lvs = g_lvol_store; + g_lvol_store = NULL; + + /* Bdev with lvol store already claimed */ + rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc != 0); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); + + /* Destruct lvol store */ + vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); +} + +static void +ut_vbdev_lvol_get_io_channel(void) +{ + struct spdk_io_channel *ch; + + g_lvol = calloc(1, sizeof(struct spdk_lvol)); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + ch = vbdev_lvol_get_io_channel(g_lvol); + CU_ASSERT(ch == g_ch); + + free(g_lvol); +} + +static void +ut_vbdev_lvol_io_type_supported(void) +{ + struct spdk_lvol *lvol; + bool ret; + + lvol = calloc(1, sizeof(struct spdk_lvol)); + SPDK_CU_ASSERT_FATAL(lvol != NULL); + + g_blob_is_read_only = false; + + /* Supported types */ + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_READ); + CU_ASSERT(ret == true); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_WRITE); + CU_ASSERT(ret == true); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_RESET); + CU_ASSERT(ret == true); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_UNMAP); + CU_ASSERT(ret == true); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_WRITE_ZEROES); + CU_ASSERT(ret == true); + + /* Unsupported types */ + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_FLUSH); + CU_ASSERT(ret == false); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_NVME_ADMIN); + CU_ASSERT(ret == false); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_NVME_IO); + CU_ASSERT(ret == false); + + g_blob_is_read_only = true; + + /* Supported types */ + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_READ); + CU_ASSERT(ret == true); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_RESET); + CU_ASSERT(ret == true); + + /* Unsupported types */ + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_WRITE); + CU_ASSERT(ret == false); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_UNMAP); + CU_ASSERT(ret == false); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_WRITE_ZEROES); + CU_ASSERT(ret == false); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_FLUSH); + CU_ASSERT(ret == false); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_NVME_ADMIN); + CU_ASSERT(ret == false); + ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_NVME_IO); + CU_ASSERT(ret == false); + + free(lvol); +} + +static void +ut_lvol_read_write(void) +{ + g_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct lvol_task)); + SPDK_CU_ASSERT_FATAL(g_io != NULL); + g_base_bdev = calloc(1, sizeof(struct spdk_bdev)); + SPDK_CU_ASSERT_FATAL(g_base_bdev != NULL); + g_lvol = calloc(1, sizeof(struct spdk_lvol)); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + g_task = (struct lvol_task *)g_io->driver_ctx; + g_io->bdev = g_base_bdev; + g_io->bdev->ctxt = g_lvol; + g_io->u.bdev.offset_blocks = 20; + g_io->u.bdev.num_blocks = 20; + + lvol_read(g_ch, g_io); + CU_ASSERT(g_task->status == SPDK_BDEV_IO_STATUS_SUCCESS); + + lvol_write(g_lvol, g_ch, g_io); + CU_ASSERT(g_task->status == SPDK_BDEV_IO_STATUS_SUCCESS); + + free(g_io); + free(g_base_bdev); + free(g_lvol); +} + +static void +ut_vbdev_lvol_submit_request(void) +{ + struct spdk_lvol request_lvol = {}; + g_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct lvol_task)); + SPDK_CU_ASSERT_FATAL(g_io != NULL); + g_base_bdev = calloc(1, sizeof(struct spdk_bdev)); + SPDK_CU_ASSERT_FATAL(g_base_bdev != NULL); + g_task = (struct lvol_task *)g_io->driver_ctx; + g_io->bdev = g_base_bdev; + + g_io->type = SPDK_BDEV_IO_TYPE_READ; + g_base_bdev->ctxt = &request_lvol; + vbdev_lvol_submit_request(g_ch, g_io); + + free(g_io); + free(g_base_bdev); +} + +static void +ut_lvs_rename(void) +{ + int rc = 0; + int sz = 10; + struct spdk_lvol_store *lvs; + + /* Lvol store is successfully created */ + rc = vbdev_lvs_create(&g_bdev, "old_lvs_name", 0, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(g_lvol_store->bs_dev != NULL); + + lvs = g_lvol_store; + g_lvol_store = NULL; + + g_base_bdev = calloc(1, sizeof(*g_base_bdev)); + SPDK_CU_ASSERT_FATAL(g_base_bdev != NULL); + + /* Successfully create lvol, which should be destroyed with lvs later */ + g_lvolerrno = -1; + rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvolerrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + /* Trying to rename lvs with lvols created */ + vbdev_lvs_rename(lvs, "new_lvs_name", lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name"); + CU_ASSERT_STRING_EQUAL(TAILQ_FIRST(&g_lvol->bdev->aliases)->alias, "new_lvs_name/lvol"); + + /* Trying to rename lvs with name already used by another lvs */ + /* This is a bdev_lvol test, so g_lvs_with_name_already_exists simulates + * existing lvs with name 'another_new_lvs_name' and this name in fact is not compared */ + g_lvs_with_name_already_exists = true; + vbdev_lvs_rename(lvs, "another_new_lvs_name", lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == -EEXIST); + CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name"); + CU_ASSERT_STRING_EQUAL(TAILQ_FIRST(&g_lvol->bdev->aliases)->alias, "new_lvs_name/lvol"); + g_lvs_with_name_already_exists = false; + + /* Unload lvol store */ + g_lvol_store = lvs; + vbdev_lvs_destruct(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store == NULL); + + free(g_base_bdev->name); + free(g_base_bdev); +} + +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("lvol", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "ut_lvs_init", ut_lvs_init) == NULL || + CU_add_test(suite, "ut_lvol_init", ut_lvol_init) == NULL || + CU_add_test(suite, "ut_lvol_snapshot", ut_lvol_snapshot) == NULL || + CU_add_test(suite, "ut_lvol_clone", ut_lvol_clone) == NULL || + CU_add_test(suite, "ut_lvs_destroy", ut_lvs_destroy) == NULL || + CU_add_test(suite, "ut_lvs_unload", ut_lvs_unload) == NULL || + CU_add_test(suite, "ut_lvol_resize", ut_lvol_resize) == NULL || + CU_add_test(suite, "lvol_hotremove", ut_lvol_hotremove) == NULL || + CU_add_test(suite, "ut_vbdev_lvol_get_io_channel", ut_vbdev_lvol_get_io_channel) == NULL || + CU_add_test(suite, "ut_vbdev_lvol_io_type_supported", ut_vbdev_lvol_io_type_supported) == NULL || + CU_add_test(suite, "ut_lvol_read_write", ut_lvol_read_write) == NULL || + CU_add_test(suite, "ut_vbdev_lvol_submit_request", ut_vbdev_lvol_submit_request) == NULL || + CU_add_test(suite, "lvol_examine", ut_lvol_examine) == NULL || + CU_add_test(suite, "ut_lvol_rename", ut_lvol_rename) == NULL || + CU_add_test(suite, "ut_lvol_destroy", ut_lvol_destroy) == NULL || + CU_add_test(suite, "ut_lvs_rename", ut_lvs_rename) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/blob/Makefile b/src/spdk/test/unit/lib/blob/Makefile new file mode 100644 index 00000000..c57d0b1c --- /dev/null +++ b/src/spdk/test/unit/lib/blob/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = blob.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/blob/blob.c/.gitignore b/src/spdk/test/unit/lib/blob/blob.c/.gitignore new file mode 100644 index 00000000..553f5465 --- /dev/null +++ b/src/spdk/test/unit/lib/blob/blob.c/.gitignore @@ -0,0 +1 @@ +blob_ut diff --git a/src/spdk/test/unit/lib/blob/blob.c/Makefile b/src/spdk/test/unit/lib/blob/blob.c/Makefile new file mode 100644 index 00000000..6e279ff0 --- /dev/null +++ b/src/spdk/test/unit/lib/blob/blob.c/Makefile @@ -0,0 +1,41 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.mock.unittest.mk + +TEST_FILE = blob_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/blob/blob.c/blob_ut.c b/src/spdk/test/unit/lib/blob/blob.c/blob_ut.c new file mode 100644 index 00000000..88f438eb --- /dev/null +++ b/src/spdk/test/unit/lib/blob/blob.c/blob_ut.c @@ -0,0 +1,5914 @@ +/*- + * 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/blob.h" +#include "spdk/string.h" + +#include "common/lib/test_env.c" +#include "../bs_dev_common.c" +#include "blob/blobstore.c" +#include "blob/request.c" +#include "blob/zeroes.c" +#include "blob/blob_bs_dev.c" + +struct spdk_blob_store *g_bs; +spdk_blob_id g_blobid; +struct spdk_blob *g_blob; +int g_bserrno; +struct spdk_xattr_names *g_names; +int g_done; +char *g_xattr_names[] = {"first", "second", "third"}; +char *g_xattr_values[] = {"one", "two", "three"}; +uint64_t g_ctx = 1729; + +struct spdk_bs_super_block_ver1 { + uint8_t signature[8]; + uint32_t version; + uint32_t length; + uint32_t clean; /* If there was a clean shutdown, this is 1. */ + spdk_blob_id super_blob; + + uint32_t cluster_size; /* In bytes */ + + uint32_t used_page_mask_start; /* Offset from beginning of disk, in pages */ + uint32_t used_page_mask_len; /* Count, in pages */ + + uint32_t used_cluster_mask_start; /* Offset from beginning of disk, in pages */ + uint32_t used_cluster_mask_len; /* Count, in pages */ + + uint32_t md_start; /* Offset from beginning of disk, in pages */ + uint32_t md_len; /* Count, in pages */ + + uint8_t reserved[4036]; + uint32_t crc; +} __attribute__((packed)); +SPDK_STATIC_ASSERT(sizeof(struct spdk_bs_super_block_ver1) == 0x1000, "Invalid super block size"); + + +static void +_get_xattr_value(void *arg, const char *name, + const void **value, size_t *value_len) +{ + uint64_t i; + + SPDK_CU_ASSERT_FATAL(value_len != NULL); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(arg == &g_ctx) + + for (i = 0; i < sizeof(g_xattr_names); i++) { + if (!strcmp(name, g_xattr_names[i])) { + *value_len = strlen(g_xattr_values[i]); + *value = g_xattr_values[i]; + break; + } + } +} + +static void +_get_xattr_value_null(void *arg, const char *name, + const void **value, size_t *value_len) +{ + SPDK_CU_ASSERT_FATAL(value_len != NULL); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(arg == NULL) + + *value_len = 0; + *value = NULL; +} + + + +static void +bs_op_complete(void *cb_arg, int bserrno) +{ + g_bserrno = bserrno; +} + +static void +bs_op_with_handle_complete(void *cb_arg, struct spdk_blob_store *bs, + int bserrno) +{ + g_bs = bs; + g_bserrno = bserrno; +} + +static void +blob_op_complete(void *cb_arg, int bserrno) +{ + g_bserrno = bserrno; +} + +static void +blob_op_with_id_complete(void *cb_arg, spdk_blob_id blobid, int bserrno) +{ + g_blobid = blobid; + g_bserrno = bserrno; +} + +static void +blob_op_with_handle_complete(void *cb_arg, struct spdk_blob *blb, int bserrno) +{ + g_blob = blb; + g_bserrno = bserrno; +} + +static void +blob_init(void) +{ + struct spdk_bs_dev *dev; + + dev = init_dev(); + + /* should fail for an unsupported blocklen */ + dev->blocklen = 500; + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + dev = init_dev(); + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_super(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + spdk_blob_id blobid; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + /* Get the super blob without having set one */ + spdk_bs_get_super(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == -ENOENT); + CU_ASSERT(g_blobid == SPDK_BLOBID_INVALID); + + /* Create a blob */ + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + /* Set the blob as the super blob */ + spdk_bs_set_super(bs, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Get the super blob */ + spdk_bs_get_super(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(blobid == g_blobid); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_open(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + spdk_blob_id blobid, blobid2; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + blobid2 = spdk_blob_get_id(blob); + CU_ASSERT(blobid == blobid2); + + /* Try to open file again. It should return success. */ + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(blob == g_blob); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* + * Close the file a second time, releasing the second reference. This + * should succeed. + */ + blob = g_blob; + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* + * Try to open file again. It should succeed. This tests the case + * where the file is opened, closed, then re-opened again. + */ + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_create(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_blob_opts opts; + spdk_blob_id blobid; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + /* Create blob with 10 clusters */ + + spdk_blob_opts_init(&opts); + opts.num_clusters = 10; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10) + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Create blob with 0 clusters */ + + spdk_blob_opts_init(&opts); + opts.num_clusters = 0; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 0) + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Create blob with default options (opts == NULL) */ + + spdk_bs_create_blob_ext(bs, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 0) + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Try to create blob with size larger than blobstore */ + + spdk_blob_opts_init(&opts); + opts.num_clusters = bs->total_clusters + 1; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == -ENOSPC); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + +} + +static void +blob_create_internal(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_blob_opts opts; + struct spdk_blob_xattr_opts internal_xattrs; + const void *value; + size_t value_len; + spdk_blob_id blobid; + int rc; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + /* Create blob with custom xattrs */ + + spdk_blob_opts_init(&opts); + _spdk_blob_xattrs_init(&internal_xattrs); + internal_xattrs.count = 3; + internal_xattrs.names = g_xattr_names; + internal_xattrs.get_value = _get_xattr_value; + internal_xattrs.ctx = &g_ctx; + + _spdk_bs_create_blob(bs, &opts, &internal_xattrs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + rc = _spdk_blob_get_xattr_value(blob, g_xattr_names[0], &value, &value_len, true); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[0])); + CU_ASSERT_NSTRING_EQUAL_FATAL(value, g_xattr_values[0], value_len); + + rc = _spdk_blob_get_xattr_value(blob, g_xattr_names[1], &value, &value_len, true); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[1])); + CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[1], value_len); + + rc = _spdk_blob_get_xattr_value(blob, g_xattr_names[2], &value, &value_len, true); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[2])); + CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[2], value_len); + + rc = spdk_blob_get_xattr_value(blob, g_xattr_names[0], &value, &value_len); + CU_ASSERT(rc != 0); + + rc = spdk_blob_get_xattr_value(blob, g_xattr_names[1], &value, &value_len); + CU_ASSERT(rc != 0); + + rc = spdk_blob_get_xattr_value(blob, g_xattr_names[2], &value, &value_len); + CU_ASSERT(rc != 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Create blob with NULL internal options */ + + _spdk_bs_create_blob(bs, NULL, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + CU_ASSERT(TAILQ_FIRST(&g_blob->xattrs_internal) == NULL); + + blob = g_blob; + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + +} + +static void +blob_thin_provision(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_blob_opts opts; + struct spdk_bs_opts bs_opts; + spdk_blob_id blobid; + + dev = init_dev(); + spdk_bs_opts_init(&bs_opts); + snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "TESTTYPE"); + + /* Initialize a new blob store */ + spdk_bs_init(dev, &bs_opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + bs = g_bs; + + /* Create blob with thin provisioning enabled */ + + spdk_blob_opts_init(&opts); + opts.thin_provision = true; + opts.num_clusters = 10; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + CU_ASSERT(blob->invalid_flags & SPDK_BLOB_THIN_PROV); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Do not shut down cleanly. This makes sure that when we load again + * and try to recover a valid used_cluster map, that blobstore will + * ignore clusters with index 0 since these are unallocated clusters. + */ + + /* Load an existing blob store and check if invalid_flags is set */ + dev = init_dev(); + snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "TESTTYPE"); + spdk_bs_load(dev, &bs_opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + bs = g_bs; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + CU_ASSERT(blob->invalid_flags & SPDK_BLOB_THIN_PROV); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_snapshot(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_blob *snapshot, *snapshot2; + struct spdk_blob_bs_dev *blob_bs_dev; + struct spdk_blob_opts opts; + struct spdk_blob_xattr_opts xattrs; + spdk_blob_id blobid; + spdk_blob_id snapshotid; + const void *value; + size_t value_len; + int rc; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + /* Create blob with 10 clusters */ + spdk_blob_opts_init(&opts); + opts.num_clusters = 10; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10) + + /* Create snapshot from blob */ + spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + snapshotid = g_blobid; + + spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot = g_blob; + CU_ASSERT(snapshot->data_ro == true) + CU_ASSERT(snapshot->md_ro == true) + CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 10) + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10) + CU_ASSERT(blob->invalid_flags & SPDK_BLOB_THIN_PROV); + CU_ASSERT(spdk_mem_all_zero(blob->active.clusters, + blob->active.num_clusters * sizeof(blob->active.clusters[0]))); + + /* Try to create snapshot from clone with xattrs */ + xattrs.names = g_xattr_names; + xattrs.get_value = _get_xattr_value; + xattrs.count = 3; + xattrs.ctx = &g_ctx; + spdk_bs_create_snapshot(bs, blobid, &xattrs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot2 = g_blob; + CU_ASSERT(snapshot2->data_ro == true) + CU_ASSERT(snapshot2->md_ro == true) + CU_ASSERT(spdk_blob_get_num_clusters(snapshot2) == 10) + + /* Confirm that blob is backed by snapshot2 and snapshot2 is backed by snapshot */ + CU_ASSERT(snapshot->back_bs_dev == NULL); + SPDK_CU_ASSERT_FATAL(blob->back_bs_dev != NULL); + SPDK_CU_ASSERT_FATAL(snapshot2->back_bs_dev != NULL); + + blob_bs_dev = (struct spdk_blob_bs_dev *)blob->back_bs_dev; + CU_ASSERT(blob_bs_dev->blob == snapshot2); + + blob_bs_dev = (struct spdk_blob_bs_dev *)snapshot2->back_bs_dev; + CU_ASSERT(blob_bs_dev->blob == snapshot); + + rc = spdk_blob_get_xattr_value(snapshot2, g_xattr_names[0], &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[0])); + CU_ASSERT_NSTRING_EQUAL_FATAL(value, g_xattr_values[0], value_len); + + rc = spdk_blob_get_xattr_value(snapshot2, g_xattr_names[1], &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[1])); + CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[1], value_len); + + rc = spdk_blob_get_xattr_value(snapshot2, g_xattr_names[2], &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[2])); + CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[2], value_len); + + /* Try to create snapshot from snapshot */ + spdk_bs_create_snapshot(bs, snapshotid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + CU_ASSERT(g_blobid == SPDK_BLOBID_INVALID); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(snapshot, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(snapshot2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_snapshot_freeze_io(void) +{ + struct spdk_io_channel *channel; + struct spdk_bs_channel *bs_channel; + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_blob_opts opts; + spdk_blob_id blobid; + uint32_t num_of_pages = 10; + uint8_t payload_read[num_of_pages * SPDK_BS_PAGE_SIZE]; + uint8_t payload_write[num_of_pages * SPDK_BS_PAGE_SIZE]; + uint8_t payload_zero[num_of_pages * SPDK_BS_PAGE_SIZE]; + + memset(payload_write, 0xE5, sizeof(payload_write)); + memset(payload_read, 0x00, sizeof(payload_read)); + memset(payload_zero, 0x00, sizeof(payload_zero)); + + dev = init_dev(); + memset(g_dev_buffer, 0, DEV_BUFFER_SIZE); + + /* Test freeze I/O during snapshot */ + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + channel = spdk_bs_alloc_io_channel(bs); + bs_channel = spdk_io_channel_get_ctx(channel); + + /* Create blob with 10 clusters */ + spdk_blob_opts_init(&opts); + opts.num_clusters = 10; + opts.thin_provision = false; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10); + + /* Enable explicitly calling callbacks. On each read/write to back device + * execution will stop and wait until _bs_flush_scheduler is called */ + g_scheduler_delay = true; + + spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL); + + /* This is implementation specific. + * Flag 'frozen_io' is set in _spdk_bs_snapshot_freeze_cpl callback. + * Four async I/O operations happen before that. */ + + _bs_flush_scheduler(4); + + CU_ASSERT(TAILQ_EMPTY(&bs_channel->queued_io)); + + /* Blob I/O should be frozen here */ + CU_ASSERT(blob->frozen_refcnt == 1); + + /* Write to the blob */ + spdk_blob_io_write(blob, channel, payload_write, 0, num_of_pages, blob_op_complete, NULL); + + /* Verify that I/O is queued */ + CU_ASSERT(!TAILQ_EMPTY(&bs_channel->queued_io)); + /* Verify that payload is not written to disk */ + CU_ASSERT(memcmp(payload_zero, &g_dev_buffer[blob->active.clusters[0]*SPDK_BS_PAGE_SIZE], + SPDK_BS_PAGE_SIZE) == 0); + + /* Disable scheduler delay. + * Finish all operations including spdk_bs_create_snapshot */ + g_scheduler_delay = false; + _bs_flush_scheduler(1); + + /* Verify snapshot */ + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + + /* Verify that blob has unset frozen_io */ + CU_ASSERT(blob->frozen_refcnt == 0); + + /* Verify that postponed I/O completed successfully by comparing payload */ + spdk_blob_io_read(blob, channel, payload_read, 0, num_of_pages, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_write, payload_read, num_of_pages * SPDK_BS_PAGE_SIZE) == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_clone(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob_opts opts; + struct spdk_blob *blob, *snapshot, *clone; + spdk_blob_id blobid, cloneid, snapshotid; + struct spdk_blob_xattr_opts xattrs; + const void *value; + size_t value_len; + int rc; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + /* Create blob with 10 clusters */ + + spdk_blob_opts_init(&opts); + opts.num_clusters = 10; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10) + + /* Create snapshot */ + spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + snapshotid = g_blobid; + + spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot = g_blob; + CU_ASSERT(snapshot->data_ro == true) + CU_ASSERT(snapshot->md_ro == true) + CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 10); + + spdk_blob_close(snapshot, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Create clone from snapshot with xattrs */ + xattrs.names = g_xattr_names; + xattrs.get_value = _get_xattr_value; + xattrs.count = 3; + xattrs.ctx = &g_ctx; + + spdk_bs_create_clone(bs, snapshotid, &xattrs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + cloneid = g_blobid; + + spdk_bs_open_blob(bs, cloneid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + clone = g_blob; + CU_ASSERT(clone->data_ro == false) + CU_ASSERT(clone->md_ro == false) + CU_ASSERT(spdk_blob_get_num_clusters(clone) == 10); + + rc = spdk_blob_get_xattr_value(clone, g_xattr_names[0], &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[0])); + CU_ASSERT_NSTRING_EQUAL_FATAL(value, g_xattr_values[0], value_len); + + rc = spdk_blob_get_xattr_value(clone, g_xattr_names[1], &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[1])); + CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[1], value_len); + + rc = spdk_blob_get_xattr_value(clone, g_xattr_names[2], &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[2])); + CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[2], value_len); + + + spdk_blob_close(clone, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Try to create clone from not read only blob */ + spdk_bs_create_clone(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + CU_ASSERT(g_blobid == SPDK_BLOBID_INVALID); + + /* Mark blob as read only */ + spdk_blob_set_read_only(blob); + spdk_blob_sync_md(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Create clone from read only blob */ + spdk_bs_create_clone(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + cloneid = g_blobid; + + spdk_bs_open_blob(bs, cloneid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + clone = g_blob; + CU_ASSERT(clone->data_ro == false) + CU_ASSERT(clone->md_ro == false) + CU_ASSERT(spdk_blob_get_num_clusters(clone) == 10); + + spdk_blob_close(clone, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + +} + +static void +_blob_inflate(bool decouple_parent) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob_opts opts; + struct spdk_blob *blob, *snapshot; + spdk_blob_id blobid, snapshotid; + struct spdk_io_channel *channel; + uint64_t free_clusters; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + channel = spdk_bs_alloc_io_channel(bs); + SPDK_CU_ASSERT_FATAL(channel != NULL); + + /* Create blob with 10 clusters */ + + spdk_blob_opts_init(&opts); + opts.num_clusters = 10; + opts.thin_provision = true; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10) + CU_ASSERT(spdk_blob_is_thin_provisioned(blob) == true); + + /* 1) Blob with no parent */ + if (decouple_parent) { + /* Decouple parent of blob with no parent (should fail) */ + spdk_bs_blob_decouple_parent(bs, channel, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno != 0); + } else { + /* Inflate of thin blob with no parent should made it thick */ + spdk_bs_inflate_blob(bs, channel, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(spdk_blob_is_thin_provisioned(blob) == false); + } + + spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + snapshotid = g_blobid; + + CU_ASSERT(spdk_blob_is_thin_provisioned(blob) == true); + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10) + + spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot = g_blob; + CU_ASSERT(snapshot->data_ro == true) + CU_ASSERT(snapshot->md_ro == true) + CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 10); + + spdk_blob_close(snapshot, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + free_clusters = spdk_bs_free_cluster_count(bs); + + /* 2) Blob with parent */ + if (!decouple_parent) { + /* Do full blob inflation */ + spdk_bs_inflate_blob(bs, channel, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + /* all 10 clusters should be allocated */ + CU_ASSERT(spdk_bs_free_cluster_count(bs) == free_clusters - 10); + } else { + /* Decouple parent of blob */ + spdk_bs_blob_decouple_parent(bs, channel, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + /* when only parent is removed, none of the clusters should be allocated */ + CU_ASSERT(spdk_bs_free_cluster_count(bs) == free_clusters); + } + + /* Now, it should be possible to delete snapshot */ + spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10) + CU_ASSERT(spdk_blob_is_thin_provisioned(blob) == decouple_parent); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + spdk_bs_free_io_channel(channel); +} + +static void +blob_inflate(void) +{ + _blob_inflate(false); + _blob_inflate(true); +} + +static void +blob_delete(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + spdk_blob_id blobid; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + /* Create a blob and then delete it. */ + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid > 0); + blobid = g_blobid; + + spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Try to open the blob */ + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -ENOENT); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_resize(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + spdk_blob_id blobid; + uint64_t free_clusters; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + free_clusters = spdk_bs_free_cluster_count(bs); + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + /* Confirm that resize fails if blob is marked read-only. */ + blob->md_ro = true; + spdk_blob_resize(blob, 5, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == -EPERM); + blob->md_ro = false; + + /* The blob started at 0 clusters. Resize it to be 5. */ + spdk_blob_resize(blob, 5, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT((free_clusters - 5) == spdk_bs_free_cluster_count(bs)); + + /* Shrink the blob to 3 clusters. This will not actually release + * the old clusters until the blob is synced. + */ + spdk_blob_resize(blob, 3, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + /* Verify there are still 5 clusters in use */ + CU_ASSERT((free_clusters - 5) == spdk_bs_free_cluster_count(bs)); + + spdk_blob_sync_md(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + /* Now there are only 3 clusters in use */ + CU_ASSERT((free_clusters - 3) == spdk_bs_free_cluster_count(bs)); + + /* Resize the blob to be 10 clusters. Growth takes effect immediately. */ + spdk_blob_resize(blob, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT((free_clusters - 10) == spdk_bs_free_cluster_count(bs)); + + /* Try to resize the blob to size larger than blobstore. */ + spdk_blob_resize(blob, bs->total_clusters + 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == -ENOSPC); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_read_only(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_bs_opts opts; + spdk_blob_id blobid; + int rc; + + dev = init_dev(); + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + rc = spdk_blob_set_read_only(blob); + CU_ASSERT(rc == 0); + + CU_ASSERT(blob->data_ro == false); + CU_ASSERT(blob->md_ro == false); + + spdk_blob_sync_md(blob, bs_op_complete, NULL); + + CU_ASSERT(blob->data_ro == true); + CU_ASSERT(blob->md_ro == true); + CU_ASSERT(blob->data_ro_flags & SPDK_BLOB_READ_ONLY); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(blob->data_ro == true); + CU_ASSERT(blob->md_ro == true); + CU_ASSERT(blob->data_ro_flags & SPDK_BLOB_READ_ONLY); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + /* Load an existing blob store */ + dev = init_dev(); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(blob->data_ro == true); + CU_ASSERT(blob->md_ro == true); + CU_ASSERT(blob->data_ro_flags & SPDK_BLOB_READ_ONLY); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + +} + +static void +channel_ops(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_io_channel *channel; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_bs_free_io_channel(channel); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_write(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_io_channel *channel; + spdk_blob_id blobid; + uint64_t pages_per_cluster; + uint8_t payload[10 * 4096]; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + pages_per_cluster = spdk_bs_get_cluster_size(bs) / spdk_bs_get_page_size(bs); + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + /* Write to a blob with 0 size */ + spdk_blob_io_write(blob, channel, payload, 0, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + /* Resize the blob */ + spdk_blob_resize(blob, 5, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Confirm that write fails if blob is marked read-only. */ + blob->data_ro = true; + spdk_blob_io_write(blob, channel, payload, 0, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == -EPERM); + blob->data_ro = false; + + /* Write to the blob */ + spdk_blob_io_write(blob, channel, payload, 0, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Write starting beyond the end */ + spdk_blob_io_write(blob, channel, payload, 5 * pages_per_cluster, 1, blob_op_complete, + NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + /* Write starting at a valid location but going off the end */ + spdk_blob_io_write(blob, channel, payload, 4 * pages_per_cluster, pages_per_cluster + 1, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_read(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_io_channel *channel; + spdk_blob_id blobid; + uint64_t pages_per_cluster; + uint8_t payload[10 * 4096]; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + pages_per_cluster = spdk_bs_get_cluster_size(bs) / spdk_bs_get_page_size(bs); + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + /* Read from a blob with 0 size */ + spdk_blob_io_read(blob, channel, payload, 0, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + /* Resize the blob */ + spdk_blob_resize(blob, 5, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Confirm that read passes if blob is marked read-only. */ + blob->data_ro = true; + spdk_blob_io_read(blob, channel, payload, 0, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + blob->data_ro = false; + + /* Read from the blob */ + spdk_blob_io_read(blob, channel, payload, 0, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Read starting beyond the end */ + spdk_blob_io_read(blob, channel, payload, 5 * pages_per_cluster, 1, blob_op_complete, + NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + /* Read starting at a valid location but going off the end */ + spdk_blob_io_read(blob, channel, payload, 4 * pages_per_cluster, pages_per_cluster + 1, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_rw_verify(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_io_channel *channel; + spdk_blob_id blobid; + uint8_t payload_read[10 * 4096]; + uint8_t payload_write[10 * 4096]; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + spdk_blob_resize(blob, 32, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + memset(payload_write, 0xE5, sizeof(payload_write)); + spdk_blob_io_write(blob, channel, payload_write, 4, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + memset(payload_read, 0x00, sizeof(payload_read)); + spdk_blob_io_read(blob, channel, payload_read, 4, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_write, payload_read, 4 * 4096) == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_rw_verify_iov(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_io_channel *channel; + spdk_blob_id blobid; + uint8_t payload_read[10 * 4096]; + uint8_t payload_write[10 * 4096]; + struct iovec iov_read[3]; + struct iovec iov_write[3]; + void *buf; + + dev = init_dev(); + memset(g_dev_buffer, 0, DEV_BUFFER_SIZE); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + spdk_blob_resize(blob, 2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* + * Manually adjust the offset of the blob's second cluster. This allows + * us to make sure that the readv/write code correctly accounts for I/O + * that cross cluster boundaries. Start by asserting that the allocated + * clusters are where we expect before modifying the second cluster. + */ + CU_ASSERT(blob->active.clusters[0] == 1 * 256); + CU_ASSERT(blob->active.clusters[1] == 2 * 256); + blob->active.clusters[1] = 3 * 256; + + memset(payload_write, 0xE5, sizeof(payload_write)); + iov_write[0].iov_base = payload_write; + iov_write[0].iov_len = 1 * 4096; + iov_write[1].iov_base = payload_write + 1 * 4096; + iov_write[1].iov_len = 5 * 4096; + iov_write[2].iov_base = payload_write + 6 * 4096; + iov_write[2].iov_len = 4 * 4096; + /* + * Choose a page offset just before the cluster boundary. The first 6 pages of payload + * will get written to the first cluster, the last 4 to the second cluster. + */ + spdk_blob_io_writev(blob, channel, iov_write, 3, 250, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + memset(payload_read, 0xAA, sizeof(payload_read)); + iov_read[0].iov_base = payload_read; + iov_read[0].iov_len = 3 * 4096; + iov_read[1].iov_base = payload_read + 3 * 4096; + iov_read[1].iov_len = 4 * 4096; + iov_read[2].iov_base = payload_read + 7 * 4096; + iov_read[2].iov_len = 3 * 4096; + spdk_blob_io_readv(blob, channel, iov_read, 3, 250, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0); + + buf = calloc(1, 256 * 4096); + SPDK_CU_ASSERT_FATAL(buf != NULL); + /* Check that cluster 2 on "disk" was not modified. */ + CU_ASSERT(memcmp(buf, &g_dev_buffer[512 * 4096], 256 * 4096) == 0); + free(buf); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static uint32_t +bs_channel_get_req_count(struct spdk_io_channel *_channel) +{ + struct spdk_bs_channel *channel = spdk_io_channel_get_ctx(_channel); + struct spdk_bs_request_set *set; + uint32_t count = 0; + + TAILQ_FOREACH(set, &channel->reqs, link) { + count++; + } + + return count; +} + +static void +blob_rw_verify_iov_nomem(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_io_channel *channel; + spdk_blob_id blobid; + uint8_t payload_write[10 * 4096]; + struct iovec iov_write[3]; + uint32_t req_count; + + dev = init_dev(); + memset(g_dev_buffer, 0, DEV_BUFFER_SIZE); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + spdk_blob_resize(blob, 2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* + * Choose a page offset just before the cluster boundary. The first 6 pages of payload + * will get written to the first cluster, the last 4 to the second cluster. + */ + iov_write[0].iov_base = payload_write; + iov_write[0].iov_len = 1 * 4096; + iov_write[1].iov_base = payload_write + 1 * 4096; + iov_write[1].iov_len = 5 * 4096; + iov_write[2].iov_base = payload_write + 6 * 4096; + iov_write[2].iov_len = 4 * 4096; + MOCK_SET(calloc, NULL); + req_count = bs_channel_get_req_count(channel); + spdk_blob_io_writev(blob, channel, iov_write, 3, 250, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno = -ENOMEM); + CU_ASSERT(req_count == bs_channel_get_req_count(channel)); + MOCK_CLEAR(calloc); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_rw_iov_read_only(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_io_channel *channel; + spdk_blob_id blobid; + uint8_t payload_read[4096]; + uint8_t payload_write[4096]; + struct iovec iov_read; + struct iovec iov_write; + + dev = init_dev(); + memset(g_dev_buffer, 0, DEV_BUFFER_SIZE); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + spdk_blob_resize(blob, 2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Verify that writev failed if read_only flag is set. */ + blob->data_ro = true; + iov_write.iov_base = payload_write; + iov_write.iov_len = sizeof(payload_write); + spdk_blob_io_writev(blob, channel, &iov_write, 1, 0, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == -EPERM); + + /* Verify that reads pass if data_ro flag is set. */ + iov_read.iov_base = payload_read; + iov_read.iov_len = sizeof(payload_read); + spdk_blob_io_readv(blob, channel, &iov_read, 1, 0, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +_blob_io_read_no_split(struct spdk_blob *blob, struct spdk_io_channel *channel, + uint8_t *payload, uint64_t offset, uint64_t length, + spdk_blob_op_complete cb_fn, void *cb_arg) +{ + uint64_t i; + uint8_t *buf; + uint64_t page_size = spdk_bs_get_page_size(blob->bs); + + /* To be sure that operation is NOT splitted, read one page at the time */ + buf = payload; + for (i = 0; i < length; i++) { + spdk_blob_io_read(blob, channel, buf, i + offset, 1, blob_op_complete, NULL); + if (g_bserrno != 0) { + /* Pass the error code up */ + break; + } + buf += page_size; + } + + cb_fn(cb_arg, g_bserrno); +} + +static void +_blob_io_write_no_split(struct spdk_blob *blob, struct spdk_io_channel *channel, + uint8_t *payload, uint64_t offset, uint64_t length, + spdk_blob_op_complete cb_fn, void *cb_arg) +{ + uint64_t i; + uint8_t *buf; + uint64_t page_size = spdk_bs_get_page_size(blob->bs); + + /* To be sure that operation is NOT splitted, write one page at the time */ + buf = payload; + for (i = 0; i < length; i++) { + spdk_blob_io_write(blob, channel, buf, i + offset, 1, blob_op_complete, NULL); + if (g_bserrno != 0) { + /* Pass the error code up */ + break; + } + buf += page_size; + } + + cb_fn(cb_arg, g_bserrno); +} + +static void +blob_operation_split_rw(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_io_channel *channel; + struct spdk_blob_opts opts; + spdk_blob_id blobid; + uint64_t cluster_size; + + uint64_t payload_size; + uint8_t *payload_read; + uint8_t *payload_write; + uint8_t *payload_pattern; + + uint64_t page_size; + uint64_t pages_per_cluster; + uint64_t pages_per_payload; + + uint64_t i; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + cluster_size = spdk_bs_get_cluster_size(bs); + page_size = spdk_bs_get_page_size(bs); + pages_per_cluster = cluster_size / page_size; + pages_per_payload = pages_per_cluster * 5; + payload_size = cluster_size * 5; + + payload_read = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_read != NULL); + + payload_write = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_write != NULL); + + payload_pattern = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_pattern != NULL); + + /* Prepare random pattern to write */ + memset(payload_pattern, 0xFF, payload_size); + for (i = 0; i < pages_per_payload; i++) { + *((uint64_t *)(payload_pattern + page_size * i)) = (i + 1); + } + + channel = spdk_bs_alloc_io_channel(bs); + SPDK_CU_ASSERT_FATAL(channel != NULL); + + /* Create blob */ + spdk_blob_opts_init(&opts); + opts.thin_provision = false; + opts.num_clusters = 5; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5); + + /* Initial read should return zeroed payload */ + memset(payload_read, 0xFF, payload_size); + spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(spdk_mem_all_zero(payload_read, payload_size)); + + /* Fill whole blob except last page */ + spdk_blob_io_write(blob, channel, payload_pattern, 0, pages_per_payload - 1, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Write last page with a pattern */ + spdk_blob_io_write(blob, channel, payload_pattern, pages_per_payload - 1, 1, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Read whole blob and check consistency */ + memset(payload_read, 0xFF, payload_size); + spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size - page_size) == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read + payload_size - page_size, page_size) == 0); + + /* Fill whole blob except first page */ + spdk_blob_io_write(blob, channel, payload_pattern, 1, pages_per_payload - 1, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Write first page with a pattern */ + spdk_blob_io_write(blob, channel, payload_pattern, 0, 1, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Read whole blob and check consistency */ + memset(payload_read, 0xFF, payload_size); + spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read + page_size, payload_size - page_size) == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read, page_size) == 0); + + + /* Fill whole blob with a pattern (5 clusters) */ + + /* 1. Read test. */ + _blob_io_write_no_split(blob, channel, payload_pattern, 0, pages_per_payload, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + memset(payload_read, 0xFF, payload_size); + spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size) == 0); + + /* 2. Write test. */ + spdk_blob_io_write(blob, channel, payload_pattern, 0, pages_per_payload, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + memset(payload_read, 0xFF, payload_size); + _blob_io_read_no_split(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size) == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + free(payload_read); + free(payload_write); + free(payload_pattern); +} + +static void +blob_operation_split_rw_iov(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_io_channel *channel; + struct spdk_blob_opts opts; + spdk_blob_id blobid; + uint64_t cluster_size; + + uint64_t payload_size; + uint8_t *payload_read; + uint8_t *payload_write; + uint8_t *payload_pattern; + + uint64_t page_size; + uint64_t pages_per_cluster; + uint64_t pages_per_payload; + + struct iovec iov_read[2]; + struct iovec iov_write[2]; + + uint64_t i, j; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + cluster_size = spdk_bs_get_cluster_size(bs); + page_size = spdk_bs_get_page_size(bs); + pages_per_cluster = cluster_size / page_size; + pages_per_payload = pages_per_cluster * 5; + payload_size = cluster_size * 5; + + payload_read = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_read != NULL); + + payload_write = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_write != NULL); + + payload_pattern = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_pattern != NULL); + + /* Prepare random pattern to write */ + for (i = 0; i < pages_per_payload; i++) { + for (j = 0; j < page_size / sizeof(uint64_t); j++) { + uint64_t *tmp; + + tmp = (uint64_t *)payload_pattern; + tmp += ((page_size * i) / sizeof(uint64_t)) + j; + *tmp = i + 1; + } + } + + channel = spdk_bs_alloc_io_channel(bs); + SPDK_CU_ASSERT_FATAL(channel != NULL); + + /* Create blob */ + spdk_blob_opts_init(&opts); + opts.thin_provision = false; + opts.num_clusters = 5; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5); + + /* Initial read should return zeroes payload */ + memset(payload_read, 0xFF, payload_size); + iov_read[0].iov_base = payload_read; + iov_read[0].iov_len = cluster_size * 3; + iov_read[1].iov_base = payload_read + cluster_size * 3; + iov_read[1].iov_len = cluster_size * 2; + spdk_blob_io_readv(blob, channel, iov_read, 2, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(spdk_mem_all_zero(payload_read, payload_size)); + + /* First of iovs fills whole blob except last page and second of iovs writes last page + * with a pattern. */ + iov_write[0].iov_base = payload_pattern; + iov_write[0].iov_len = payload_size - page_size; + iov_write[1].iov_base = payload_pattern; + iov_write[1].iov_len = page_size; + spdk_blob_io_writev(blob, channel, iov_write, 2, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Read whole blob and check consistency */ + memset(payload_read, 0xFF, payload_size); + iov_read[0].iov_base = payload_read; + iov_read[0].iov_len = cluster_size * 2; + iov_read[1].iov_base = payload_read + cluster_size * 2; + iov_read[1].iov_len = cluster_size * 3; + spdk_blob_io_readv(blob, channel, iov_read, 2, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size - page_size) == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read + payload_size - page_size, page_size) == 0); + + /* First of iovs fills only first page and second of iovs writes whole blob except + * first page with a pattern. */ + iov_write[0].iov_base = payload_pattern; + iov_write[0].iov_len = page_size; + iov_write[1].iov_base = payload_pattern; + iov_write[1].iov_len = payload_size - page_size; + spdk_blob_io_writev(blob, channel, iov_write, 2, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Read whole blob and check consistency */ + memset(payload_read, 0xFF, payload_size); + iov_read[0].iov_base = payload_read; + iov_read[0].iov_len = cluster_size * 4; + iov_read[1].iov_base = payload_read + cluster_size * 4; + iov_read[1].iov_len = cluster_size; + spdk_blob_io_readv(blob, channel, iov_read, 2, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read + page_size, payload_size - page_size) == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read, page_size) == 0); + + + /* Fill whole blob with a pattern (5 clusters) */ + + /* 1. Read test. */ + _blob_io_write_no_split(blob, channel, payload_pattern, 0, pages_per_payload, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + memset(payload_read, 0xFF, payload_size); + iov_read[0].iov_base = payload_read; + iov_read[0].iov_len = cluster_size; + iov_read[1].iov_base = payload_read + cluster_size; + iov_read[1].iov_len = cluster_size * 4; + spdk_blob_io_readv(blob, channel, iov_read, 2, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size) == 0); + + /* 2. Write test. */ + iov_write[0].iov_base = payload_read; + iov_write[0].iov_len = cluster_size * 2; + iov_write[1].iov_base = payload_read + cluster_size * 2; + iov_write[1].iov_len = cluster_size * 3; + spdk_blob_io_writev(blob, channel, iov_write, 2, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + memset(payload_read, 0xFF, payload_size); + _blob_io_read_no_split(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size) == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + free(payload_read); + free(payload_write); + free(payload_pattern); +} + +static void +blob_unmap(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_io_channel *channel; + spdk_blob_id blobid; + struct spdk_blob_opts opts; + uint8_t payload[4096]; + int i; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_blob_opts_init(&opts); + opts.num_clusters = 10; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + spdk_blob_resize(blob, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + memset(payload, 0, sizeof(payload)); + payload[0] = 0xFF; + + /* + * Set first byte of every cluster to 0xFF. + * First cluster on device is reserved so let's start from cluster number 1 + */ + for (i = 1; i < 11; i++) { + g_dev_buffer[i * SPDK_BLOB_OPTS_CLUSTER_SZ] = 0xFF; + } + + /* Confirm writes */ + for (i = 0; i < 10; i++) { + payload[0] = 0; + spdk_blob_io_read(blob, channel, &payload, i * SPDK_BLOB_OPTS_CLUSTER_SZ / 4096, 1, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(payload[0] == 0xFF); + } + + /* Mark some clusters as unallocated */ + blob->active.clusters[1] = 0; + blob->active.clusters[2] = 0; + blob->active.clusters[3] = 0; + blob->active.clusters[6] = 0; + blob->active.clusters[8] = 0; + + /* Unmap clusters by resizing to 0 */ + spdk_blob_resize(blob, 0, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_sync_md(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Confirm that only 'allocated' clusters were unmapped */ + for (i = 1; i < 11; i++) { + switch (i) { + case 2: + case 3: + case 4: + case 7: + case 9: + CU_ASSERT(g_dev_buffer[i * SPDK_BLOB_OPTS_CLUSTER_SZ] == 0xFF); + break; + default: + CU_ASSERT(g_dev_buffer[i * SPDK_BLOB_OPTS_CLUSTER_SZ] == 0); + break; + } + } + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + + +static void +blob_iter(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + spdk_blob_id blobid; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + spdk_bs_iter_first(bs, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_blob == NULL); + CU_ASSERT(g_bserrno == -ENOENT); + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_iter_first(bs, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_blob != NULL); + CU_ASSERT(g_bserrno == 0); + blob = g_blob; + CU_ASSERT(spdk_blob_get_id(blob) == blobid); + + spdk_bs_iter_next(bs, blob, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_blob == NULL); + CU_ASSERT(g_bserrno == -ENOENT); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_xattr(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + spdk_blob_id blobid; + uint64_t length; + int rc; + const char *name1, *name2; + const void *value; + size_t value_len; + struct spdk_xattr_names *names; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + /* Test that set_xattr fails if md_ro flag is set. */ + blob->md_ro = true; + rc = spdk_blob_set_xattr(blob, "name", "log.txt", strlen("log.txt") + 1); + CU_ASSERT(rc == -EPERM); + + blob->md_ro = false; + rc = spdk_blob_set_xattr(blob, "name", "log.txt", strlen("log.txt") + 1); + CU_ASSERT(rc == 0); + + length = 2345; + rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length)); + CU_ASSERT(rc == 0); + + /* Overwrite "length" xattr. */ + length = 3456; + rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length)); + CU_ASSERT(rc == 0); + + /* get_xattr should still work even if md_ro flag is set. */ + value = NULL; + blob->md_ro = true; + rc = spdk_blob_get_xattr_value(blob, "length", &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(*(uint64_t *)value == length); + CU_ASSERT(value_len == 8); + blob->md_ro = false; + + rc = spdk_blob_get_xattr_value(blob, "foobar", &value, &value_len); + CU_ASSERT(rc == -ENOENT); + + names = NULL; + rc = spdk_blob_get_xattr_names(blob, &names); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(names != NULL); + CU_ASSERT(spdk_xattr_names_get_count(names) == 2); + name1 = spdk_xattr_names_get_name(names, 0); + SPDK_CU_ASSERT_FATAL(name1 != NULL); + CU_ASSERT(!strcmp(name1, "name") || !strcmp(name1, "length")); + name2 = spdk_xattr_names_get_name(names, 1); + SPDK_CU_ASSERT_FATAL(name2 != NULL); + CU_ASSERT(!strcmp(name2, "name") || !strcmp(name2, "length")); + CU_ASSERT(strcmp(name1, name2)); + spdk_xattr_names_free(names); + + /* Confirm that remove_xattr fails if md_ro is set to true. */ + blob->md_ro = true; + rc = spdk_blob_remove_xattr(blob, "name"); + CU_ASSERT(rc == -EPERM); + + blob->md_ro = false; + rc = spdk_blob_remove_xattr(blob, "name"); + CU_ASSERT(rc == 0); + + rc = spdk_blob_remove_xattr(blob, "foobar"); + CU_ASSERT(rc == -ENOENT); + + /* Set internal xattr */ + length = 7898; + rc = _spdk_blob_set_xattr(blob, "internal", &length, sizeof(length), true); + CU_ASSERT(rc == 0); + rc = _spdk_blob_get_xattr_value(blob, "internal", &value, &value_len, true); + CU_ASSERT(rc == 0); + CU_ASSERT(*(uint64_t *)value == length); + /* try to get public xattr with same name */ + rc = spdk_blob_get_xattr_value(blob, "internal", &value, &value_len); + CU_ASSERT(rc != 0); + rc = _spdk_blob_get_xattr_value(blob, "internal", &value, &value_len, false); + CU_ASSERT(rc != 0); + /* Check if SPDK_BLOB_INTERNAL_XATTR is set */ + CU_ASSERT((blob->invalid_flags & SPDK_BLOB_INTERNAL_XATTR) == + SPDK_BLOB_INTERNAL_XATTR) + + spdk_blob_close(blob, blob_op_complete, NULL); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + + /* Check if xattrs are persisted */ + dev = init_dev(); + + spdk_bs_load(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + bs = g_bs; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + rc = _spdk_blob_get_xattr_value(blob, "internal", &value, &value_len, true); + CU_ASSERT(rc == 0); + CU_ASSERT(*(uint64_t *)value == length); + + /* try to get internal xattr trough public call */ + rc = spdk_blob_get_xattr_value(blob, "internal", &value, &value_len); + CU_ASSERT(rc != 0); + + rc = _spdk_blob_remove_xattr(blob, "internal", true); + CU_ASSERT(rc == 0); + + CU_ASSERT((blob->invalid_flags & SPDK_BLOB_INTERNAL_XATTR) == 0); + + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +bs_load(void) +{ + struct spdk_bs_dev *dev; + spdk_blob_id blobid; + struct spdk_blob *blob; + struct spdk_bs_super_block *super_block; + uint64_t length; + int rc; + const void *value; + size_t value_len; + struct spdk_bs_opts opts; + + dev = init_dev(); + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + /* Try to open a blobid that does not exist */ + spdk_bs_open_blob(g_bs, 0, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -ENOENT); + CU_ASSERT(g_blob == NULL); + + /* Create a blob */ + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + /* Try again to open valid blob but without the upper bit set */ + spdk_bs_open_blob(g_bs, blobid & 0xFFFFFFFF, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -ENOENT); + CU_ASSERT(g_blob == NULL); + + /* Set some xattrs */ + rc = spdk_blob_set_xattr(blob, "name", "log.txt", strlen("log.txt") + 1); + CU_ASSERT(rc == 0); + + length = 2345; + rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length)); + CU_ASSERT(rc == 0); + + /* Resize the blob */ + spdk_blob_resize(blob, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + blob = NULL; + g_blob = NULL; + g_blobid = SPDK_BLOBID_INVALID; + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + super_block = (struct spdk_bs_super_block *)g_dev_buffer; + CU_ASSERT(super_block->clean == 1); + + /* Load should fail for device with an unsupported blocklen */ + dev = init_dev(); + dev->blocklen = SPDK_BS_PAGE_SIZE * 2; + spdk_bs_load(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + /* Load should when max_md_ops is set to zero */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + opts.max_md_ops = 0; + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + /* Load should when max_channel_ops is set to zero */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + opts.max_channel_ops = 0; + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + /* Load an existing blob store */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + super_block = (struct spdk_bs_super_block *)g_dev_buffer; + CU_ASSERT(super_block->clean == 1); + CU_ASSERT(super_block->size == dev->blockcnt * dev->blocklen); + + spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + /* Verify that blobstore is marked dirty after first metadata sync */ + spdk_blob_sync_md(blob, blob_op_complete, NULL); + CU_ASSERT(super_block->clean == 1); + + /* Get the xattrs */ + value = NULL; + rc = spdk_blob_get_xattr_value(blob, "length", &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(*(uint64_t *)value == length); + CU_ASSERT(value_len == 8); + + rc = spdk_blob_get_xattr_value(blob, "foobar", &value, &value_len); + CU_ASSERT(rc == -ENOENT); + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + blob = NULL; + g_blob = NULL; + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + /* Load should fail: bdev size < saved size */ + dev = init_dev(); + dev->blockcnt /= 2; + + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + + CU_ASSERT(g_bserrno == -EILSEQ); + + /* Load should succeed: bdev size > saved size */ + dev = init_dev(); + dev->blockcnt *= 4; + + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + + CU_ASSERT(g_bserrno == 0); + spdk_bs_unload(g_bs, bs_op_complete, NULL); + + + /* Test compatibility mode */ + + dev = init_dev(); + super_block->size = 0; + super_block->crc = _spdk_blob_md_page_calc_crc(super_block); + + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + /* Create a blob */ + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + + /* Blobstore should update number of blocks in super_block */ + CU_ASSERT(super_block->size == dev->blockcnt * dev->blocklen); + CU_ASSERT(super_block->clean == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(super_block->clean == 1); + g_bs = NULL; + +} + +static void +bs_load_custom_cluster_size(void) +{ + struct spdk_bs_dev *dev; + struct spdk_bs_super_block *super_block; + struct spdk_bs_opts opts; + uint32_t custom_cluster_size = 4194304; /* 4MiB */ + uint32_t cluster_sz; + uint64_t total_clusters; + + dev = init_dev(); + spdk_bs_opts_init(&opts); + opts.cluster_sz = custom_cluster_size; + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + cluster_sz = g_bs->cluster_sz; + total_clusters = g_bs->total_clusters; + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + super_block = (struct spdk_bs_super_block *)g_dev_buffer; + CU_ASSERT(super_block->clean == 1); + + /* Load an existing blob store */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + /* Compare cluster size and number to one after initialization */ + CU_ASSERT(cluster_sz == g_bs->cluster_sz); + CU_ASSERT(total_clusters == g_bs->total_clusters); + + super_block = (struct spdk_bs_super_block *)g_dev_buffer; + CU_ASSERT(super_block->clean == 1); + CU_ASSERT(super_block->size == dev->blockcnt * dev->blocklen); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(super_block->clean == 1); + g_bs = NULL; +} + +static void +bs_type(void) +{ + struct spdk_bs_dev *dev; + struct spdk_bs_opts opts; + + dev = init_dev(); + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + /* Load non existing blobstore type */ + dev = init_dev(); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "NONEXISTING"); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno != 0); + + /* Load with empty blobstore type */ + dev = init_dev(); + memset(opts.bstype.bstype, 0, sizeof(opts.bstype.bstype)); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + /* Initialize a new blob store with empty bstype */ + dev = init_dev(); + memset(opts.bstype.bstype, 0, sizeof(opts.bstype.bstype)); + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + /* Load non existing blobstore type */ + dev = init_dev(); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "NONEXISTING"); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno != 0); + + /* Load with empty blobstore type */ + dev = init_dev(); + memset(opts.bstype.bstype, 0, sizeof(opts.bstype.bstype)); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +bs_super_block(void) +{ + struct spdk_bs_dev *dev; + struct spdk_bs_super_block *super_block; + struct spdk_bs_opts opts; + struct spdk_bs_super_block_ver1 super_block_v1; + + dev = init_dev(); + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + /* Load an existing blob store with version newer than supported */ + super_block = (struct spdk_bs_super_block *)g_dev_buffer; + super_block->version++; + + dev = init_dev(); + memset(opts.bstype.bstype, 0, sizeof(opts.bstype.bstype)); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno != 0); + + /* Create a new blob store with super block version 1 */ + dev = init_dev(); + super_block_v1.version = 1; + memcpy(super_block_v1.signature, "SPDKBLOB", sizeof(super_block_v1.signature)); + super_block_v1.length = 0x1000; + super_block_v1.clean = 1; + super_block_v1.super_blob = 0xFFFFFFFFFFFFFFFF; + super_block_v1.cluster_size = 0x100000; + super_block_v1.used_page_mask_start = 0x01; + super_block_v1.used_page_mask_len = 0x01; + super_block_v1.used_cluster_mask_start = 0x02; + super_block_v1.used_cluster_mask_len = 0x01; + super_block_v1.md_start = 0x03; + super_block_v1.md_len = 0x40; + memset(super_block_v1.reserved, 0, 4036); + super_block_v1.crc = _spdk_blob_md_page_calc_crc(&super_block_v1); + memcpy(g_dev_buffer, &super_block_v1, sizeof(struct spdk_bs_super_block_ver1)); + + memset(opts.bstype.bstype, 0, sizeof(opts.bstype.bstype)); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +/* + * Create a blobstore and then unload it. + */ +static void +bs_unload(void) +{ + struct spdk_bs_dev *dev; + struct spdk_blob_store *bs; + spdk_blob_id blobid; + struct spdk_blob *blob; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + /* Create a blob and open it. */ + g_bserrno = -1; + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid > 0); + blobid = g_blobid; + + g_bserrno = -1; + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + /* Try to unload blobstore, should fail with open blob */ + g_bserrno = -1; + spdk_bs_unload(bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == -EBUSY); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + /* Close the blob, then successfully unload blobstore */ + g_bserrno = -1; + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + g_bserrno = -1; + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +/* + * Create a blobstore with a cluster size different than the default, and ensure it is + * persisted. + */ +static void +bs_cluster_sz(void) +{ + struct spdk_bs_dev *dev; + struct spdk_bs_opts opts; + uint32_t cluster_sz; + + /* Set cluster size to zero */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + opts.cluster_sz = 0; + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + SPDK_CU_ASSERT_FATAL(g_bs == NULL); + + /* + * Set cluster size to blobstore page size, + * to work it is required to be at least twice the blobstore page size. + */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + opts.cluster_sz = SPDK_BS_PAGE_SIZE; + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -ENOMEM); + SPDK_CU_ASSERT_FATAL(g_bs == NULL); + + /* + * Set cluster size to lower than page size, + * to work it is required to be at least twice the blobstore page size. + */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + opts.cluster_sz = SPDK_BS_PAGE_SIZE - 1; + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + SPDK_CU_ASSERT_FATAL(g_bs == NULL); + + /* Set cluster size to twice the default */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + opts.cluster_sz *= 2; + cluster_sz = opts.cluster_sz; + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + CU_ASSERT(spdk_bs_get_cluster_size(g_bs) == cluster_sz); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + dev = init_dev(); + /* Load an existing blob store */ + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + CU_ASSERT(spdk_bs_get_cluster_size(g_bs) == cluster_sz); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +/* + * Create a blobstore, reload it and ensure total usable cluster count + * stays the same. + */ +static void +bs_usable_clusters(void) +{ + struct spdk_bs_dev *dev; + struct spdk_bs_opts opts; + uint32_t clusters; + int i; + + /* Init blobstore */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + clusters = spdk_bs_total_data_cluster_count(g_bs); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + dev = init_dev(); + /* Load an existing blob store */ + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + CU_ASSERT(spdk_bs_total_data_cluster_count(g_bs) == clusters); + + /* Create and resize blobs to make sure that useable cluster count won't change */ + for (i = 0; i < 4; i++) { + g_bserrno = -1; + g_blobid = SPDK_BLOBID_INVALID; + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + + g_bserrno = -1; + g_blob = NULL; + spdk_bs_open_blob(g_bs, g_blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + + spdk_blob_resize(g_blob, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + g_bserrno = -1; + spdk_blob_close(g_blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(spdk_bs_total_data_cluster_count(g_bs) == clusters); + } + + /* Reload the blob store to make sure that nothing changed */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + dev = init_dev(); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + CU_ASSERT(spdk_bs_total_data_cluster_count(g_bs) == clusters); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +/* + * Test resizing of the metadata blob. This requires creating enough blobs + * so that one cluster is not enough to fit the metadata for those blobs. + * To induce this condition to happen more quickly, we reduce the cluster + * size to 16KB, which means only 4 4KB blob metadata pages can fit. + */ +static void +bs_resize_md(void) +{ + const int CLUSTER_PAGE_COUNT = 4; + const int NUM_BLOBS = CLUSTER_PAGE_COUNT * 4; + struct spdk_bs_dev *dev; + struct spdk_bs_opts opts; + uint32_t cluster_sz; + spdk_blob_id blobids[NUM_BLOBS]; + int i; + + + dev = init_dev(); + spdk_bs_opts_init(&opts); + opts.cluster_sz = CLUSTER_PAGE_COUNT * 4096; + cluster_sz = opts.cluster_sz; + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + CU_ASSERT(spdk_bs_get_cluster_size(g_bs) == cluster_sz); + + for (i = 0; i < NUM_BLOBS; i++) { + g_bserrno = -1; + g_blobid = SPDK_BLOBID_INVALID; + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobids[i] = g_blobid; + } + + /* Unload the blob store */ + g_bserrno = -1; + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Load an existing blob store */ + g_bserrno = -1; + g_bs = NULL; + dev = init_dev(); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + CU_ASSERT(spdk_bs_get_cluster_size(g_bs) == cluster_sz); + + for (i = 0; i < NUM_BLOBS; i++) { + g_bserrno = -1; + g_blob = NULL; + spdk_bs_open_blob(g_bs, blobids[i], blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + g_bserrno = -1; + spdk_blob_close(g_blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + } + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +bs_destroy(void) +{ + struct spdk_bs_dev *dev; + struct spdk_bs_opts opts; + + /* Initialize a new blob store */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + /* Destroy the blob store */ + g_bserrno = -1; + spdk_bs_destroy(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Loading an non-existent blob store should fail. */ + g_bs = NULL; + dev = init_dev(); + + g_bserrno = 0; + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno != 0); +} + +/* Try to hit all of the corner cases associated with serializing + * a blob to disk + */ +static void +blob_serialize(void) +{ + struct spdk_bs_dev *dev; + struct spdk_bs_opts opts; + struct spdk_blob_store *bs; + spdk_blob_id blobid[2]; + struct spdk_blob *blob[2]; + uint64_t i; + char *value; + int rc; + + dev = init_dev(); + + /* Initialize a new blobstore with very small clusters */ + spdk_bs_opts_init(&opts); + opts.cluster_sz = dev->blocklen * 8; + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + /* Create and open two blobs */ + for (i = 0; i < 2; i++) { + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid[i] = g_blobid; + + /* Open a blob */ + spdk_bs_open_blob(bs, blobid[i], blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob[i] = g_blob; + + /* Set a fairly large xattr on both blobs to eat up + * metadata space + */ + value = calloc(dev->blocklen - 64, sizeof(char)); + SPDK_CU_ASSERT_FATAL(value != NULL); + memset(value, i, dev->blocklen / 2); + rc = spdk_blob_set_xattr(blob[i], "name", value, dev->blocklen - 64); + CU_ASSERT(rc == 0); + free(value); + } + + /* Resize the blobs, alternating 1 cluster at a time. + * This thwarts run length encoding and will cause spill + * over of the extents. + */ + for (i = 0; i < 6; i++) { + spdk_blob_resize(blob[i % 2], (i / 2) + 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + } + + for (i = 0; i < 2; i++) { + spdk_blob_sync_md(blob[i], blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + } + + /* Close the blobs */ + for (i = 0; i < 2; i++) { + spdk_blob_close(blob[i], blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + } + + /* Unload the blobstore */ + spdk_bs_unload(bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + bs = NULL; + + dev = init_dev(); + /* Load an existing blob store */ + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + for (i = 0; i < 2; i++) { + blob[i] = NULL; + + spdk_bs_open_blob(bs, blobid[i], blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob[i] = g_blob; + + CU_ASSERT(spdk_blob_get_num_clusters(blob[i]) == 3); + + spdk_blob_close(blob[i], blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + } + + spdk_bs_unload(bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_crc(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + spdk_blob_id blobid; + uint32_t page_num; + int index; + struct spdk_blob_md_page *page; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + page_num = _spdk_bs_blobid_to_page(blobid); + index = DEV_BUFFER_BLOCKLEN * (bs->md_start + page_num); + page = (struct spdk_blob_md_page *)&g_dev_buffer[index]; + page->crc = 0; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + CU_ASSERT(g_blob == NULL); + g_bserrno = 0; + + spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +super_block_crc(void) +{ + struct spdk_bs_dev *dev; + struct spdk_bs_super_block *super_block; + struct spdk_bs_opts opts; + + dev = init_dev(); + spdk_bs_opts_init(&opts); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + super_block = (struct spdk_bs_super_block *)g_dev_buffer; + super_block->crc = 0; + dev = init_dev(); + + /* Load an existing blob store */ + g_bserrno = 0; + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == -EILSEQ); +} + +/* For blob dirty shutdown test case we do the following sub-test cases: + * 1 Initialize new blob store and create 1 super blob with some xattrs, then we + * dirty shutdown and reload the blob store and verify the xattrs. + * 2 Resize the blob from 10 clusters to 20 clusters and then dirty shutdown, + * reload the blob store and verify the clusters number. + * 3 Create the second blob and then dirty shutdown, reload the blob store + * and verify the second blob. + * 4 Delete the second blob and then dirty shutdown, reload the blob store + * and verify the second blob is invalid. + * 5 Create the second blob again and also create the third blob, modify the + * md of second blob which makes the md invalid, and then dirty shutdown, + * reload the blob store verify the second blob, it should invalid and also + * verify the third blob, it should correct. + */ +static void +blob_dirty_shutdown(void) +{ + int rc; + int index; + struct spdk_bs_dev *dev; + spdk_blob_id blobid1, blobid2, blobid3; + struct spdk_blob *blob; + uint64_t length; + uint64_t free_clusters; + const void *value; + size_t value_len; + uint32_t page_num; + struct spdk_blob_md_page *page; + struct spdk_bs_opts opts; + + dev = init_dev(); + spdk_bs_opts_init(&opts); + /* Initialize a new blob store */ + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + /* Create first blob */ + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid1 = g_blobid; + + spdk_bs_open_blob(g_bs, blobid1, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + /* Set some xattrs */ + rc = spdk_blob_set_xattr(blob, "name", "log.txt", strlen("log.txt") + 1); + CU_ASSERT(rc == 0); + + length = 2345; + rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length)); + CU_ASSERT(rc == 0); + + /* Resize the blob */ + spdk_blob_resize(blob, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Set the blob as the super blob */ + spdk_bs_set_super(g_bs, blobid1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + free_clusters = spdk_bs_free_cluster_count(g_bs); + + spdk_blob_close(blob, blob_op_complete, NULL); + blob = NULL; + g_blob = NULL; + g_blobid = SPDK_BLOBID_INVALID; + + /* Dirty shutdown */ + _spdk_bs_free(g_bs); + + /* reload blobstore */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Get the super blob */ + spdk_bs_get_super(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(blobid1 == g_blobid); + + spdk_bs_open_blob(g_bs, blobid1, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(g_bs)); + + /* Get the xattrs */ + value = NULL; + rc = spdk_blob_get_xattr_value(blob, "length", &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(*(uint64_t *)value == length); + CU_ASSERT(value_len == 8); + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10); + + /* Resize the blob */ + spdk_blob_resize(blob, 20, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + free_clusters = spdk_bs_free_cluster_count(g_bs); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + blob = NULL; + g_blob = NULL; + g_blobid = SPDK_BLOBID_INVALID; + + /* Dirty shutdown */ + _spdk_bs_free(g_bs); + + /* reload the blobstore */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + /* Load an existing blob store */ + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + spdk_bs_open_blob(g_bs, blobid1, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 20); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(g_bs)); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + blob = NULL; + g_blob = NULL; + g_blobid = SPDK_BLOBID_INVALID; + + /* Create second blob */ + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid2 = g_blobid; + + spdk_bs_open_blob(g_bs, blobid2, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + /* Set some xattrs */ + rc = spdk_blob_set_xattr(blob, "name", "log1.txt", strlen("log1.txt") + 1); + CU_ASSERT(rc == 0); + + length = 5432; + rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length)); + CU_ASSERT(rc == 0); + + /* Resize the blob */ + spdk_blob_resize(blob, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + free_clusters = spdk_bs_free_cluster_count(g_bs); + + spdk_blob_close(blob, blob_op_complete, NULL); + blob = NULL; + g_blob = NULL; + g_blobid = SPDK_BLOBID_INVALID; + + /* Dirty shutdown */ + _spdk_bs_free(g_bs); + + /* reload the blobstore */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_open_blob(g_bs, blobid2, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + /* Get the xattrs */ + value = NULL; + rc = spdk_blob_get_xattr_value(blob, "length", &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(*(uint64_t *)value == length); + CU_ASSERT(value_len == 8); + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(g_bs)); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + spdk_bs_delete_blob(g_bs, blobid2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + free_clusters = spdk_bs_free_cluster_count(g_bs); + + /* Dirty shutdown */ + _spdk_bs_free(g_bs); + /* reload the blobstore */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_open_blob(g_bs, blobid2, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno != 0); + CU_ASSERT(g_blob == NULL); + + spdk_bs_open_blob(g_bs, blobid1, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(g_bs)); + spdk_blob_close(g_blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + /* reload the blobstore */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Create second blob */ + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid2 = g_blobid; + + /* Create third blob */ + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid3 = g_blobid; + + spdk_bs_open_blob(g_bs, blobid2, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + /* Set some xattrs for second blob */ + rc = spdk_blob_set_xattr(blob, "name", "log1.txt", strlen("log1.txt") + 1); + CU_ASSERT(rc == 0); + + length = 5432; + rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length)); + CU_ASSERT(rc == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + blob = NULL; + g_blob = NULL; + g_blobid = SPDK_BLOBID_INVALID; + + spdk_bs_open_blob(g_bs, blobid3, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + /* Set some xattrs for third blob */ + rc = spdk_blob_set_xattr(blob, "name", "log2.txt", strlen("log2.txt") + 1); + CU_ASSERT(rc == 0); + + length = 5432; + rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length)); + CU_ASSERT(rc == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + blob = NULL; + g_blob = NULL; + g_blobid = SPDK_BLOBID_INVALID; + + /* Mark second blob as invalid */ + page_num = _spdk_bs_blobid_to_page(blobid2); + + index = DEV_BUFFER_BLOCKLEN * (g_bs->md_start + page_num); + page = (struct spdk_blob_md_page *)&g_dev_buffer[index]; + page->sequence_num = 1; + page->crc = _spdk_blob_md_page_calc_crc(page); + + free_clusters = spdk_bs_free_cluster_count(g_bs); + + /* Dirty shutdown */ + _spdk_bs_free(g_bs); + /* reload the blobstore */ + dev = init_dev(); + spdk_bs_opts_init(&opts); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_open_blob(g_bs, blobid2, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno != 0); + CU_ASSERT(g_blob == NULL); + + spdk_bs_open_blob(g_bs, blobid3, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(g_bs)); + + spdk_blob_close(blob, blob_op_complete, NULL); + blob = NULL; + g_blob = NULL; + g_blobid = SPDK_BLOBID_INVALID; + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_flags(void) +{ + struct spdk_bs_dev *dev; + spdk_blob_id blobid_invalid, blobid_data_ro, blobid_md_ro; + struct spdk_blob *blob_invalid, *blob_data_ro, *blob_md_ro; + struct spdk_bs_opts opts; + int rc; + + dev = init_dev(); + spdk_bs_opts_init(&opts); + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + /* Create three blobs - one each for testing invalid, data_ro and md_ro flags. */ + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid_invalid = g_blobid; + + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid_data_ro = g_blobid; + + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid_md_ro = g_blobid; + + spdk_bs_open_blob(g_bs, blobid_invalid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob_invalid = g_blob; + + spdk_bs_open_blob(g_bs, blobid_data_ro, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob_data_ro = g_blob; + + spdk_bs_open_blob(g_bs, blobid_md_ro, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob_md_ro = g_blob; + + /* Change the size of blob_data_ro to check if flags are serialized + * when blob has non zero number of extents */ + spdk_blob_resize(blob_data_ro, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Set the xattr to check if flags are serialized + * when blob has non zero number of xattrs */ + rc = spdk_blob_set_xattr(blob_md_ro, "name", "log.txt", strlen("log.txt") + 1); + CU_ASSERT(rc == 0); + + blob_invalid->invalid_flags = (1ULL << 63); + blob_invalid->state = SPDK_BLOB_STATE_DIRTY; + blob_data_ro->data_ro_flags = (1ULL << 62); + blob_data_ro->state = SPDK_BLOB_STATE_DIRTY; + blob_md_ro->md_ro_flags = (1ULL << 61); + blob_md_ro->state = SPDK_BLOB_STATE_DIRTY; + + g_bserrno = -1; + spdk_blob_sync_md(blob_invalid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bserrno = -1; + spdk_blob_sync_md(blob_data_ro, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bserrno = -1; + spdk_blob_sync_md(blob_md_ro, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + g_bserrno = -1; + spdk_blob_close(blob_invalid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + blob_invalid = NULL; + g_bserrno = -1; + spdk_blob_close(blob_data_ro, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + blob_data_ro = NULL; + g_bserrno = -1; + spdk_blob_close(blob_md_ro, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + blob_md_ro = NULL; + + g_blob = NULL; + g_blobid = SPDK_BLOBID_INVALID; + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + /* Load an existing blob store */ + dev = init_dev(); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + g_blob = NULL; + g_bserrno = 0; + spdk_bs_open_blob(g_bs, blobid_invalid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno != 0); + CU_ASSERT(g_blob == NULL); + + g_blob = NULL; + g_bserrno = -1; + spdk_bs_open_blob(g_bs, blobid_data_ro, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob_data_ro = g_blob; + /* If an unknown data_ro flag was found, the blob should be marked both data and md read-only. */ + CU_ASSERT(blob_data_ro->data_ro == true); + CU_ASSERT(blob_data_ro->md_ro == true); + CU_ASSERT(spdk_blob_get_num_clusters(blob_data_ro) == 10); + + g_blob = NULL; + g_bserrno = -1; + spdk_bs_open_blob(g_bs, blobid_md_ro, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob_md_ro = g_blob; + CU_ASSERT(blob_md_ro->data_ro == false); + CU_ASSERT(blob_md_ro->md_ro == true); + + g_bserrno = -1; + spdk_blob_sync_md(blob_md_ro, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(blob_data_ro, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + spdk_blob_close(blob_md_ro, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); +} + +static void +bs_version(void) +{ + struct spdk_bs_super_block *super; + struct spdk_bs_dev *dev; + struct spdk_bs_opts opts; + spdk_blob_id blobid; + + dev = init_dev(); + spdk_bs_opts_init(&opts); + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + /* + * Change the bs version on disk. This will allow us to + * test that the version does not get modified automatically + * when loading and unloading the blobstore. + */ + super = (struct spdk_bs_super_block *)&g_dev_buffer[0]; + CU_ASSERT(super->version == SPDK_BS_VERSION); + CU_ASSERT(super->clean == 1); + super->version = 2; + /* + * Version 2 metadata does not have a used blobid mask, so clear + * those fields in the super block and zero the corresponding + * region on "disk". We will use this to ensure blob IDs are + * correctly reconstructed. + */ + memset(&g_dev_buffer[super->used_blobid_mask_start * SPDK_BS_PAGE_SIZE], 0, + super->used_blobid_mask_len * SPDK_BS_PAGE_SIZE); + super->used_blobid_mask_start = 0; + super->used_blobid_mask_len = 0; + super->crc = _spdk_blob_md_page_calc_crc(super); + + /* Load an existing blob store */ + dev = init_dev(); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + CU_ASSERT(super->clean == 1); + + /* + * Create a blob - just to make sure that when we unload it + * results in writing the super block (since metadata pages + * were allocated. + */ + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + CU_ASSERT(super->version == 2); + CU_ASSERT(super->used_blobid_mask_start == 0); + CU_ASSERT(super->used_blobid_mask_len == 0); + + dev = init_dev(); + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + g_blob = NULL; + spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + + spdk_blob_close(g_blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + CU_ASSERT(super->version == 2); + CU_ASSERT(super->used_blobid_mask_start == 0); + CU_ASSERT(super->used_blobid_mask_len == 0); +} + +static void +blob_set_xattrs(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_blob_opts opts; + spdk_blob_id blobid; + const void *value; + size_t value_len; + int rc; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + /* Create blob with extra attributes */ + spdk_blob_opts_init(&opts); + + opts.xattrs.names = g_xattr_names; + opts.xattrs.get_value = _get_xattr_value; + opts.xattrs.count = 3; + opts.xattrs.ctx = &g_ctx; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + /* Get the xattrs */ + value = NULL; + + rc = spdk_blob_get_xattr_value(blob, g_xattr_names[0], &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[0])); + CU_ASSERT_NSTRING_EQUAL_FATAL(value, g_xattr_values[0], value_len); + + rc = spdk_blob_get_xattr_value(blob, g_xattr_names[1], &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[1])); + CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[1], value_len); + + rc = spdk_blob_get_xattr_value(blob, g_xattr_names[2], &value, &value_len); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(value_len == strlen(g_xattr_values[2])); + CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[2], value_len); + + /* Try to get non existing attribute */ + + rc = spdk_blob_get_xattr_value(blob, "foobar", &value, &value_len); + CU_ASSERT(rc == -ENOENT); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + blob = NULL; + g_blob = NULL; + g_blobid = SPDK_BLOBID_INVALID; + + /* NULL callback */ + spdk_blob_opts_init(&opts); + opts.xattrs.names = g_xattr_names; + opts.xattrs.get_value = NULL; + opts.xattrs.count = 1; + opts.xattrs.ctx = &g_ctx; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + + /* NULL values */ + spdk_blob_opts_init(&opts); + opts.xattrs.names = g_xattr_names; + opts.xattrs.get_value = _get_xattr_value_null; + opts.xattrs.count = 1; + opts.xattrs.ctx = NULL; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == -EINVAL); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + +} + +static void +blob_thin_prov_alloc(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_blob_opts opts; + spdk_blob_id blobid; + uint64_t free_clusters; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + free_clusters = spdk_bs_free_cluster_count(bs); + + /* Set blob as thin provisioned */ + spdk_blob_opts_init(&opts); + opts.thin_provision = true; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(blob->active.num_clusters == 0); + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 0); + + /* The blob started at 0 clusters. Resize it to be 5, but still unallocated. */ + spdk_blob_resize(blob, 5, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + CU_ASSERT(blob->active.num_clusters == 5); + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5); + + /* Grow it to 1TB - still unallocated */ + spdk_blob_resize(blob, 262144, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + CU_ASSERT(blob->active.num_clusters == 262144); + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 262144); + + spdk_blob_sync_md(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + /* Sync must not change anything */ + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + CU_ASSERT(blob->active.num_clusters == 262144); + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 262144); + /* Since clusters are not allocated, + * number of metadata pages is expected to be minimal. + */ + CU_ASSERT(blob->active.num_pages == 1); + + /* Shrink the blob to 3 clusters - still unallocated */ + spdk_blob_resize(blob, 3, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + CU_ASSERT(blob->active.num_clusters == 3); + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 3); + + spdk_blob_sync_md(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + /* Sync must not change anything */ + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + CU_ASSERT(blob->active.num_clusters == 3); + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 3); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + /* Load an existing blob store */ + dev = init_dev(); + spdk_bs_load(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + bs = g_bs; + + spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + /* Check that clusters allocation and size is still the same */ + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + CU_ASSERT(blob->active.num_clusters == 3); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_insert_cluster_msg(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_blob_opts opts; + spdk_blob_id blobid; + uint64_t free_clusters; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + free_clusters = spdk_bs_free_cluster_count(bs); + + /* Set blob as thin provisioned */ + spdk_blob_opts_init(&opts); + opts.thin_provision = true; + opts.num_clusters = 4; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(blob->active.num_clusters == 4); + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 4); + CU_ASSERT(blob->active.clusters[1] == 0); + + _spdk_bs_claim_cluster(bs, 0xF); + _spdk_blob_insert_cluster_on_md_thread(blob, 1, 0xF, blob_op_complete, NULL); + + CU_ASSERT(blob->active.clusters[1] != 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + /* Load an existing blob store */ + dev = init_dev(); + spdk_bs_load(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + bs = g_bs; + + spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(blob->active.clusters[1] != 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_thin_prov_rw(void) +{ + static const uint8_t zero[10 * 4096] = { 0 }; + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_io_channel *channel; + struct spdk_blob_opts opts; + spdk_blob_id blobid; + uint64_t free_clusters; + uint64_t page_size; + uint8_t payload_read[10 * 4096]; + uint8_t payload_write[10 * 4096]; + uint64_t write_bytes; + uint64_t read_bytes; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + free_clusters = spdk_bs_free_cluster_count(bs); + page_size = spdk_bs_get_page_size(bs); + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_blob_opts_init(&opts); + opts.thin_provision = true; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(blob->active.num_clusters == 0); + + /* The blob started at 0 clusters. Resize it to be 5, but still unallocated. */ + spdk_blob_resize(blob, 5, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + CU_ASSERT(blob->active.num_clusters == 5); + + spdk_blob_sync_md(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + /* Sync must not change anything */ + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + CU_ASSERT(blob->active.num_clusters == 5); + + /* Payload should be all zeros from unallocated clusters */ + memset(payload_read, 0xFF, sizeof(payload_read)); + spdk_blob_io_read(blob, channel, payload_read, 4, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(zero, payload_read, 10 * 4096) == 0); + + write_bytes = g_dev_write_bytes; + read_bytes = g_dev_read_bytes; + + memset(payload_write, 0xE5, sizeof(payload_write)); + spdk_blob_io_write(blob, channel, payload_write, 4, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs)); + /* For thin-provisioned blob we need to write 10 pages plus one page metadata and + * read 0 bytes */ + CU_ASSERT(g_dev_write_bytes - write_bytes == page_size * 11); + CU_ASSERT(g_dev_read_bytes - read_bytes == 0); + + spdk_blob_io_read(blob, channel, payload_read, 4, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + + spdk_bs_free_io_channel(channel); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; +} + +static void +blob_thin_prov_rw_iov(void) +{ + static const uint8_t zero[10 * 4096] = { 0 }; + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob; + struct spdk_io_channel *channel; + struct spdk_blob_opts opts; + spdk_blob_id blobid; + uint64_t free_clusters; + uint8_t payload_read[10 * 4096]; + uint8_t payload_write[10 * 4096]; + struct iovec iov_read[3]; + struct iovec iov_write[3]; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + free_clusters = spdk_bs_free_cluster_count(bs); + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_blob_opts_init(&opts); + opts.thin_provision = true; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(blob->active.num_clusters == 0); + + /* The blob started at 0 clusters. Resize it to be 5, but still unallocated. */ + spdk_blob_resize(blob, 5, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + CU_ASSERT(blob->active.num_clusters == 5); + + spdk_blob_sync_md(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + /* Sync must not change anything */ + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + CU_ASSERT(blob->active.num_clusters == 5); + + /* Payload should be all zeros from unallocated clusters */ + memset(payload_read, 0xAA, sizeof(payload_read)); + iov_read[0].iov_base = payload_read; + iov_read[0].iov_len = 3 * 4096; + iov_read[1].iov_base = payload_read + 3 * 4096; + iov_read[1].iov_len = 4 * 4096; + iov_read[2].iov_base = payload_read + 7 * 4096; + iov_read[2].iov_len = 3 * 4096; + spdk_blob_io_readv(blob, channel, iov_read, 3, 250, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(zero, payload_read, 10 * 4096) == 0); + + memset(payload_write, 0xE5, sizeof(payload_write)); + iov_write[0].iov_base = payload_write; + iov_write[0].iov_len = 1 * 4096; + iov_write[1].iov_base = payload_write + 1 * 4096; + iov_write[1].iov_len = 5 * 4096; + iov_write[2].iov_base = payload_write + 6 * 4096; + iov_write[2].iov_len = 4 * 4096; + + spdk_blob_io_writev(blob, channel, iov_write, 3, 250, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + memset(payload_read, 0xAA, sizeof(payload_read)); + iov_read[0].iov_base = payload_read; + iov_read[0].iov_len = 3 * 4096; + iov_read[1].iov_base = payload_read + 3 * 4096; + iov_read[1].iov_len = 4 * 4096; + iov_read[2].iov_base = payload_read + 7 * 4096; + iov_read[2].iov_len = 3 * 4096; + spdk_blob_io_readv(blob, channel, iov_read, 3, 250, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; +} + +struct iter_ctx { + int current_iter; + spdk_blob_id blobid[4]; +}; + +static void +test_iter(void *arg, struct spdk_blob *blob, int bserrno) +{ + struct iter_ctx *iter_ctx = arg; + spdk_blob_id blobid; + + CU_ASSERT(bserrno == 0); + blobid = spdk_blob_get_id(blob); + CU_ASSERT(blobid == iter_ctx->blobid[iter_ctx->current_iter++]); +} + +static void +bs_load_iter(void) +{ + struct spdk_bs_dev *dev; + struct iter_ctx iter_ctx = { 0 }; + struct spdk_blob *blob; + int i, rc; + struct spdk_bs_opts opts; + + dev = init_dev(); + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + + /* Initialize a new blob store */ + spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + for (i = 0; i < 4; i++) { + g_bserrno = -1; + g_blobid = SPDK_BLOBID_INVALID; + spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + iter_ctx.blobid[i] = g_blobid; + + g_bserrno = -1; + g_blob = NULL; + spdk_bs_open_blob(g_bs, g_blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + /* Just save the blobid as an xattr for testing purposes. */ + rc = spdk_blob_set_xattr(blob, "blobid", &g_blobid, sizeof(g_blobid)); + CU_ASSERT(rc == 0); + + /* Resize the blob */ + spdk_blob_resize(blob, i, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + } + + g_bserrno = -1; + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + dev = init_dev(); + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + opts.iter_cb_fn = test_iter; + opts.iter_cb_arg = &iter_ctx; + + /* Test blob iteration during load after a clean shutdown. */ + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + /* Dirty shutdown */ + _spdk_bs_free(g_bs); + + dev = init_dev(); + spdk_bs_opts_init(&opts); + snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE"); + opts.iter_cb_fn = test_iter; + iter_ctx.current_iter = 0; + opts.iter_cb_arg = &iter_ctx; + + /* Test blob iteration during load after a dirty shutdown. */ + spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; +} + +static void +blob_snapshot_rw(void) +{ + static const uint8_t zero[10 * 4096] = { 0 }; + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob, *snapshot; + struct spdk_io_channel *channel; + struct spdk_blob_opts opts; + spdk_blob_id blobid, snapshotid; + uint64_t free_clusters; + uint64_t cluster_size; + uint64_t page_size; + uint8_t payload_read[10 * 4096]; + uint8_t payload_write[10 * 4096]; + uint64_t write_bytes; + uint64_t read_bytes; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + free_clusters = spdk_bs_free_cluster_count(bs); + cluster_size = spdk_bs_get_cluster_size(bs); + page_size = spdk_bs_get_page_size(bs); + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_blob_opts_init(&opts); + opts.thin_provision = true; + opts.num_clusters = 5; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5); + + memset(payload_read, 0xFF, sizeof(payload_read)); + spdk_blob_io_read(blob, channel, payload_read, 4, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(zero, payload_read, 10 * 4096) == 0); + + memset(payload_write, 0xE5, sizeof(payload_write)); + spdk_blob_io_write(blob, channel, payload_write, 4, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs)); + + /* Create snapshot from blob */ + spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + snapshotid = g_blobid; + + spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot = g_blob; + CU_ASSERT(snapshot->data_ro == true) + CU_ASSERT(snapshot->md_ro == true) + + CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 5) + + write_bytes = g_dev_write_bytes; + read_bytes = g_dev_read_bytes; + + memset(payload_write, 0xAA, sizeof(payload_write)); + spdk_blob_io_write(blob, channel, payload_write, 4, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs)); + + /* For a clone we need to allocate and copy one cluster, update one page of metadata + * and then write 10 pages of payload. + */ + CU_ASSERT(g_dev_write_bytes - write_bytes == page_size * 11 + cluster_size); + CU_ASSERT(g_dev_read_bytes - read_bytes == cluster_size); + + spdk_blob_io_read(blob, channel, payload_read, 4, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0); + + /* Data on snapshot should not change after write to clone */ + memset(payload_write, 0xE5, sizeof(payload_write)); + spdk_blob_io_read(snapshot, channel, payload_read, 4, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(snapshot, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; +} + +static void +blob_snapshot_rw_iov(void) +{ + static const uint8_t zero[10 * 4096] = { 0 }; + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob, *snapshot; + struct spdk_io_channel *channel; + struct spdk_blob_opts opts; + spdk_blob_id blobid, snapshotid; + uint64_t free_clusters; + uint8_t payload_read[10 * 4096]; + uint8_t payload_write[10 * 4096]; + struct iovec iov_read[3]; + struct iovec iov_write[3]; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + free_clusters = spdk_bs_free_cluster_count(bs); + + channel = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(channel != NULL); + + spdk_blob_opts_init(&opts); + opts.thin_provision = true; + opts.num_clusters = 5; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5); + + /* Create snapshot from blob */ + spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + snapshotid = g_blobid; + + spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot = g_blob; + CU_ASSERT(snapshot->data_ro == true) + CU_ASSERT(snapshot->md_ro == true) + CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 5); + + /* Payload should be all zeros from unallocated clusters */ + memset(payload_read, 0xAA, sizeof(payload_read)); + iov_read[0].iov_base = payload_read; + iov_read[0].iov_len = 3 * 4096; + iov_read[1].iov_base = payload_read + 3 * 4096; + iov_read[1].iov_len = 4 * 4096; + iov_read[2].iov_base = payload_read + 7 * 4096; + iov_read[2].iov_len = 3 * 4096; + spdk_blob_io_readv(blob, channel, iov_read, 3, 250, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(zero, payload_read, 10 * 4096) == 0); + + memset(payload_write, 0xE5, sizeof(payload_write)); + iov_write[0].iov_base = payload_write; + iov_write[0].iov_len = 1 * 4096; + iov_write[1].iov_base = payload_write + 1 * 4096; + iov_write[1].iov_len = 5 * 4096; + iov_write[2].iov_base = payload_write + 6 * 4096; + iov_write[2].iov_len = 4 * 4096; + + spdk_blob_io_writev(blob, channel, iov_write, 3, 250, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + memset(payload_read, 0xAA, sizeof(payload_read)); + iov_read[0].iov_base = payload_read; + iov_read[0].iov_len = 3 * 4096; + iov_read[1].iov_base = payload_read + 3 * 4096; + iov_read[1].iov_len = 4 * 4096; + iov_read[2].iov_base = payload_read + 7 * 4096; + iov_read[2].iov_len = 3 * 4096; + spdk_blob_io_readv(blob, channel, iov_read, 3, 250, 10, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(snapshot, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; +} + +/** + * Inflate / decouple parent rw unit tests. + * + * -------------- + * original blob: 0 1 2 3 4 + * ,---------+---------+---------+---------+---------. + * snapshot |xxxxxxxxx|xxxxxxxxx|xxxxxxxxx|xxxxxxxxx| - | + * +---------+---------+---------+---------+---------+ + * snapshot2 | - |yyyyyyyyy| - |yyyyyyyyy| - | + * +---------+---------+---------+---------+---------+ + * blob | - |zzzzzzzzz| - | - | - | + * '---------+---------+---------+---------+---------' + * . . . . . . + * -------- . . . . . . + * inflate: . . . . . . + * ,---------+---------+---------+---------+---------. + * blob |xxxxxxxxx|zzzzzzzzz|xxxxxxxxx|yyyyyyyyy|000000000| + * '---------+---------+---------+---------+---------' + * + * NOTE: needs to allocate 4 clusters, thin provisioning removed, dependency + * on snapshot2 and snapshot removed . . . + * . . . . . . + * ---------------- . . . . . . + * decouple parent: . . . . . . + * ,---------+---------+---------+---------+---------. + * snapshot |xxxxxxxxx|xxxxxxxxx|xxxxxxxxx|xxxxxxxxx| - | + * +---------+---------+---------+---------+---------+ + * blob | - |zzzzzzzzz| - |yyyyyyyyy| - | + * '---------+---------+---------+---------+---------' + * + * NOTE: needs to allocate 1 cluster, 3 clusters unallocated, dependency + * on snapshot2 removed and on snapshot still exists. Snapshot2 + * should remain a clone of snapshot. + */ +static void +_blob_inflate_rw(bool decouple_parent) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob, *snapshot, *snapshot2; + struct spdk_io_channel *channel; + struct spdk_blob_opts opts; + spdk_blob_id blobid, snapshotid, snapshot2id; + uint64_t free_clusters; + uint64_t cluster_size; + + uint64_t payload_size; + uint8_t *payload_read; + uint8_t *payload_write; + uint8_t *payload_clone; + + uint64_t pages_per_cluster; + uint64_t pages_per_payload; + + int i; + spdk_blob_id ids[2]; + size_t count; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + free_clusters = spdk_bs_free_cluster_count(bs); + cluster_size = spdk_bs_get_cluster_size(bs); + pages_per_cluster = cluster_size / spdk_bs_get_page_size(bs); + pages_per_payload = pages_per_cluster * 5; + + payload_size = cluster_size * 5; + + payload_read = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_read != NULL); + + payload_write = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_write != NULL); + + payload_clone = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_clone != NULL); + + channel = spdk_bs_alloc_io_channel(bs); + SPDK_CU_ASSERT_FATAL(channel != NULL); + + /* Create blob */ + spdk_blob_opts_init(&opts); + opts.thin_provision = true; + opts.num_clusters = 5; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5); + + /* 1) Initial read should return zeroed payload */ + memset(payload_read, 0xFF, payload_size); + spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(spdk_mem_all_zero(payload_read, payload_size)); + + /* Fill whole blob with a pattern, except last cluster (to be sure it + * isn't allocated) */ + memset(payload_write, 0xE5, payload_size - cluster_size); + spdk_blob_io_write(blob, channel, payload_write, 0, pages_per_payload - + pages_per_cluster, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs)); + + /* 2) Create snapshot from blob (first level) */ + spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + snapshotid = g_blobid; + + spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot = g_blob; + CU_ASSERT(snapshot->data_ro == true) + CU_ASSERT(snapshot->md_ro == true) + + CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 5) + + /* Write every second cluster with a pattern. + * + * Last cluster shouldn't be written, to be sure that snapshot nor clone + * doesn't allocate it. + * + * payload_clone stores expected result on "blob" read at the time and + * is used only to check data consistency on clone before and after + * inflation. Initially we fill it with a backing snapshots pattern + * used before. + */ + memset(payload_clone, 0xE5, payload_size - cluster_size); + memset(payload_clone + payload_size - cluster_size, 0x00, cluster_size); + memset(payload_write, 0xAA, payload_size); + for (i = 1; i < 5; i += 2) { + spdk_blob_io_write(blob, channel, payload_write, i * pages_per_cluster, + pages_per_cluster, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Update expected result */ + memcpy(payload_clone + (cluster_size * i), payload_write, + cluster_size); + } + CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs)); + + /* Check data consistency on clone */ + memset(payload_read, 0xFF, payload_size); + spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_clone, payload_read, payload_size) == 0); + + /* 3) Create second levels snapshot from blob */ + spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + snapshot2id = g_blobid; + + spdk_bs_open_blob(bs, snapshot2id, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot2 = g_blob; + CU_ASSERT(snapshot2->data_ro == true) + CU_ASSERT(snapshot2->md_ro == true) + + CU_ASSERT(spdk_blob_get_num_clusters(snapshot2) == 5) + + CU_ASSERT(snapshot2->parent_id == snapshotid); + + /* Write one cluster on the top level blob. This cluster (1) covers + * already allocated cluster in the snapshot2, so shouldn't be inflated + * at all */ + spdk_blob_io_write(blob, channel, payload_write, pages_per_cluster, + pages_per_cluster, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Update expected result */ + memcpy(payload_clone + cluster_size, payload_write, cluster_size); + + /* Check data consistency on clone */ + memset(payload_read, 0xFF, payload_size); + spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_clone, payload_read, payload_size) == 0); + + + /* Close all blobs */ + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(snapshot2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(snapshot, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Check snapshot-clone relations */ + count = 2; + CU_ASSERT(spdk_blob_get_clones(bs, snapshotid, ids, &count) == 0); + CU_ASSERT(count == 1); + CU_ASSERT(ids[0] == snapshot2id); + + count = 2; + CU_ASSERT(spdk_blob_get_clones(bs, snapshot2id, ids, &count) == 0); + CU_ASSERT(count == 1); + CU_ASSERT(ids[0] == blobid); + + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshot2id); + + free_clusters = spdk_bs_free_cluster_count(bs); + if (!decouple_parent) { + /* Do full blob inflation */ + spdk_bs_inflate_blob(bs, channel, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* All clusters should be inflated (except one already allocated + * in a top level blob) */ + CU_ASSERT(spdk_bs_free_cluster_count(bs) == free_clusters - 4); + + /* Check if relation tree updated correctly */ + count = 2; + CU_ASSERT(spdk_blob_get_clones(bs, snapshotid, ids, &count) == 0); + + /* snapshotid have one clone */ + CU_ASSERT(count == 1); + CU_ASSERT(ids[0] == snapshot2id); + + /* snapshot2id have no clones */ + count = 2; + CU_ASSERT(spdk_blob_get_clones(bs, snapshot2id, ids, &count) == 0); + CU_ASSERT(count == 0); + + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == SPDK_BLOBID_INVALID); + } else { + /* Decouple parent of blob */ + spdk_bs_blob_decouple_parent(bs, channel, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Only one cluster from a parent should be inflated (second one + * is covered by a cluster written on a top level blob, and + * already allocated) */ + CU_ASSERT(spdk_bs_free_cluster_count(bs) == free_clusters - 1); + + /* Check if relation tree updated correctly */ + count = 2; + CU_ASSERT(spdk_blob_get_clones(bs, snapshotid, ids, &count) == 0); + + /* snapshotid have two clones now */ + CU_ASSERT(count == 2); + CU_ASSERT(ids[0] == blobid || ids[1] == blobid); + CU_ASSERT(ids[0] == snapshot2id || ids[1] == snapshot2id); + + /* snapshot2id have no clones */ + count = 2; + CU_ASSERT(spdk_blob_get_clones(bs, snapshot2id, ids, &count) == 0); + CU_ASSERT(count == 0); + + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshotid); + } + + /* Try to delete snapshot2 (should pass) */ + spdk_bs_delete_blob(bs, snapshot2id, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Try to delete base snapshot (for decouple_parent should fail while + * dependency still exists) */ + spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL); + CU_ASSERT(decouple_parent || g_bserrno == 0); + CU_ASSERT(!decouple_parent || g_bserrno != 0); + + /* Reopen blob after snapshot deletion */ + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5); + + /* Check data consistency on inflated blob */ + memset(payload_read, 0xFF, payload_size); + spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_clone, payload_read, payload_size) == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + free(payload_read); + free(payload_write); + free(payload_clone); +} + +static void +blob_inflate_rw(void) +{ + _blob_inflate_rw(false); + _blob_inflate_rw(true); +} + +/** + * Snapshot-clones relation test + * + * snapshot + * | + * +-----+-----+ + * | | + * blob(ro) snapshot2 + * | | + * clone2 clone + */ +static void +blob_relations(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_bs_opts bs_opts; + struct spdk_blob_opts opts; + struct spdk_blob *blob, *snapshot, *snapshot2, *clone, *clone2; + spdk_blob_id blobid, cloneid, snapshotid, cloneid2, snapshotid2; + int rc; + size_t count; + spdk_blob_id ids[10] = {}; + + dev = init_dev(); + spdk_bs_opts_init(&bs_opts); + snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "TESTTYPE"); + + spdk_bs_init(dev, &bs_opts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + /* 1. Create blob with 10 clusters */ + + spdk_blob_opts_init(&opts); + opts.num_clusters = 10; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(!spdk_blob_is_read_only(blob)); + CU_ASSERT(!spdk_blob_is_snapshot(blob)); + CU_ASSERT(!spdk_blob_is_clone(blob)); + CU_ASSERT(!spdk_blob_is_thin_provisioned(blob)); + + /* blob should not have underlying snapshot nor clones */ + CU_ASSERT(blob->parent_id == SPDK_BLOBID_INVALID); + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == SPDK_BLOBID_INVALID); + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, blobid, ids, &count); + CU_ASSERT(rc == 0); + CU_ASSERT(count == 0); + + + /* 2. Create snapshot */ + + spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + snapshotid = g_blobid; + + spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot = g_blob; + + CU_ASSERT(spdk_blob_is_read_only(snapshot)); + CU_ASSERT(spdk_blob_is_snapshot(snapshot)); + CU_ASSERT(!spdk_blob_is_clone(snapshot)); + CU_ASSERT(snapshot->parent_id == SPDK_BLOBID_INVALID); + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid) == SPDK_BLOBID_INVALID); + + /* Check if original blob is converted to the clone of snapshot */ + CU_ASSERT(!spdk_blob_is_read_only(blob)); + CU_ASSERT(!spdk_blob_is_snapshot(blob)); + CU_ASSERT(spdk_blob_is_clone(blob)); + CU_ASSERT(spdk_blob_is_thin_provisioned(blob)); + CU_ASSERT(blob->parent_id == snapshotid); + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshotid); + + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, snapshotid, ids, &count); + CU_ASSERT(rc == 0); + CU_ASSERT(count == 1); + CU_ASSERT(ids[0] == blobid); + + + /* 3. Create clone from snapshot */ + + spdk_bs_create_clone(bs, snapshotid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + cloneid = g_blobid; + + spdk_bs_open_blob(bs, cloneid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + clone = g_blob; + + CU_ASSERT(!spdk_blob_is_read_only(clone)); + CU_ASSERT(!spdk_blob_is_snapshot(clone)); + CU_ASSERT(spdk_blob_is_clone(clone)); + CU_ASSERT(spdk_blob_is_thin_provisioned(clone)); + CU_ASSERT(clone->parent_id == snapshotid); + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid) == snapshotid); + + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, cloneid, ids, &count); + CU_ASSERT(rc == 0); + CU_ASSERT(count == 0); + + /* Check if clone is on the snapshot's list */ + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, snapshotid, ids, &count); + CU_ASSERT(rc == 0); + CU_ASSERT(ids[0] == blobid || ids[1] == blobid); + CU_ASSERT(ids[0] == cloneid || ids[1] == cloneid); + + + /* 4. Create snapshot of the clone */ + + spdk_bs_create_snapshot(bs, cloneid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + snapshotid2 = g_blobid; + + spdk_bs_open_blob(bs, snapshotid2, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot2 = g_blob; + + CU_ASSERT(spdk_blob_is_read_only(snapshot2)); + CU_ASSERT(spdk_blob_is_snapshot(snapshot2)); + CU_ASSERT(spdk_blob_is_clone(snapshot2)); + CU_ASSERT(snapshot2->parent_id == snapshotid); + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid2) == snapshotid); + + /* Check if clone is converted to the clone of snapshot2 and snapshot2 + * is a child of snapshot */ + CU_ASSERT(!spdk_blob_is_read_only(clone)); + CU_ASSERT(!spdk_blob_is_snapshot(clone)); + CU_ASSERT(spdk_blob_is_clone(clone)); + CU_ASSERT(spdk_blob_is_thin_provisioned(clone)); + CU_ASSERT(clone->parent_id == snapshotid2); + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid) == snapshotid2); + + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, snapshotid2, ids, &count); + CU_ASSERT(rc == 0); + CU_ASSERT(count == 1); + CU_ASSERT(ids[0] == cloneid); + + + /* 5. Try to create clone from read only blob */ + + /* Mark blob as read only */ + spdk_blob_set_read_only(blob); + spdk_blob_sync_md(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Check if previously created blob is read only clone */ + CU_ASSERT(spdk_blob_is_read_only(blob)); + CU_ASSERT(!spdk_blob_is_snapshot(blob)); + CU_ASSERT(spdk_blob_is_clone(blob)); + CU_ASSERT(spdk_blob_is_thin_provisioned(blob)); + + /* Create clone from read only blob */ + spdk_bs_create_clone(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + cloneid2 = g_blobid; + + spdk_bs_open_blob(bs, cloneid2, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + clone2 = g_blob; + + CU_ASSERT(!spdk_blob_is_read_only(clone2)); + CU_ASSERT(!spdk_blob_is_snapshot(clone2)); + CU_ASSERT(spdk_blob_is_clone(clone2)); + CU_ASSERT(spdk_blob_is_thin_provisioned(clone2)); + + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid2) == blobid); + + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, blobid, ids, &count); + CU_ASSERT(rc == 0); + + CU_ASSERT(count == 1); + CU_ASSERT(ids[0] == cloneid2); + + /* Close blobs */ + + spdk_blob_close(clone2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(clone, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(snapshot, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(snapshot2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Try to delete snapshot with created clones */ + spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno != 0); + + spdk_bs_delete_blob(bs, snapshotid2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno != 0); + + spdk_bs_unload(bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + /* Load an existing blob store */ + dev = init_dev(); + snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "TESTTYPE"); + + spdk_bs_load(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + + /* NULL ids array should return number of clones in count */ + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, snapshotid, NULL, &count); + CU_ASSERT(rc == -ENOMEM); + CU_ASSERT(count == 2); + + /* incorrect array size */ + count = 1; + rc = spdk_blob_get_clones(bs, snapshotid, ids, &count); + CU_ASSERT(rc == -ENOMEM); + CU_ASSERT(count == 2); + + + /* Verify structure of loaded blob store */ + + /* snapshot */ + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid) == SPDK_BLOBID_INVALID); + + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, snapshotid, ids, &count); + CU_ASSERT(rc == 0); + CU_ASSERT(count == 2); + CU_ASSERT(ids[0] == blobid || ids[1] == blobid); + CU_ASSERT(ids[0] == snapshotid2 || ids[1] == snapshotid2); + + /* blob */ + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshotid); + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, blobid, ids, &count); + CU_ASSERT(rc == 0); + CU_ASSERT(count == 1); + CU_ASSERT(ids[0] == cloneid2); + + /* clone */ + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid) == snapshotid2); + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, cloneid, ids, &count); + CU_ASSERT(rc == 0); + CU_ASSERT(count == 0); + + /* snapshot2 */ + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid2) == snapshotid); + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, snapshotid2, ids, &count); + CU_ASSERT(rc == 0); + CU_ASSERT(count == 1); + CU_ASSERT(ids[0] == cloneid); + + /* clone2 */ + CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid2) == blobid); + count = SPDK_COUNTOF(ids); + rc = spdk_blob_get_clones(bs, cloneid2, ids, &count); + CU_ASSERT(rc == 0); + CU_ASSERT(count == 0); + + /* Try to delete all blobs in the worse possible order */ + + spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno != 0); + + spdk_bs_delete_blob(bs, snapshotid2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno != 0); + + spdk_bs_delete_blob(bs, cloneid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_delete_blob(bs, snapshotid2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno != 0); + + spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno != 0); + + spdk_bs_delete_blob(bs, cloneid2, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + g_bs = NULL; +} + +static void +test_io_write(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel) +{ + uint8_t payload_ff[64 * 512]; + uint8_t payload_aa[64 * 512]; + uint8_t payload_00[64 * 512]; + uint8_t *cluster0, *cluster1; + + memset(payload_ff, 0xFF, sizeof(payload_ff)); + memset(payload_aa, 0xAA, sizeof(payload_aa)); + memset(payload_00, 0x00, sizeof(payload_00)); + + /* Try to perform I/O with io unit = 512 */ + spdk_blob_io_write(blob, channel, payload_ff, 0, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* If thin provisioned is set cluster should be allocated now */ + SPDK_CU_ASSERT_FATAL(blob->active.clusters[0] != 0); + cluster0 = &g_dev_buffer[blob->active.clusters[0] * dev->blocklen]; + + /* Each character 0-F symbolizes single io_unit containing 512 bytes block filled with that character. + * Each page is separated by |. Whole block [...] symbolizes one cluster (containing 4 pages). */ + /* cluster0: [ F000 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 31 * 512) == 0); + + /* Verify write with offset on first page */ + spdk_blob_io_write(blob, channel, payload_ff, 2, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* cluster0: [ F0F0 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_00, 28 * 512) == 0); + + /* Verify write with offset on first page */ + spdk_blob_io_write(blob, channel, payload_ff, 4, 4, blob_op_complete, NULL); + + /* cluster0: [ F0F0 FFFF | 0000 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_ff, 4 * 512) == 0); + CU_ASSERT(memcmp(cluster0 + 8 * 512, payload_00, 24 * 512) == 0); + + /* Verify write with offset on second page */ + spdk_blob_io_write(blob, channel, payload_ff, 8, 4, blob_op_complete, NULL); + + /* cluster0: [ F0F0 FFFF | FFFF 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_ff, 8 * 512) == 0); + CU_ASSERT(memcmp(cluster0 + 12 * 512, payload_00, 20 * 512) == 0); + + /* Verify write across multiple pages */ + spdk_blob_io_write(blob, channel, payload_aa, 4, 8, blob_op_complete, NULL); + + /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0); + CU_ASSERT(memcmp(cluster0 + 12 * 512, payload_00, 20 * 512) == 0); + + /* Verify write across multiple clusters */ + spdk_blob_io_write(blob, channel, payload_ff, 28, 8, blob_op_complete, NULL); + + SPDK_CU_ASSERT_FATAL(blob->active.clusters[1] != 0); + cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen]; + + /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0); + CU_ASSERT(memcmp(cluster0 + 28 * 512, payload_ff, 4 * 512) == 0); + + CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_ff, 4 * 512) == 0); + CU_ASSERT(memcmp(cluster1 + 4 * 512, payload_00, 28 * 512) == 0); + + /* Verify write to second cluster */ + spdk_blob_io_write(blob, channel, payload_ff, 32 + 12, 2, blob_op_complete, NULL); + + SPDK_CU_ASSERT_FATAL(blob->active.clusters[1] != 0); + cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen]; + + /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0); + CU_ASSERT(memcmp(cluster0 + 28 * 512, payload_ff, 4 * 512) == 0); + + CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_ff, 4 * 512) == 0); + CU_ASSERT(memcmp(cluster1 + 4 * 512, payload_00, 8 * 512) == 0); + CU_ASSERT(memcmp(cluster1 + 12 * 512, payload_ff, 2 * 512) == 0); + CU_ASSERT(memcmp(cluster1 + 14 * 512, payload_00, 18 * 512) == 0); +} + +static void +test_io_read(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel) +{ + uint8_t payload_read[64 * 512]; + uint8_t payload_ff[64 * 512]; + uint8_t payload_aa[64 * 512]; + uint8_t payload_00[64 * 512]; + + memset(payload_ff, 0xFF, sizeof(payload_ff)); + memset(payload_aa, 0xAA, sizeof(payload_aa)); + memset(payload_00, 0x00, sizeof(payload_00)); + + /* Read only first io unit */ + /* cluster0: [ (F)0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] + * payload_read: F000 0000 | 0000 0000 ... */ + memset(payload_read, 0x00, sizeof(payload_read)); + spdk_blob_io_read(blob, channel, payload_read, 0, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 31 * 512) == 0); + + /* Read four io_units starting from offset = 2 + * cluster0: [ F0(F0 AA)AA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] + * payload_read: F0AA 0000 | 0000 0000 ... */ + + memset(payload_read, 0x00, sizeof(payload_read)); + spdk_blob_io_read(blob, channel, payload_read, 2, 4, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(payload_read + 2 * 512, payload_aa, 512) == 0); + CU_ASSERT(memcmp(payload_read + 3 * 512, payload_aa, 512) == 0); + CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 28 * 512) == 0); + + /* Read eight io_units across multiple pages + * cluster0: [ F0F0 (AAAA | AAAA) 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] + * payload_read: AAAA AAAA | 0000 0000 ... */ + memset(payload_read, 0x00, sizeof(payload_read)); + spdk_blob_io_read(blob, channel, payload_read, 4, 8, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_aa, 8 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 8 * 512, payload_00, 24 * 512) == 0); + + /* Read eight io_units across multiple clusters + * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 (FFFF ] + * cluster1: [ FFFF) 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] + * payload_read: FFFF FFFF | 0000 0000 ... */ + memset(payload_read, 0x00, sizeof(payload_read)); + spdk_blob_io_read(blob, channel, payload_read, 28, 8, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 8 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 8 * 512, payload_00, 24 * 512) == 0); + + /* Read four io_units from second cluster + * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 00(00 FF)00 | 0000 0000 | 0000 0000 ] + * payload_read: 00FF 0000 | 0000 0000 ... */ + memset(payload_read, 0x00, sizeof(payload_read)); + spdk_blob_io_read(blob, channel, payload_read, 32 + 10, 4, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_00, 2 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 2 * 512, payload_ff, 2 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 28 * 512) == 0); + + /* Read second cluster + * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ (FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000) ] + * payload_read: FFFF 0000 | 0000 FF00 ... */ + memset(payload_read, 0x00, sizeof(payload_read)); + spdk_blob_io_read(blob, channel, payload_read, 32, 32, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 4 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 8 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 12 * 512, payload_ff, 2 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 14 * 512, payload_00, 18 * 512) == 0); + + /* Read whole two clusters + * cluster0: [ (F0F0 AAAA | AAAA) 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000) ] */ + memset(payload_read, 0x00, sizeof(payload_read)); + spdk_blob_io_read(blob, channel, payload_read, 0, 64, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(payload_read + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(payload_read + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(payload_read + 4 * 512, payload_aa, 8 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 28 * 512, payload_ff, 4 * 512) == 0); + + CU_ASSERT(memcmp(payload_read + (32 + 0) * 512, payload_ff, 4 * 512) == 0); + CU_ASSERT(memcmp(payload_read + (32 + 4) * 512, payload_00, 8 * 512) == 0); + CU_ASSERT(memcmp(payload_read + (32 + 12) * 512, payload_ff, 2 * 512) == 0); + CU_ASSERT(memcmp(payload_read + (32 + 14) * 512, payload_00, 18 * 512) == 0); +} + + +static void +test_io_unmap(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel) +{ + uint8_t payload_ff[64 * 512]; + uint8_t payload_aa[64 * 512]; + uint8_t payload_00[64 * 512]; + uint8_t *cluster0, *cluster1; + + memset(payload_ff, 0xFF, sizeof(payload_ff)); + memset(payload_aa, 0xAA, sizeof(payload_aa)); + memset(payload_00, 0x00, sizeof(payload_00)); + + cluster0 = &g_dev_buffer[blob->active.clusters[0] * dev->blocklen]; + cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen]; + + /* Unmap */ + spdk_blob_io_unmap(blob, channel, 0, 64, blob_op_complete, NULL); + + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_00, 32 * 512) == 0); + CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_00, 32 * 512) == 0); +} + +static void +test_io_zeroes(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel) +{ + uint8_t payload_ff[64 * 512]; + uint8_t payload_aa[64 * 512]; + uint8_t payload_00[64 * 512]; + uint8_t *cluster0, *cluster1; + + memset(payload_ff, 0xFF, sizeof(payload_ff)); + memset(payload_aa, 0xAA, sizeof(payload_aa)); + memset(payload_00, 0x00, sizeof(payload_00)); + + cluster0 = &g_dev_buffer[blob->active.clusters[0] * dev->blocklen]; + cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen]; + + /* Write zeroes */ + spdk_blob_io_write_zeroes(blob, channel, 0, 64, blob_op_complete, NULL); + + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_00, 32 * 512) == 0); + CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_00, 32 * 512) == 0); +} + + +static void +test_iov_write(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel) +{ + uint8_t payload_ff[64 * 512]; + uint8_t payload_aa[64 * 512]; + uint8_t payload_00[64 * 512]; + uint8_t *cluster0, *cluster1; + struct iovec iov[4]; + + memset(payload_ff, 0xFF, sizeof(payload_ff)); + memset(payload_aa, 0xAA, sizeof(payload_aa)); + memset(payload_00, 0x00, sizeof(payload_00)); + + /* Try to perform I/O with io unit = 512 */ + iov[0].iov_base = payload_ff; + iov[0].iov_len = 1 * 512; + spdk_blob_io_writev(blob, channel, iov, 1, 0, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* If thin provisioned is set cluster should be allocated now */ + SPDK_CU_ASSERT_FATAL(blob->active.clusters[0] != 0); + cluster0 = &g_dev_buffer[blob->active.clusters[0] * dev->blocklen]; + + /* Each character 0-F symbolizes single io_unit containing 512 bytes block filled with that character. + * Each page is separated by |. Whole block [...] symbolizes one cluster (containing 4 pages). */ + /* cluster0: [ F000 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 31 * 512) == 0); + + /* Verify write with offset on first page */ + iov[0].iov_base = payload_ff; + iov[0].iov_len = 1 * 512; + spdk_blob_io_writev(blob, channel, iov, 1, 2, 1, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* cluster0: [ F0F0 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_00, 28 * 512) == 0); + + /* Verify write with offset on first page */ + iov[0].iov_base = payload_ff; + iov[0].iov_len = 4 * 512; + spdk_blob_io_writev(blob, channel, iov, 1, 4, 4, blob_op_complete, NULL); + + /* cluster0: [ F0F0 FFFF | 0000 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_ff, 4 * 512) == 0); + CU_ASSERT(memcmp(cluster0 + 8 * 512, payload_00, 24 * 512) == 0); + + /* Verify write with offset on second page */ + iov[0].iov_base = payload_ff; + iov[0].iov_len = 4 * 512; + spdk_blob_io_writev(blob, channel, iov, 1, 8, 4, blob_op_complete, NULL); + + /* cluster0: [ F0F0 FFFF | FFFF 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_ff, 8 * 512) == 0); + CU_ASSERT(memcmp(cluster0 + 12 * 512, payload_00, 20 * 512) == 0); + + /* Verify write across multiple pages */ + iov[0].iov_base = payload_aa; + iov[0].iov_len = 8 * 512; + spdk_blob_io_writev(blob, channel, iov, 1, 4, 8, blob_op_complete, NULL); + + /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0); + CU_ASSERT(memcmp(cluster0 + 12 * 512, payload_00, 20 * 512) == 0); + + /* Verify write across multiple clusters */ + + iov[0].iov_base = payload_ff; + iov[0].iov_len = 8 * 512; + spdk_blob_io_writev(blob, channel, iov, 1, 28, 8, blob_op_complete, NULL); + + SPDK_CU_ASSERT_FATAL(blob->active.clusters[1] != 0); + cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen]; + + /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0); + CU_ASSERT(memcmp(cluster0 + 12 * 512, payload_00, 16 * 512) == 0); + CU_ASSERT(memcmp(cluster0 + 28 * 512, payload_ff, 4 * 512) == 0); + + CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_ff, 4 * 512) == 0); + CU_ASSERT(memcmp(cluster1 + 4 * 512, payload_00, 28 * 512) == 0); + + /* Verify write to second cluster */ + + iov[0].iov_base = payload_ff; + iov[0].iov_len = 2 * 512; + spdk_blob_io_writev(blob, channel, iov, 1, 32 + 12, 2, blob_op_complete, NULL); + + SPDK_CU_ASSERT_FATAL(blob->active.clusters[1] != 0); + cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen]; + + /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] */ + CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0); + CU_ASSERT(memcmp(cluster0 + 28 * 512, payload_ff, 4 * 512) == 0); + + CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_ff, 4 * 512) == 0); + CU_ASSERT(memcmp(cluster1 + 4 * 512, payload_00, 8 * 512) == 0); + CU_ASSERT(memcmp(cluster1 + 12 * 512, payload_ff, 2 * 512) == 0); + CU_ASSERT(memcmp(cluster1 + 14 * 512, payload_00, 18 * 512) == 0); +} + +static void +test_iov_read(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel) +{ + uint8_t payload_read[64 * 512]; + uint8_t payload_ff[64 * 512]; + uint8_t payload_aa[64 * 512]; + uint8_t payload_00[64 * 512]; + struct iovec iov[4]; + + memset(payload_ff, 0xFF, sizeof(payload_ff)); + memset(payload_aa, 0xAA, sizeof(payload_aa)); + memset(payload_00, 0x00, sizeof(payload_00)); + + /* Read only first io unit */ + /* cluster0: [ (F)0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] + * payload_read: F000 0000 | 0000 0000 ... */ + memset(payload_read, 0x00, sizeof(payload_read)); + iov[0].iov_base = payload_read; + iov[0].iov_len = 1 * 512; + spdk_blob_io_readv(blob, channel, iov, 1, 0, 1, blob_op_complete, NULL); + + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 31 * 512) == 0); + + /* Read four io_units starting from offset = 2 + * cluster0: [ F0(F0 AA)AA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] + * payload_read: F0AA 0000 | 0000 0000 ... */ + + memset(payload_read, 0x00, sizeof(payload_read)); + iov[0].iov_base = payload_read; + iov[0].iov_len = 4 * 512; + spdk_blob_io_readv(blob, channel, iov, 1, 2, 4, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(payload_read + 2 * 512, payload_aa, 512) == 0); + CU_ASSERT(memcmp(payload_read + 3 * 512, payload_aa, 512) == 0); + CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 28 * 512) == 0); + + /* Read eight io_units across multiple pages + * cluster0: [ F0F0 (AAAA | AAAA) 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] + * payload_read: AAAA AAAA | 0000 0000 ... */ + memset(payload_read, 0x00, sizeof(payload_read)); + iov[0].iov_base = payload_read; + iov[0].iov_len = 4 * 512; + iov[1].iov_base = payload_read + 4 * 512; + iov[1].iov_len = 4 * 512; + spdk_blob_io_readv(blob, channel, iov, 2, 4, 8, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_aa, 8 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 8 * 512, payload_00, 24 * 512) == 0); + + /* Read eight io_units across multiple clusters + * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 (FFFF ] + * cluster1: [ FFFF) 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] + * payload_read: FFFF FFFF | 0000 0000 ... */ + memset(payload_read, 0x00, sizeof(payload_read)); + iov[0].iov_base = payload_read; + iov[0].iov_len = 2 * 512; + iov[1].iov_base = payload_read + 2 * 512; + iov[1].iov_len = 2 * 512; + iov[2].iov_base = payload_read + 4 * 512; + iov[2].iov_len = 2 * 512; + iov[3].iov_base = payload_read + 6 * 512; + iov[3].iov_len = 2 * 512; + spdk_blob_io_readv(blob, channel, iov, 4, 28, 8, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 8 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 8 * 512, payload_00, 24 * 512) == 0); + + /* Read four io_units from second cluster + * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 00(00 FF)00 | 0000 0000 | 0000 0000 ] + * payload_read: 00FF 0000 | 0000 0000 ... */ + memset(payload_read, 0x00, sizeof(payload_read)); + iov[0].iov_base = payload_read; + iov[0].iov_len = 1 * 512; + iov[1].iov_base = payload_read + 1 * 512; + iov[1].iov_len = 3 * 512; + spdk_blob_io_readv(blob, channel, iov, 2, 32 + 10, 4, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_00, 2 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 2 * 512, payload_ff, 2 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 28 * 512) == 0); + + /* Read second cluster + * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ (FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000) ] + * payload_read: FFFF 0000 | 0000 FF00 ... */ + memset(payload_read, 0x00, sizeof(payload_read)); + iov[0].iov_base = payload_read; + iov[0].iov_len = 1 * 512; + iov[1].iov_base = payload_read + 1 * 512; + iov[1].iov_len = 2 * 512; + iov[2].iov_base = payload_read + 3 * 512; + iov[2].iov_len = 4 * 512; + iov[3].iov_base = payload_read + 7 * 512; + iov[3].iov_len = 25 * 512; + spdk_blob_io_readv(blob, channel, iov, 4, 32, 32, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 4 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 8 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 12 * 512, payload_ff, 2 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 14 * 512, payload_00, 18 * 512) == 0); + + /* Read whole two clusters + * cluster0: [ (F0F0 AAAA | AAAA) 0000 | 0000 0000 | 0000 FFFF ] + * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000) ] */ + memset(payload_read, 0x00, sizeof(payload_read)); + iov[0].iov_base = payload_read; + iov[0].iov_len = 1 * 512; + iov[1].iov_base = payload_read + 1 * 512; + iov[1].iov_len = 8 * 512; + iov[2].iov_base = payload_read + 9 * 512; + iov[2].iov_len = 16 * 512; + iov[3].iov_base = payload_read + 25 * 512; + iov[3].iov_len = 39 * 512; + spdk_blob_io_readv(blob, channel, iov, 4, 0, 64, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(payload_read + 2 * 512, payload_ff, 512) == 0); + CU_ASSERT(memcmp(payload_read + 3 * 512, payload_00, 512) == 0); + CU_ASSERT(memcmp(payload_read + 4 * 512, payload_aa, 8 * 512) == 0); + CU_ASSERT(memcmp(payload_read + 28 * 512, payload_ff, 4 * 512) == 0); + + CU_ASSERT(memcmp(payload_read + (32 + 0) * 512, payload_ff, 4 * 512) == 0); + CU_ASSERT(memcmp(payload_read + (32 + 4) * 512, payload_00, 8 * 512) == 0); + CU_ASSERT(memcmp(payload_read + (32 + 12) * 512, payload_ff, 2 * 512) == 0); + CU_ASSERT(memcmp(payload_read + (32 + 14) * 512, payload_00, 18 * 512) == 0); +} + +static void +blob_io_unit(void) +{ + struct spdk_bs_opts bsopts; + struct spdk_blob_opts opts; + struct spdk_bs_dev *dev; + struct spdk_blob *blob, *snapshot, *clone; + spdk_blob_id blobid; + struct spdk_io_channel *channel; + + /* Create dev with 512 bytes io unit size */ + + spdk_bs_opts_init(&bsopts); + bsopts.cluster_sz = SPDK_BS_PAGE_SIZE * 4; // 8 * 4 = 32 io_unit + snprintf(bsopts.bstype.bstype, sizeof(bsopts.bstype.bstype), "TESTTYPE"); + + /* Try to initialize a new blob store with unsupported io_unit */ + dev = init_dev(); + dev->blocklen = 512; + dev->blockcnt = DEV_BUFFER_SIZE / dev->blocklen; + + /* Initialize a new blob store */ + spdk_bs_init(dev, &bsopts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + CU_ASSERT(spdk_bs_get_io_unit_size(g_bs) == 512); + channel = spdk_bs_alloc_io_channel(g_bs); + + /* Create thick provisioned blob */ + spdk_blob_opts_init(&opts); + opts.thin_provision = false; + opts.num_clusters = 32; + + spdk_bs_create_blob_ext(g_bs, &opts, blob_op_with_id_complete, NULL); + + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + test_io_write(dev, blob, channel); + test_io_read(dev, blob, channel); + test_io_zeroes(dev, blob, channel); + + test_iov_write(dev, blob, channel); + test_iov_read(dev, blob, channel); + + test_io_unmap(dev, blob, channel); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + blob = NULL; + g_blob = NULL; + + /* Create thin provisioned blob */ + + spdk_blob_opts_init(&opts); + opts.thin_provision = true; + opts.num_clusters = 32; + + spdk_bs_create_blob_ext(g_bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + blob = g_blob; + + test_io_write(dev, blob, channel); + test_io_read(dev, blob, channel); + + test_io_zeroes(dev, blob, channel); + + test_iov_write(dev, blob, channel); + test_iov_read(dev, blob, channel); + + /* Create snapshot */ + + spdk_bs_create_snapshot(g_bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + snapshot = g_blob; + + spdk_bs_create_clone(g_bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + blobid = g_blobid; + + spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blob != NULL); + clone = g_blob; + + test_io_read(dev, blob, channel); + test_io_read(dev, snapshot, channel); + test_io_read(dev, clone, channel); + + test_iov_read(dev, blob, channel); + test_iov_read(dev, snapshot, channel); + test_iov_read(dev, clone, channel); + + /* Inflate clone */ + + spdk_bs_inflate_blob(g_bs, channel, blobid, blob_op_complete, NULL); + + CU_ASSERT(g_bserrno == 0); + + test_io_read(dev, clone, channel); + + test_io_unmap(dev, clone, channel); + + test_iov_write(dev, clone, channel); + test_iov_read(dev, clone, channel); + + spdk_blob_close(blob, blob_op_complete, NULL); + spdk_blob_close(snapshot, blob_op_complete, NULL); + spdk_blob_close(clone, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + blob = NULL; + g_blob = NULL; + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; +} + +static void +blob_io_unit_compatiblity(void) +{ + struct spdk_bs_opts bsopts; + struct spdk_bs_dev *dev; + struct spdk_bs_super_block *super; + + /* Create dev with 512 bytes io unit size */ + + spdk_bs_opts_init(&bsopts); + bsopts.cluster_sz = SPDK_BS_PAGE_SIZE * 4; // 8 * 4 = 32 io_unit + snprintf(bsopts.bstype.bstype, sizeof(bsopts.bstype.bstype), "TESTTYPE"); + + /* Try to initialize a new blob store with unsupported io_unit */ + dev = init_dev(); + dev->blocklen = 512; + dev->blockcnt = DEV_BUFFER_SIZE / dev->blocklen; + + /* Initialize a new blob store */ + spdk_bs_init(dev, &bsopts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + CU_ASSERT(spdk_bs_get_io_unit_size(g_bs) == 512); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Modify super block to behave like older version. + * Check if loaded io unit size equals SPDK_BS_PAGE_SIZE */ + super = (struct spdk_bs_super_block *)&g_dev_buffer[0]; + super->io_unit_size = 0; + super->crc = _spdk_blob_md_page_calc_crc(super); + + dev = init_dev(); + dev->blocklen = 512; + dev->blockcnt = DEV_BUFFER_SIZE / dev->blocklen; + + spdk_bs_load(dev, &bsopts, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + + CU_ASSERT(spdk_bs_get_io_unit_size(g_bs) == SPDK_BS_PAGE_SIZE); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; +} + +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("blob", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "blob_init", blob_init) == NULL || + CU_add_test(suite, "blob_open", blob_open) == NULL || + CU_add_test(suite, "blob_create", blob_create) == NULL || + CU_add_test(suite, "blob_create_internal", blob_create_internal) == NULL || + CU_add_test(suite, "blob_thin_provision", blob_thin_provision) == NULL || + CU_add_test(suite, "blob_snapshot", blob_snapshot) == NULL || + CU_add_test(suite, "blob_clone", blob_clone) == NULL || + CU_add_test(suite, "blob_inflate", blob_inflate) == NULL || + CU_add_test(suite, "blob_delete", blob_delete) == NULL || + CU_add_test(suite, "blob_resize", blob_resize) == NULL || + CU_add_test(suite, "blob_read_only", blob_read_only) == NULL || + CU_add_test(suite, "channel_ops", channel_ops) == NULL || + CU_add_test(suite, "blob_super", blob_super) == NULL || + CU_add_test(suite, "blob_write", blob_write) == NULL || + CU_add_test(suite, "blob_read", blob_read) == NULL || + CU_add_test(suite, "blob_rw_verify", blob_rw_verify) == NULL || + CU_add_test(suite, "blob_rw_verify_iov", blob_rw_verify_iov) == NULL || + CU_add_test(suite, "blob_rw_verify_iov_nomem", blob_rw_verify_iov_nomem) == NULL || + CU_add_test(suite, "blob_rw_iov_read_only", blob_rw_iov_read_only) == NULL || + CU_add_test(suite, "blob_unmap", blob_unmap) == NULL || + CU_add_test(suite, "blob_iter", blob_iter) == NULL || + CU_add_test(suite, "blob_xattr", blob_xattr) == NULL || + CU_add_test(suite, "bs_load", bs_load) == NULL || + CU_add_test(suite, "bs_load_custom_cluster_size", bs_load_custom_cluster_size) == NULL || + CU_add_test(suite, "bs_unload", bs_unload) == NULL || + CU_add_test(suite, "bs_cluster_sz", bs_cluster_sz) == NULL || + CU_add_test(suite, "bs_usable_clusters", bs_usable_clusters) == NULL || + CU_add_test(suite, "bs_resize_md", bs_resize_md) == NULL || + CU_add_test(suite, "bs_destroy", bs_destroy) == NULL || + CU_add_test(suite, "bs_type", bs_type) == NULL || + CU_add_test(suite, "bs_super_block", bs_super_block) == NULL || + CU_add_test(suite, "blob_serialize", blob_serialize) == NULL || + CU_add_test(suite, "blob_crc", blob_crc) == NULL || + CU_add_test(suite, "super_block_crc", super_block_crc) == NULL || + CU_add_test(suite, "blob_dirty_shutdown", blob_dirty_shutdown) == NULL || + CU_add_test(suite, "blob_flags", blob_flags) == NULL || + CU_add_test(suite, "bs_version", bs_version) == NULL || + CU_add_test(suite, "blob_set_xattrs", blob_set_xattrs) == NULL || + CU_add_test(suite, "blob_thin_prov_alloc", blob_thin_prov_alloc) == NULL || + CU_add_test(suite, "blob_insert_cluster_msg", blob_insert_cluster_msg) == NULL || + CU_add_test(suite, "blob_thin_prov_rw", blob_thin_prov_rw) == NULL || + CU_add_test(suite, "blob_thin_prov_rw_iov", blob_thin_prov_rw_iov) == NULL || + CU_add_test(suite, "bs_load_iter", bs_load_iter) == NULL || + CU_add_test(suite, "blob_snapshot_rw", blob_snapshot_rw) == NULL || + CU_add_test(suite, "blob_snapshot_rw_iov", blob_snapshot_rw_iov) == NULL || + CU_add_test(suite, "blob_relations", blob_relations) == NULL || + CU_add_test(suite, "blob_inflate_rw", blob_inflate_rw) == NULL || + CU_add_test(suite, "blob_snapshot_freeze_io", blob_snapshot_freeze_io) == NULL || + CU_add_test(suite, "blob_operation_split_rw", blob_operation_split_rw) == NULL || + CU_add_test(suite, "blob_operation_split_rw_iov", blob_operation_split_rw_iov) == NULL || + CU_add_test(suite, "blob_io_unit", blob_io_unit) == NULL || + CU_add_test(suite, "blob_io_unit_compatiblity", blob_io_unit_compatiblity) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + g_dev_buffer = calloc(1, DEV_BUFFER_SIZE); + spdk_allocate_thread(_bs_send_msg, NULL, NULL, NULL, "thread0"); + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + spdk_free_thread(); + free(g_dev_buffer); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/blob/bs_dev_common.c b/src/spdk/test/unit/lib/blob/bs_dev_common.c new file mode 100644 index 00000000..fe310526 --- /dev/null +++ b/src/spdk/test/unit/lib/blob/bs_dev_common.c @@ -0,0 +1,225 @@ +/*- + * 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/thread.h" +#include "bs_scheduler.c" + + +#define DEV_BUFFER_SIZE (64 * 1024 * 1024) +#define DEV_BUFFER_BLOCKLEN (4096) +#define DEV_BUFFER_BLOCKCNT (DEV_BUFFER_SIZE / DEV_BUFFER_BLOCKLEN) +uint8_t *g_dev_buffer; +uint64_t g_dev_write_bytes; +uint64_t g_dev_read_bytes; + +/* Define here for UT only. */ +struct spdk_io_channel g_io_channel; + +static struct spdk_io_channel * +dev_create_channel(struct spdk_bs_dev *dev) +{ + return &g_io_channel; +} + +static void +dev_destroy_channel(struct spdk_bs_dev *dev, struct spdk_io_channel *channel) +{ +} + +static void +dev_destroy(struct spdk_bs_dev *dev) +{ + free(dev); +} + + +static void +dev_complete_cb(void *arg) +{ + struct spdk_bs_dev_cb_args *cb_args = arg; + + cb_args->cb_fn(cb_args->channel, cb_args->cb_arg, 0); +} + +static void +dev_complete(void *arg) +{ + _bs_send_msg(dev_complete_cb, arg, NULL); +} + +static void +dev_read(struct spdk_bs_dev *dev, struct spdk_io_channel *channel, void *payload, + uint64_t lba, uint32_t lba_count, + struct spdk_bs_dev_cb_args *cb_args) +{ + uint64_t offset, length; + + offset = lba * dev->blocklen; + length = lba_count * dev->blocklen; + SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE); + memcpy(payload, &g_dev_buffer[offset], length); + g_dev_read_bytes += length; + spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args); +} + +static void +dev_write(struct spdk_bs_dev *dev, struct spdk_io_channel *channel, void *payload, + uint64_t lba, uint32_t lba_count, + struct spdk_bs_dev_cb_args *cb_args) +{ + uint64_t offset, length; + + offset = lba * dev->blocklen; + length = lba_count * dev->blocklen; + SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE); + memcpy(&g_dev_buffer[offset], payload, length); + g_dev_write_bytes += length; + spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args); +} + +static void +__check_iov(struct iovec *iov, int iovcnt, uint64_t length) +{ + int i; + + for (i = 0; i < iovcnt; i++) { + length -= iov[i].iov_len; + } + + CU_ASSERT(length == 0); +} + +static void +dev_readv(struct spdk_bs_dev *dev, struct spdk_io_channel *channel, + struct iovec *iov, int iovcnt, + uint64_t lba, uint32_t lba_count, + struct spdk_bs_dev_cb_args *cb_args) +{ + uint64_t offset, length; + int i; + + offset = lba * dev->blocklen; + length = lba_count * dev->blocklen; + SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE); + __check_iov(iov, iovcnt, length); + + for (i = 0; i < iovcnt; i++) { + memcpy(iov[i].iov_base, &g_dev_buffer[offset], iov[i].iov_len); + offset += iov[i].iov_len; + } + + g_dev_read_bytes += length; + spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args); +} + +static void +dev_writev(struct spdk_bs_dev *dev, struct spdk_io_channel *channel, + struct iovec *iov, int iovcnt, + uint64_t lba, uint32_t lba_count, + struct spdk_bs_dev_cb_args *cb_args) +{ + uint64_t offset, length; + int i; + + offset = lba * dev->blocklen; + length = lba_count * dev->blocklen; + SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE); + __check_iov(iov, iovcnt, length); + + for (i = 0; i < iovcnt; i++) { + memcpy(&g_dev_buffer[offset], iov[i].iov_base, iov[i].iov_len); + offset += iov[i].iov_len; + } + + g_dev_write_bytes += length; + spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args); +} + +static void +dev_flush(struct spdk_bs_dev *dev, struct spdk_io_channel *channel, + struct spdk_bs_dev_cb_args *cb_args) +{ + spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args); +} + +static void +dev_unmap(struct spdk_bs_dev *dev, struct spdk_io_channel *channel, + uint64_t lba, uint32_t lba_count, + struct spdk_bs_dev_cb_args *cb_args) +{ + uint64_t offset, length; + + offset = lba * dev->blocklen; + length = lba_count * dev->blocklen; + SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE); + memset(&g_dev_buffer[offset], 0, length); + spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args); +} + +static void +dev_write_zeroes(struct spdk_bs_dev *dev, struct spdk_io_channel *channel, + uint64_t lba, uint32_t lba_count, + struct spdk_bs_dev_cb_args *cb_args) +{ + uint64_t offset, length; + + offset = lba * dev->blocklen; + length = lba_count * dev->blocklen; + SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE); + memset(&g_dev_buffer[offset], 0, length); + g_dev_write_bytes += length; + spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args); +} + +static struct spdk_bs_dev * +init_dev(void) +{ + struct spdk_bs_dev *dev = calloc(1, sizeof(*dev)); + + SPDK_CU_ASSERT_FATAL(dev != NULL); + + dev->create_channel = dev_create_channel; + dev->destroy_channel = dev_destroy_channel; + dev->destroy = dev_destroy; + dev->read = dev_read; + dev->write = dev_write; + dev->readv = dev_readv; + dev->writev = dev_writev; + dev->flush = dev_flush; + dev->unmap = dev_unmap; + dev->write_zeroes = dev_write_zeroes; + dev->blockcnt = DEV_BUFFER_BLOCKCNT; + dev->blocklen = DEV_BUFFER_BLOCKLEN; + + return dev; +} diff --git a/src/spdk/test/unit/lib/blob/bs_scheduler.c b/src/spdk/test/unit/lib/blob/bs_scheduler.c new file mode 100644 index 00000000..76fa067e --- /dev/null +++ b/src/spdk/test/unit/lib/blob/bs_scheduler.c @@ -0,0 +1,87 @@ +/*- + * 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. + */ + +bool g_scheduler_delay = false; + +struct scheduled_ops { + spdk_thread_fn fn; + void *ctx; + + TAILQ_ENTRY(scheduled_ops) ops_queue; +}; + +static TAILQ_HEAD(, scheduled_ops) g_scheduled_ops = TAILQ_HEAD_INITIALIZER(g_scheduled_ops); + +void _bs_flush_scheduler(uint32_t); + +static void +_bs_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx) +{ + if (g_scheduler_delay) { + struct scheduled_ops *ops = calloc(1, sizeof(*ops)); + + SPDK_CU_ASSERT_FATAL(ops != NULL); + ops->fn = fn; + ops->ctx = ctx; + TAILQ_INSERT_TAIL(&g_scheduled_ops, ops, ops_queue); + + } else { + fn(ctx); + } +} + +static void +_bs_flush_scheduler_single(void) +{ + struct scheduled_ops *op; + TAILQ_HEAD(, scheduled_ops) ops; + TAILQ_INIT(&ops); + + TAILQ_SWAP(&g_scheduled_ops, &ops, scheduled_ops, ops_queue); + + while (!TAILQ_EMPTY(&ops)) { + op = TAILQ_FIRST(&ops); + TAILQ_REMOVE(&ops, op, ops_queue); + + op->fn(op->ctx); + free(op); + } +} + +void +_bs_flush_scheduler(uint32_t n) +{ + while (n--) { + _bs_flush_scheduler_single(); + } +} diff --git a/src/spdk/test/unit/lib/blobfs/Makefile b/src/spdk/test/unit/lib/blobfs/Makefile new file mode 100644 index 00000000..dfb98f23 --- /dev/null +++ b/src/spdk/test/unit/lib/blobfs/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = tree.c blobfs_async_ut blobfs_sync_ut + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/.gitignore b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/.gitignore new file mode 100644 index 00000000..aea3b021 --- /dev/null +++ b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/.gitignore @@ -0,0 +1 @@ +blobfs_async_ut diff --git a/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/Makefile b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/Makefile new file mode 100644 index 00000000..e6dd0ce2 --- /dev/null +++ b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/Makefile @@ -0,0 +1,41 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +SPDK_LIB_LIST = blob +TEST_FILE = blobfs_async_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/blobfs_async_ut.c b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/blobfs_async_ut.c new file mode 100644 index 00000000..baf4bc7f --- /dev/null +++ b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/blobfs_async_ut.c @@ -0,0 +1,522 @@ +/*- + * 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 "CUnit/Basic.h" + +#include "common/lib/test_env.c" + +#include "spdk_cunit.h" +#include "blobfs/blobfs.c" +#include "blobfs/tree.c" + +#include "unit/lib/blob/bs_dev_common.c" + +struct spdk_filesystem *g_fs; +struct spdk_file *g_file; +int g_fserrno; + +/* Return NULL to test hardcoded defaults. */ +struct spdk_conf_section * +spdk_conf_find_section(struct spdk_conf *cp, const char *name) +{ + return NULL; +} + +/* Return -1 to test hardcoded defaults. */ +int +spdk_conf_section_get_intval(struct spdk_conf_section *sp, const char *key) +{ + return -1; +} + +static void +_fs_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx) +{ + fn(ctx); +} + +static void +fs_op_complete(void *ctx, int fserrno) +{ + g_fserrno = fserrno; +} + +static void +fs_op_with_handle_complete(void *ctx, struct spdk_filesystem *fs, int fserrno) +{ + g_fs = fs; + g_fserrno = fserrno; +} + +static void +fs_init(void) +{ + struct spdk_filesystem *fs; + struct spdk_bs_dev *dev; + + dev = init_dev(); + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + + spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL); + SPDK_CU_ASSERT_FATAL(g_fs != NULL); + CU_ASSERT(g_fserrno == 0); + fs = g_fs; + + g_fserrno = 1; + spdk_fs_unload(fs, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + + spdk_free_thread(); +} + +static void +create_cb(void *ctx, int fserrno) +{ + g_fserrno = fserrno; +} + +static void +open_cb(void *ctx, struct spdk_file *f, int fserrno) +{ + g_fserrno = fserrno; + g_file = f; +} + +static void +delete_cb(void *ctx, int fserrno) +{ + g_fserrno = fserrno; +} + +static void +fs_open(void) +{ + struct spdk_filesystem *fs; + spdk_fs_iter iter; + struct spdk_bs_dev *dev; + struct spdk_file *file; + char name[257] = {'\0'}; + + dev = init_dev(); + memset(name, 'a', sizeof(name) - 1); + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + + spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL); + SPDK_CU_ASSERT_FATAL(g_fs != NULL); + CU_ASSERT(g_fserrno == 0); + fs = g_fs; + + g_fserrno = 0; + /* Open should fail, because the file name is too long. */ + spdk_fs_open_file_async(fs, name, SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL); + CU_ASSERT(g_fserrno == -ENAMETOOLONG); + + g_fserrno = 0; + spdk_fs_open_file_async(fs, "file1", 0, open_cb, NULL); + CU_ASSERT(g_fserrno == -ENOENT); + + g_file = NULL; + g_fserrno = 1; + spdk_fs_open_file_async(fs, "file1", SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL); + CU_ASSERT(g_fserrno == 0); + SPDK_CU_ASSERT_FATAL(g_file != NULL); + CU_ASSERT(!strcmp("file1", g_file->name)); + CU_ASSERT(g_file->ref_count == 1); + + iter = spdk_fs_iter_first(fs); + CU_ASSERT(iter != NULL); + file = spdk_fs_iter_get_file(iter); + SPDK_CU_ASSERT_FATAL(file != NULL); + CU_ASSERT(!strcmp("file1", file->name)); + iter = spdk_fs_iter_next(iter); + CU_ASSERT(iter == NULL); + + g_fserrno = 0; + /* Delete should successful, we will mark the file as deleted. */ + spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL); + CU_ASSERT(g_fserrno == 0); + CU_ASSERT(!TAILQ_EMPTY(&fs->files)); + + g_fserrno = 1; + spdk_file_close_async(g_file, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + CU_ASSERT(TAILQ_EMPTY(&fs->files)); + + g_fserrno = 1; + spdk_fs_unload(fs, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + + spdk_free_thread(); +} + +static void +fs_create(void) +{ + struct spdk_filesystem *fs; + struct spdk_bs_dev *dev; + char name[257] = {'\0'}; + + dev = init_dev(); + memset(name, 'a', sizeof(name) - 1); + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + + spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL); + SPDK_CU_ASSERT_FATAL(g_fs != NULL); + CU_ASSERT(g_fserrno == 0); + fs = g_fs; + + g_fserrno = 0; + /* Create should fail, because the file name is too long. */ + spdk_fs_create_file_async(fs, name, create_cb, NULL); + CU_ASSERT(g_fserrno == -ENAMETOOLONG); + + g_fserrno = 1; + spdk_fs_create_file_async(fs, "file1", create_cb, NULL); + CU_ASSERT(g_fserrno == 0); + + g_fserrno = 1; + spdk_fs_create_file_async(fs, "file1", create_cb, NULL); + CU_ASSERT(g_fserrno == -EEXIST); + + g_fserrno = 1; + spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL); + CU_ASSERT(g_fserrno == 0); + CU_ASSERT(TAILQ_EMPTY(&fs->files)); + + g_fserrno = 1; + spdk_fs_unload(fs, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + + spdk_free_thread(); +} + +static void +fs_truncate(void) +{ + struct spdk_filesystem *fs; + struct spdk_bs_dev *dev; + + dev = init_dev(); + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + + spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL); + SPDK_CU_ASSERT_FATAL(g_fs != NULL); + CU_ASSERT(g_fserrno == 0); + fs = g_fs; + + g_file = NULL; + g_fserrno = 1; + spdk_fs_open_file_async(fs, "file1", SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL); + CU_ASSERT(g_fserrno == 0); + SPDK_CU_ASSERT_FATAL(g_file != NULL); + + g_fserrno = 1; + spdk_file_truncate_async(g_file, 18 * 1024 * 1024 + 1, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + CU_ASSERT(g_file->length == 18 * 1024 * 1024 + 1); + + g_fserrno = 1; + spdk_file_truncate_async(g_file, 1, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + CU_ASSERT(g_file->length == 1); + + g_fserrno = 1; + spdk_file_truncate_async(g_file, 18 * 1024 * 1024 + 1, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + CU_ASSERT(g_file->length == 18 * 1024 * 1024 + 1); + + g_fserrno = 1; + spdk_file_close_async(g_file, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + CU_ASSERT(g_file->ref_count == 0); + + g_fserrno = 1; + spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL); + CU_ASSERT(g_fserrno == 0); + CU_ASSERT(TAILQ_EMPTY(&fs->files)); + + g_fserrno = 1; + spdk_fs_unload(fs, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + + spdk_free_thread(); +} + +static void +fs_rename(void) +{ + struct spdk_filesystem *fs; + struct spdk_file *file, *file2; + struct spdk_bs_dev *dev; + + dev = init_dev(); + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + + spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL); + SPDK_CU_ASSERT_FATAL(g_fs != NULL); + CU_ASSERT(g_fserrno == 0); + fs = g_fs; + + g_fserrno = 1; + spdk_fs_create_file_async(fs, "file1", create_cb, NULL); + CU_ASSERT(g_fserrno == 0); + + g_file = NULL; + g_fserrno = 1; + spdk_fs_open_file_async(fs, "file1", 0, open_cb, NULL); + CU_ASSERT(g_fserrno == 0); + SPDK_CU_ASSERT_FATAL(g_file != NULL); + CU_ASSERT(g_file->ref_count == 1); + + file = g_file; + g_file = NULL; + g_fserrno = 1; + spdk_file_close_async(file, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + SPDK_CU_ASSERT_FATAL(file->ref_count == 0); + + g_file = NULL; + g_fserrno = 1; + spdk_fs_open_file_async(fs, "file2", SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL); + CU_ASSERT(g_fserrno == 0); + SPDK_CU_ASSERT_FATAL(g_file != NULL); + CU_ASSERT(g_file->ref_count == 1); + + file2 = g_file; + g_file = NULL; + g_fserrno = 1; + spdk_file_close_async(file2, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + SPDK_CU_ASSERT_FATAL(file2->ref_count == 0); + + /* + * Do a 3-way rename. This should delete the old "file2", then rename + * "file1" to "file2". + */ + g_fserrno = 1; + spdk_fs_rename_file_async(fs, "file1", "file2", fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + CU_ASSERT(file->ref_count == 0); + CU_ASSERT(!strcmp(file->name, "file2")); + CU_ASSERT(TAILQ_FIRST(&fs->files) == file); + CU_ASSERT(TAILQ_NEXT(file, tailq) == NULL); + + g_fserrno = 0; + spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL); + CU_ASSERT(g_fserrno == -ENOENT); + CU_ASSERT(!TAILQ_EMPTY(&fs->files)); + + g_fserrno = 1; + spdk_fs_delete_file_async(fs, "file2", delete_cb, NULL); + CU_ASSERT(g_fserrno == 0); + CU_ASSERT(TAILQ_EMPTY(&fs->files)); + + g_fserrno = 1; + spdk_fs_unload(fs, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + + spdk_free_thread(); +} + +static void +tree_find_buffer_ut(void) +{ + struct cache_tree *root; + struct cache_tree *level1_0; + struct cache_tree *level0_0_0; + struct cache_tree *level0_0_12; + struct cache_buffer *leaf_0_0_4; + struct cache_buffer *leaf_0_12_8; + struct cache_buffer *leaf_9_23_15; + struct cache_buffer *buffer; + + level1_0 = calloc(1, sizeof(struct cache_tree)); + SPDK_CU_ASSERT_FATAL(level1_0 != NULL); + level0_0_0 = calloc(1, sizeof(struct cache_tree)); + SPDK_CU_ASSERT_FATAL(level0_0_0 != NULL); + level0_0_12 = calloc(1, sizeof(struct cache_tree)); + SPDK_CU_ASSERT_FATAL(level0_0_12 != NULL); + leaf_0_0_4 = calloc(1, sizeof(struct cache_buffer)); + SPDK_CU_ASSERT_FATAL(leaf_0_0_4 != NULL); + leaf_0_12_8 = calloc(1, sizeof(struct cache_buffer)); + SPDK_CU_ASSERT_FATAL(leaf_0_12_8 != NULL); + leaf_9_23_15 = calloc(1, sizeof(struct cache_buffer)); + SPDK_CU_ASSERT_FATAL(leaf_9_23_15 != NULL); + + level1_0->level = 1; + level0_0_0->level = 0; + level0_0_12->level = 0; + + leaf_0_0_4->offset = CACHE_BUFFER_SIZE * 4; + level0_0_0->u.buffer[4] = leaf_0_0_4; + level0_0_0->present_mask |= (1ULL << 4); + + leaf_0_12_8->offset = CACHE_TREE_LEVEL_SIZE(1) * 12 + CACHE_BUFFER_SIZE * 8; + level0_0_12->u.buffer[8] = leaf_0_12_8; + level0_0_12->present_mask |= (1ULL << 8); + + level1_0->u.tree[0] = level0_0_0; + level1_0->present_mask |= (1ULL << 0); + level1_0->u.tree[12] = level0_0_12; + level1_0->present_mask |= (1ULL << 12); + + buffer = spdk_tree_find_buffer(NULL, 0); + CU_ASSERT(buffer == NULL); + + buffer = spdk_tree_find_buffer(level0_0_0, 0); + CU_ASSERT(buffer == NULL); + + buffer = spdk_tree_find_buffer(level0_0_0, CACHE_TREE_LEVEL_SIZE(0) + 1); + CU_ASSERT(buffer == NULL); + + buffer = spdk_tree_find_buffer(level0_0_0, leaf_0_0_4->offset); + CU_ASSERT(buffer == leaf_0_0_4); + + buffer = spdk_tree_find_buffer(level1_0, leaf_0_0_4->offset); + CU_ASSERT(buffer == leaf_0_0_4); + + buffer = spdk_tree_find_buffer(level1_0, leaf_0_12_8->offset); + CU_ASSERT(buffer == leaf_0_12_8); + + buffer = spdk_tree_find_buffer(level1_0, leaf_0_12_8->offset + CACHE_BUFFER_SIZE - 1); + CU_ASSERT(buffer == leaf_0_12_8); + + buffer = spdk_tree_find_buffer(level1_0, leaf_0_12_8->offset - 1); + CU_ASSERT(buffer == NULL); + + leaf_9_23_15->offset = CACHE_TREE_LEVEL_SIZE(2) * 9 + + CACHE_TREE_LEVEL_SIZE(1) * 23 + + CACHE_BUFFER_SIZE * 15; + root = spdk_tree_insert_buffer(level1_0, leaf_9_23_15); + CU_ASSERT(root != level1_0); + buffer = spdk_tree_find_buffer(root, leaf_9_23_15->offset); + CU_ASSERT(buffer == leaf_9_23_15); + spdk_tree_free_buffers(root); + free(root); +} + +static void +channel_ops(void) +{ + struct spdk_filesystem *fs; + struct spdk_bs_dev *dev; + struct spdk_io_channel *channel; + + dev = init_dev(); + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + + spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL); + SPDK_CU_ASSERT_FATAL(g_fs != NULL); + CU_ASSERT(g_fserrno == 0); + fs = g_fs; + + channel = spdk_fs_alloc_io_channel(fs); + CU_ASSERT(channel != NULL); + + spdk_fs_free_io_channel(channel); + + g_fserrno = 1; + spdk_fs_unload(fs, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + g_fs = NULL; + + spdk_free_thread(); +} + +static void +channel_ops_sync(void) +{ + struct spdk_filesystem *fs; + struct spdk_bs_dev *dev; + struct spdk_io_channel *channel; + + dev = init_dev(); + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + + spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL); + SPDK_CU_ASSERT_FATAL(g_fs != NULL); + CU_ASSERT(g_fserrno == 0); + fs = g_fs; + + channel = spdk_fs_alloc_io_channel_sync(fs); + CU_ASSERT(channel != NULL); + + spdk_fs_free_io_channel(channel); + + g_fserrno = 1; + spdk_fs_unload(fs, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + g_fs = NULL; + + spdk_free_thread(); +} + +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("blobfs_async_ut", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "fs_init", fs_init) == NULL || + CU_add_test(suite, "fs_open", fs_open) == NULL || + CU_add_test(suite, "fs_create", fs_create) == NULL || + CU_add_test(suite, "fs_truncate", fs_truncate) == NULL || + CU_add_test(suite, "fs_rename", fs_rename) == NULL || + CU_add_test(suite, "tree_find_buffer", tree_find_buffer_ut) == NULL || + CU_add_test(suite, "channel_ops", channel_ops) == NULL || + CU_add_test(suite, "channel_ops_sync", channel_ops_sync) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + g_dev_buffer = calloc(1, DEV_BUFFER_SIZE); + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + free(g_dev_buffer); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/.gitignore b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/.gitignore new file mode 100644 index 00000000..93ef643f --- /dev/null +++ b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/.gitignore @@ -0,0 +1 @@ +blobfs_sync_ut diff --git a/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/Makefile b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/Makefile new file mode 100644 index 00000000..28f38421 --- /dev/null +++ b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/Makefile @@ -0,0 +1,41 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +SPDK_LIB_LIST = blob +TEST_FILE = blobfs_sync_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/blobfs_sync_ut.c b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/blobfs_sync_ut.c new file mode 100644 index 00000000..140d99bd --- /dev/null +++ b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/blobfs_sync_ut.c @@ -0,0 +1,410 @@ +/*- + * 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/blobfs.h" +#include "spdk/env.h" +#include "spdk/log.h" +#include "spdk/thread.h" +#include "spdk/barrier.h" + +#include "spdk_cunit.h" +#include "unit/lib/blob/bs_dev_common.c" +#include "common/lib/test_env.c" +#include "blobfs/blobfs.c" +#include "blobfs/tree.c" + +struct spdk_filesystem *g_fs; +struct spdk_file *g_file; +int g_fserrno; + +/* Return NULL to test hardcoded defaults. */ +struct spdk_conf_section * +spdk_conf_find_section(struct spdk_conf *cp, const char *name) +{ + return NULL; +} + +/* Return -1 to test hardcoded defaults. */ +int +spdk_conf_section_get_intval(struct spdk_conf_section *sp, const char *key) +{ + return -1; +} + +static void +_fs_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx) +{ + fn(ctx); +} + +struct ut_request { + fs_request_fn fn; + void *arg; + volatile int done; + int from_ut; +}; + +static struct ut_request *g_req = NULL; +static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void +send_request(fs_request_fn fn, void *arg) +{ + struct ut_request *req; + + req = calloc(1, sizeof(*req)); + assert(req != NULL); + req->fn = fn; + req->arg = arg; + req->done = 0; + req->from_ut = 0; + + pthread_mutex_lock(&g_mutex); + g_req = req; + pthread_mutex_unlock(&g_mutex); +} + +static void +ut_send_request(fs_request_fn fn, void *arg) +{ + struct ut_request req; + + + req.fn = fn; + req.arg = arg; + req.done = 0; + req.from_ut = 1; + + pthread_mutex_lock(&g_mutex); + g_req = &req; + pthread_mutex_unlock(&g_mutex); + + while (1) { + pthread_mutex_lock(&g_mutex); + if (req.done == 1) { + pthread_mutex_unlock(&g_mutex); + break; + } + pthread_mutex_unlock(&g_mutex); + } + + /* + * Make sure the address of the local req variable is not in g_req when we exit this + * function to make static analysis tools happy. + */ + g_req = NULL; +} + +static void +fs_op_complete(void *ctx, int fserrno) +{ + g_fserrno = fserrno; +} + +static void +fs_op_with_handle_complete(void *ctx, struct spdk_filesystem *fs, int fserrno) +{ + g_fs = fs; + g_fserrno = fserrno; +} + +static void +_fs_init(void *arg) +{ + struct spdk_bs_dev *dev; + + g_fs = NULL; + g_fserrno = -1; + dev = init_dev(); + spdk_fs_init(dev, NULL, send_request, fs_op_with_handle_complete, NULL); + SPDK_CU_ASSERT_FATAL(g_fs != NULL); + CU_ASSERT(g_fserrno == 0); +} + +static void +_fs_unload(void *arg) +{ + g_fserrno = -1; + spdk_fs_unload(g_fs, fs_op_complete, NULL); + CU_ASSERT(g_fserrno == 0); + g_fs = NULL; +} + +static void +cache_write(void) +{ + uint64_t length; + int rc; + char buf[100]; + struct spdk_io_channel *channel; + + ut_send_request(_fs_init, NULL); + + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + channel = spdk_fs_alloc_io_channel_sync(g_fs); + + rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(g_file != NULL); + + length = (4 * 1024 * 1024); + rc = spdk_file_truncate(g_file, channel, length); + CU_ASSERT(rc == 0); + + spdk_file_write(g_file, channel, buf, 0, sizeof(buf)); + + CU_ASSERT(spdk_file_get_length(g_file) == length); + + rc = spdk_file_truncate(g_file, channel, sizeof(buf)); + CU_ASSERT(rc == 0); + + spdk_file_close(g_file, channel); + rc = spdk_fs_delete_file(g_fs, channel, "testfile"); + CU_ASSERT(rc == 0); + + rc = spdk_fs_delete_file(g_fs, channel, "testfile"); + CU_ASSERT(rc == -ENOENT); + + spdk_fs_free_io_channel(channel); + spdk_free_thread(); + + ut_send_request(_fs_unload, NULL); +} + +static void +cache_write_null_buffer(void) +{ + uint64_t length; + int rc; + struct spdk_io_channel *channel; + + ut_send_request(_fs_init, NULL); + + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + channel = spdk_fs_alloc_io_channel_sync(g_fs); + + rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(g_file != NULL); + + length = 0; + rc = spdk_file_truncate(g_file, channel, length); + CU_ASSERT(rc == 0); + + rc = spdk_file_write(g_file, channel, NULL, 0, 0); + CU_ASSERT(rc == 0); + + spdk_file_close(g_file, channel); + rc = spdk_fs_delete_file(g_fs, channel, "testfile"); + CU_ASSERT(rc == 0); + + spdk_fs_free_io_channel(channel); + spdk_free_thread(); + + ut_send_request(_fs_unload, NULL); +} + +static void +fs_create_sync(void) +{ + int rc; + struct spdk_io_channel *channel; + + ut_send_request(_fs_init, NULL); + + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + channel = spdk_fs_alloc_io_channel_sync(g_fs); + CU_ASSERT(channel != NULL); + + rc = spdk_fs_create_file(g_fs, channel, "testfile"); + CU_ASSERT(rc == 0); + + /* Create should fail, because the file already exists. */ + rc = spdk_fs_create_file(g_fs, channel, "testfile"); + CU_ASSERT(rc != 0); + + rc = spdk_fs_delete_file(g_fs, channel, "testfile"); + CU_ASSERT(rc == 0); + + spdk_fs_free_io_channel(channel); + spdk_free_thread(); + + ut_send_request(_fs_unload, NULL); +} + +static void +cache_append_no_cache(void) +{ + int rc; + char buf[100]; + struct spdk_io_channel *channel; + + ut_send_request(_fs_init, NULL); + + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + channel = spdk_fs_alloc_io_channel_sync(g_fs); + + rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(g_file != NULL); + + spdk_file_write(g_file, channel, buf, 0 * sizeof(buf), sizeof(buf)); + CU_ASSERT(spdk_file_get_length(g_file) == 1 * sizeof(buf)); + spdk_file_write(g_file, channel, buf, 1 * sizeof(buf), sizeof(buf)); + CU_ASSERT(spdk_file_get_length(g_file) == 2 * sizeof(buf)); + spdk_file_sync(g_file, channel); + cache_free_buffers(g_file); + spdk_file_write(g_file, channel, buf, 2 * sizeof(buf), sizeof(buf)); + CU_ASSERT(spdk_file_get_length(g_file) == 3 * sizeof(buf)); + spdk_file_write(g_file, channel, buf, 3 * sizeof(buf), sizeof(buf)); + CU_ASSERT(spdk_file_get_length(g_file) == 4 * sizeof(buf)); + spdk_file_write(g_file, channel, buf, 4 * sizeof(buf), sizeof(buf)); + CU_ASSERT(spdk_file_get_length(g_file) == 5 * sizeof(buf)); + + spdk_file_close(g_file, channel); + rc = spdk_fs_delete_file(g_fs, channel, "testfile"); + CU_ASSERT(rc == 0); + + spdk_fs_free_io_channel(channel); + spdk_free_thread(); + + ut_send_request(_fs_unload, NULL); +} + +static void +fs_delete_file_without_close(void) +{ + int rc; + struct spdk_io_channel *channel; + struct spdk_file *file; + + ut_send_request(_fs_init, NULL); + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + channel = spdk_fs_alloc_io_channel_sync(g_fs); + CU_ASSERT(channel != NULL); + + rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(g_file != NULL); + + rc = spdk_fs_delete_file(g_fs, channel, "testfile"); + CU_ASSERT(rc == 0); + CU_ASSERT(g_file->ref_count != 0); + CU_ASSERT(g_file->is_deleted == true); + + rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &file); + CU_ASSERT(rc != 0); + + spdk_file_close(g_file, channel); + + rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &file); + CU_ASSERT(rc != 0); + + spdk_fs_free_io_channel(channel); + spdk_free_thread(); + + ut_send_request(_fs_unload, NULL); + +} + +static void +terminate_spdk_thread(void *arg) +{ + spdk_free_thread(); + pthread_exit(NULL); +} + +static void * +spdk_thread(void *arg) +{ + struct ut_request *req; + + spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0"); + + while (1) { + pthread_mutex_lock(&g_mutex); + if (g_req != NULL) { + req = g_req; + req->fn(req->arg); + req->done = 1; + if (!req->from_ut) { + free(req); + } + g_req = NULL; + } + pthread_mutex_unlock(&g_mutex); + } + + return NULL; +} + +int main(int argc, char **argv) +{ + CU_pSuite suite = NULL; + pthread_t spdk_tid; + unsigned int num_failures; + + if (CU_initialize_registry() != CUE_SUCCESS) { + return CU_get_error(); + } + + suite = CU_add_suite("blobfs_sync_ut", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "write", cache_write) == NULL || + CU_add_test(suite, "write_null_buffer", cache_write_null_buffer) == NULL || + CU_add_test(suite, "create_sync", fs_create_sync) == NULL || + CU_add_test(suite, "append_no_cache", cache_append_no_cache) == NULL || + CU_add_test(suite, "delete_file_without_close", fs_delete_file_without_close) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + pthread_create(&spdk_tid, NULL, spdk_thread, NULL); + g_dev_buffer = calloc(1, DEV_BUFFER_SIZE); + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + free(g_dev_buffer); + send_request(terminate_spdk_thread, NULL); + pthread_join(spdk_tid, NULL); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/blobfs/tree.c/.gitignore b/src/spdk/test/unit/lib/blobfs/tree.c/.gitignore new file mode 100644 index 00000000..57e77bf7 --- /dev/null +++ b/src/spdk/test/unit/lib/blobfs/tree.c/.gitignore @@ -0,0 +1 @@ +tree_ut diff --git a/src/spdk/test/unit/lib/blobfs/tree.c/Makefile b/src/spdk/test/unit/lib/blobfs/tree.c/Makefile new file mode 100644 index 00000000..64bc202a --- /dev/null +++ b/src/spdk/test/unit/lib/blobfs/tree.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = tree_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/blobfs/tree.c/tree_ut.c b/src/spdk/test/unit/lib/blobfs/tree.c/tree_ut.c new file mode 100644 index 00000000..c24aaa78 --- /dev/null +++ b/src/spdk/test/unit/lib/blobfs/tree.c/tree_ut.c @@ -0,0 +1,159 @@ +/*- + * 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_cunit.h" + +#include "blobfs/tree.c" + +void +spdk_cache_buffer_free(struct cache_buffer *cache_buffer) +{ + free(cache_buffer); +} + +static void +blobfs_tree_op_test(void) +{ + struct cache_tree *tree; + struct cache_buffer *buffer[5]; + struct cache_buffer *tmp_buffer; + int i; + + for (i = 0; i < 5; i ++) { + buffer[i] = calloc(1, sizeof(struct cache_buffer)); + SPDK_CU_ASSERT_FATAL(buffer[i]); + } + + tree = calloc(1, sizeof(*tree)); + SPDK_CU_ASSERT_FATAL(tree != NULL); + + /* insert buffer[0] */ + buffer[0]->offset = 0; + tree = spdk_tree_insert_buffer(tree, buffer[0]); + SPDK_CU_ASSERT_FATAL(tree != NULL); + CU_ASSERT(tree->level == 0); + tmp_buffer = spdk_tree_find_buffer(tree, buffer[0]->offset); + CU_ASSERT(tmp_buffer == buffer[0]); + + /* insert buffer[1] */ + buffer[1]->offset = CACHE_BUFFER_SIZE; + /* set the bytes_filled equal = bytes_filled with same non zero value, e.g., 32 */ + buffer[1]->bytes_filled = buffer[1]->bytes_flushed = 32; + tree = spdk_tree_insert_buffer(tree, buffer[1]); + SPDK_CU_ASSERT_FATAL(tree != NULL); + CU_ASSERT(tree->level == 0); + tmp_buffer = spdk_tree_find_filled_buffer(tree, buffer[1]->offset); + CU_ASSERT(tmp_buffer == buffer[1]); + + /* insert buffer[2] */ + buffer[2]->offset = (CACHE_TREE_WIDTH - 1) * CACHE_BUFFER_SIZE; + tree = spdk_tree_insert_buffer(tree, buffer[2]); + SPDK_CU_ASSERT_FATAL(tree != NULL); + CU_ASSERT(tree->level == 0); + tmp_buffer = spdk_tree_find_buffer(tree, buffer[2]->offset); + CU_ASSERT(tmp_buffer == buffer[2]); + tmp_buffer = spdk_tree_find_filled_buffer(tree, buffer[2]->offset); + CU_ASSERT(tmp_buffer == NULL); + + /* insert buffer[3], set an offset which can not be fit level 0 */ + buffer[3]->offset = CACHE_TREE_LEVEL_SIZE(1); + tree = spdk_tree_insert_buffer(tree, buffer[3]); + SPDK_CU_ASSERT_FATAL(tree != NULL); + CU_ASSERT(tree->level == 1); + tmp_buffer = spdk_tree_find_buffer(tree, buffer[3]->offset); + CU_ASSERT(tmp_buffer == buffer[3]); + + /* insert buffer[4], set an offset which can not be fit level 1 */ + buffer[4]->offset = CACHE_TREE_LEVEL_SIZE(2); + tree = spdk_tree_insert_buffer(tree, buffer[4]); + SPDK_CU_ASSERT_FATAL(tree != NULL); + CU_ASSERT(tree->level == 2); + tmp_buffer = spdk_tree_find_buffer(tree, buffer[4]->offset); + CU_ASSERT(tmp_buffer == buffer[4]); + + /* delete buffer[0] */ + spdk_tree_remove_buffer(tree, buffer[0]); + /* check whether buffer[0] is still existed or not */ + tmp_buffer = spdk_tree_find_buffer(tree, 0); + CU_ASSERT(tmp_buffer == NULL); + + /* delete buffer[3] */ + spdk_tree_remove_buffer(tree, buffer[3]); + /* check whether buffer[3] is still existed or not */ + tmp_buffer = spdk_tree_find_buffer(tree, CACHE_TREE_LEVEL_SIZE(1)); + CU_ASSERT(tmp_buffer == NULL); + + /* free all buffers in the tree */ + spdk_tree_free_buffers(tree); + + /* check whether buffer[1] is still existed or not */ + tmp_buffer = spdk_tree_find_buffer(tree, CACHE_BUFFER_SIZE); + CU_ASSERT(tmp_buffer == NULL); + /* check whether buffer[2] is still existed or not */ + tmp_buffer = spdk_tree_find_buffer(tree, (CACHE_TREE_WIDTH - 1) * CACHE_BUFFER_SIZE); + CU_ASSERT(tmp_buffer == NULL); + /* check whether buffer[4] is still existed or not */ + tmp_buffer = spdk_tree_find_buffer(tree, CACHE_TREE_LEVEL_SIZE(2)); + CU_ASSERT(tmp_buffer == NULL); + + /* According to spdk_tree_free_buffers, root will not be freed */ + free(tree); +} + +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("tree", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if (CU_add_test(suite, "blobfs_tree_op_test", blobfs_tree_op_test) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/event/Makefile b/src/spdk/test/unit/lib/event/Makefile new file mode 100644 index 00000000..a6629af9 --- /dev/null +++ b/src/spdk/test/unit/lib/event/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = subsystem.c app.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/event/app.c/.gitignore b/src/spdk/test/unit/lib/event/app.c/.gitignore new file mode 100644 index 00000000..123e1673 --- /dev/null +++ b/src/spdk/test/unit/lib/event/app.c/.gitignore @@ -0,0 +1 @@ +app_ut diff --git a/src/spdk/test/unit/lib/event/app.c/Makefile b/src/spdk/test/unit/lib/event/app.c/Makefile new file mode 100644 index 00000000..cce3ad86 --- /dev/null +++ b/src/spdk/test/unit/lib/event/app.c/Makefile @@ -0,0 +1,42 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.mock.unittest.mk + +SPDK_LIB_LIST = conf trace jsonrpc json +TEST_FILE = app_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/event/app.c/app_ut.c b/src/spdk/test/unit/lib/event/app.c/app_ut.c new file mode 100644 index 00000000..7d549261 --- /dev/null +++ b/src/spdk/test/unit/lib/event/app.c/app_ut.c @@ -0,0 +1,195 @@ +/*- + * 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 "common/lib/test_env.c" +#include "event/app.c" + +#define test_argc 6 + +DEFINE_STUB_V(spdk_rpc_initialize, (const char *listen_addr)); +DEFINE_STUB_V(spdk_rpc_finish, (void)); +DEFINE_STUB_V(spdk_event_call, (struct spdk_event *event)); +DEFINE_STUB_V(spdk_reactors_start, (void)); +DEFINE_STUB_V(spdk_reactors_stop, (void *arg1, void *arg2)); +DEFINE_STUB(spdk_reactors_init, int, (unsigned int max_delay_us), 0); +DEFINE_STUB_V(spdk_reactors_fini, (void)); +DEFINE_STUB(spdk_event_allocate, struct spdk_event *, (uint32_t core, spdk_event_fn fn, void *arg1, + void *arg2), NULL); +DEFINE_STUB(spdk_env_get_current_core, uint32_t, (void), 0); +DEFINE_STUB(spdk_app_get_core_mask, struct spdk_cpuset *, (void), NULL); +DEFINE_STUB_V(spdk_subsystem_config, (FILE *fp)); +DEFINE_STUB_V(spdk_subsystem_init, (struct spdk_event *app_start_event)); +DEFINE_STUB_V(spdk_subsystem_fini, (struct spdk_event *app_stop_event)); +DEFINE_STUB(spdk_env_init, int, (const struct spdk_env_opts *opts), 0); +DEFINE_STUB_V(spdk_env_opts_init, (struct spdk_env_opts *opts)); +DEFINE_STUB(spdk_env_get_core_count, uint32_t, (void), 1); +DEFINE_STUB_V(spdk_rpc_register_method, (const char *method, spdk_rpc_method_handler func, + uint32_t state_mask)); +DEFINE_STUB_V(spdk_rpc_set_state, (uint32_t state)); + + +static void +unittest_usage(void) +{ +} + +static void +unittest_parse_args(int ch, char *arg) +{ +} + +static void +test_spdk_app_parse_args(void) +{ + spdk_app_parse_args_rvals_t rc; + struct spdk_app_opts opts = {}; + struct option my_options[2] = {}; + char *valid_argv[test_argc] = {"app_ut", + "--wait-for-rpc", + "-d", + "-p0", + "-B", + "0000:81:00.0" + }; + char *invalid_argv_BW[test_argc] = {"app_ut", + "-B", + "0000:81:00.0", + "-W", + "0000:82:00.0", + "-cspdk.conf" + }; + /* currently use -z as our new option */ + char *argv_added_short_opt[test_argc] = {"app_ut", + "-z", + "-d", + "--wait-for-rpc", + "-p0", + "-cspdk.conf" + }; + char *argv_added_long_opt[test_argc] = {"app_ut", + "-cspdk.conf", + "-d", + "-r/var/tmp/spdk.sock", + "--test-long-opt", + "--wait-for-rpc" + }; + char *invalid_argv_missing_option[test_argc] = {"app_ut", + "-d", + "-p", + "--wait-for-rpc", + "--silence-noticelog" + "-R" + }; + + /* Test valid arguments. Expected result: PASS */ + rc = spdk_app_parse_args(test_argc, valid_argv, &opts, "", NULL, unittest_parse_args, NULL); + CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_SUCCESS); + optind = 1; + + /* Test invalid short option Expected result: FAIL */ + rc = spdk_app_parse_args(test_argc, argv_added_short_opt, &opts, "", NULL, unittest_parse_args, + NULL); + CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_FAIL); + optind = 1; + + /* Test valid global and local options. Expected result: PASS */ + rc = spdk_app_parse_args(test_argc, argv_added_short_opt, &opts, "z", NULL, unittest_parse_args, + unittest_usage); + CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_SUCCESS); + optind = 1; + + /* Test invalid long option Expected result: FAIL */ + rc = spdk_app_parse_args(test_argc, argv_added_long_opt, &opts, "", NULL, unittest_parse_args, + NULL); + CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_FAIL); + optind = 1; + + /* Test valid global and local options. Expected result: PASS */ + my_options[0].name = "test-long-opt"; + rc = spdk_app_parse_args(test_argc, argv_added_long_opt, &opts, "", my_options, unittest_parse_args, + unittest_usage); + CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_SUCCESS); + optind = 1; + + /* Test overlapping global and local options. Expected result: FAIL */ + rc = spdk_app_parse_args(test_argc, valid_argv, &opts, SPDK_APP_GETOPT_STRING, NULL, + unittest_parse_args, NULL); + CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_FAIL); + optind = 1; + + /* Specify -B and -W options at the same time. Expected result: FAIL */ + rc = spdk_app_parse_args(test_argc, invalid_argv_BW, &opts, "", NULL, unittest_parse_args, NULL); + SPDK_CU_ASSERT_FATAL(rc == SPDK_APP_PARSE_ARGS_FAIL); + optind = 1; + + /* Omit necessary argument to option */ + rc = spdk_app_parse_args(test_argc, invalid_argv_missing_option, &opts, "", NULL, + unittest_parse_args, NULL); + CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_FAIL); + optind = 1; +} + +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("app_suite", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_spdk_app_parse_args", + test_spdk_app_parse_args) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/event/subsystem.c/.gitignore b/src/spdk/test/unit/lib/event/subsystem.c/.gitignore new file mode 100644 index 00000000..76ca0d33 --- /dev/null +++ b/src/spdk/test/unit/lib/event/subsystem.c/.gitignore @@ -0,0 +1 @@ +subsystem_ut diff --git a/src/spdk/test/unit/lib/event/subsystem.c/Makefile b/src/spdk/test/unit/lib/event/subsystem.c/Makefile new file mode 100644 index 00000000..ef76f4fe --- /dev/null +++ b/src/spdk/test/unit/lib/event/subsystem.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = subsystem_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/event/subsystem.c/subsystem_ut.c b/src/spdk/test/unit/lib/event/subsystem.c/subsystem_ut.c new file mode 100644 index 00000000..8663e0e3 --- /dev/null +++ b/src/spdk/test/unit/lib/event/subsystem.c/subsystem_ut.c @@ -0,0 +1,304 @@ +/*- + * 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 "unit/lib/json_mock.c" +#include "event/subsystem.c" + +static struct spdk_subsystem g_ut_subsystems[8]; +static struct spdk_subsystem_depend g_ut_subsystem_deps[8]; +static int global_rc; + +void +spdk_app_stop(int rc) +{ + global_rc = rc; +} + +uint32_t +spdk_env_get_current_core(void) +{ + return 0; +} + +static void +ut_event_fn(void *arg1, void *arg2) +{ +} + +struct spdk_event * +spdk_event_allocate(uint32_t core, spdk_event_fn fn, void *arg1, void *arg2) +{ + struct spdk_event *event = calloc(1, sizeof(*event)); + + SPDK_CU_ASSERT_FATAL(event != NULL); + + event->fn = fn; + event->arg1 = arg1; + event->arg2 = arg2; + + return event; +} + +void spdk_event_call(struct spdk_event *event) +{ + if (event != NULL) { + if (event->fn != NULL) { + event->fn(event->arg1, event->arg2); + } + free(event); + } +} + +static void +set_up_subsystem(struct spdk_subsystem *subsystem, const char *name) +{ + subsystem->init = NULL; + subsystem->fini = NULL; + subsystem->config = NULL; + subsystem->name = name; +} + +static void +set_up_depends(struct spdk_subsystem_depend *depend, const char *subsystem_name, + const char *dpends_on_name) +{ + depend->name = subsystem_name; + depend->depends_on = dpends_on_name; +} + +static void +subsystem_clear(void) +{ + struct spdk_subsystem *subsystem, *subsystem_tmp; + struct spdk_subsystem_depend *subsystem_dep, *subsystem_dep_tmp; + + TAILQ_FOREACH_SAFE(subsystem, &g_subsystems, tailq, subsystem_tmp) { + TAILQ_REMOVE(&g_subsystems, subsystem, tailq); + } + + TAILQ_FOREACH_SAFE(subsystem_dep, &g_subsystems_deps, tailq, subsystem_dep_tmp) { + TAILQ_REMOVE(&g_subsystems_deps, subsystem_dep, tailq); + } +} + +static void +subsystem_sort_test_depends_on_single(void) +{ + struct spdk_subsystem *subsystem; + int i; + char subsystem_name[16]; + struct spdk_event *app_start_event; + + global_rc = -1; + app_start_event = spdk_event_allocate(0, ut_event_fn, NULL, NULL); + spdk_subsystem_init(app_start_event); + + i = 4; + TAILQ_FOREACH(subsystem, &g_subsystems, tailq) { + snprintf(subsystem_name, sizeof(subsystem_name), "subsystem%d", i); + SPDK_CU_ASSERT_FATAL(i > 0); + i--; + CU_ASSERT(strcmp(subsystem_name, subsystem->name) == 0); + } +} + +static void +subsystem_sort_test_depends_on_multiple(void) +{ + int i; + struct spdk_subsystem *subsystem; + struct spdk_event *app_start_event; + + subsystem_clear(); + set_up_subsystem(&g_ut_subsystems[0], "iscsi"); + set_up_subsystem(&g_ut_subsystems[1], "nvmf"); + set_up_subsystem(&g_ut_subsystems[2], "sock"); + set_up_subsystem(&g_ut_subsystems[3], "bdev"); + set_up_subsystem(&g_ut_subsystems[4], "rpc"); + set_up_subsystem(&g_ut_subsystems[5], "scsi"); + set_up_subsystem(&g_ut_subsystems[6], "interface"); + set_up_subsystem(&g_ut_subsystems[7], "copy"); + + for (i = 0; i < 8; i++) { + spdk_add_subsystem(&g_ut_subsystems[i]); + } + + set_up_depends(&g_ut_subsystem_deps[0], "bdev", "copy"); + set_up_depends(&g_ut_subsystem_deps[1], "scsi", "bdev"); + set_up_depends(&g_ut_subsystem_deps[2], "rpc", "interface"); + set_up_depends(&g_ut_subsystem_deps[3], "sock", "interface"); + set_up_depends(&g_ut_subsystem_deps[4], "nvmf", "interface"); + set_up_depends(&g_ut_subsystem_deps[5], "iscsi", "scsi"); + set_up_depends(&g_ut_subsystem_deps[6], "iscsi", "sock"); + set_up_depends(&g_ut_subsystem_deps[7], "iscsi", "rpc"); + + for (i = 0; i < 8; i++) { + spdk_add_subsystem_depend(&g_ut_subsystem_deps[i]); + } + + global_rc = -1; + app_start_event = spdk_event_allocate(0, ut_event_fn, NULL, NULL); + spdk_subsystem_init(app_start_event); + + subsystem = TAILQ_FIRST(&g_subsystems); + CU_ASSERT(strcmp(subsystem->name, "interface") == 0); + TAILQ_REMOVE(&g_subsystems, subsystem, tailq); + + subsystem = TAILQ_FIRST(&g_subsystems); + CU_ASSERT(strcmp(subsystem->name, "copy") == 0); + TAILQ_REMOVE(&g_subsystems, subsystem, tailq); + + subsystem = TAILQ_FIRST(&g_subsystems); + CU_ASSERT(strcmp(subsystem->name, "nvmf") == 0); + TAILQ_REMOVE(&g_subsystems, subsystem, tailq); + + subsystem = TAILQ_FIRST(&g_subsystems); + CU_ASSERT(strcmp(subsystem->name, "sock") == 0); + TAILQ_REMOVE(&g_subsystems, subsystem, tailq); + + subsystem = TAILQ_FIRST(&g_subsystems); + CU_ASSERT(strcmp(subsystem->name, "bdev") == 0); + TAILQ_REMOVE(&g_subsystems, subsystem, tailq); + + subsystem = TAILQ_FIRST(&g_subsystems); + CU_ASSERT(strcmp(subsystem->name, "rpc") == 0); + TAILQ_REMOVE(&g_subsystems, subsystem, tailq); + + subsystem = TAILQ_FIRST(&g_subsystems); + CU_ASSERT(strcmp(subsystem->name, "scsi") == 0); + TAILQ_REMOVE(&g_subsystems, subsystem, tailq); + + subsystem = TAILQ_FIRST(&g_subsystems); + CU_ASSERT(strcmp(subsystem->name, "iscsi") == 0); + TAILQ_REMOVE(&g_subsystems, subsystem, tailq); +} + +struct spdk_subsystem subsystem1 = { + .name = "subsystem1", +}; + +struct spdk_subsystem subsystem2 = { + .name = "subsystem2", +}; +struct spdk_subsystem subsystem3 = { + .name = "subsystem3", +}; + +struct spdk_subsystem subsystem4 = { + .name = "subsystem4", +}; + +SPDK_SUBSYSTEM_REGISTER(subsystem1); +SPDK_SUBSYSTEM_REGISTER(subsystem2); +SPDK_SUBSYSTEM_REGISTER(subsystem3); +SPDK_SUBSYSTEM_REGISTER(subsystem4); + +SPDK_SUBSYSTEM_DEPEND(subsystem1, subsystem2) +SPDK_SUBSYSTEM_DEPEND(subsystem2, subsystem3) +SPDK_SUBSYSTEM_DEPEND(subsystem3, subsystem4) + + +static void +subsystem_sort_test_missing_dependency(void) +{ + /* + * A depends on B, but B is missing + */ + + subsystem_clear(); + set_up_subsystem(&g_ut_subsystems[0], "A"); + spdk_add_subsystem(&g_ut_subsystems[0]); + + set_up_depends(&g_ut_subsystem_deps[0], "A", "B"); + spdk_add_subsystem_depend(&g_ut_subsystem_deps[0]); + + global_rc = -1; + spdk_subsystem_init(NULL); + CU_ASSERT(global_rc != 0); + + /* + * Dependency from C to A is defined, but C is missing + */ + + subsystem_clear(); + set_up_subsystem(&g_ut_subsystems[0], "A"); + spdk_add_subsystem(&g_ut_subsystems[0]); + + set_up_depends(&g_ut_subsystem_deps[0], "C", "A"); + spdk_add_subsystem_depend(&g_ut_subsystem_deps[0]); + + global_rc = -1; + spdk_subsystem_init(NULL); + CU_ASSERT(global_rc != 0); + +} + +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("subsystem_suite", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "subsystem_sort_test_depends_on_single", + subsystem_sort_test_depends_on_single) == NULL + || CU_add_test(suite, "subsystem_sort_test_depends_on_multiple", + subsystem_sort_test_depends_on_multiple) == NULL + || CU_add_test(suite, "subsystem_sort_test_missing_dependency", + subsystem_sort_test_missing_dependency) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/ioat/Makefile b/src/spdk/test/unit/lib/ioat/Makefile new file mode 100644 index 00000000..8d982710 --- /dev/null +++ b/src/spdk/test/unit/lib/ioat/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = ioat.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/ioat/ioat.c/.gitignore b/src/spdk/test/unit/lib/ioat/ioat.c/.gitignore new file mode 100644 index 00000000..deefbf0c --- /dev/null +++ b/src/spdk/test/unit/lib/ioat/ioat.c/.gitignore @@ -0,0 +1 @@ +ioat_ut diff --git a/src/spdk/test/unit/lib/ioat/ioat.c/Makefile b/src/spdk/test/unit/lib/ioat/ioat.c/Makefile new file mode 100644 index 00000000..c3787c3d --- /dev/null +++ b/src/spdk/test/unit/lib/ioat/ioat.c/Makefile @@ -0,0 +1,39 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +TEST_FILE = ioat_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/ioat/ioat.c/ioat_ut.c b/src/spdk/test/unit/lib/ioat/ioat.c/ioat_ut.c new file mode 100644 index 00000000..92330e32 --- /dev/null +++ b/src/spdk/test/unit/lib/ioat/ioat.c/ioat_ut.c @@ -0,0 +1,153 @@ +/*- + * 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_cunit.h" + +#include "ioat/ioat.c" + +#include "spdk_internal/mock.h" + +#include "common/lib/test_env.c" + +int +spdk_pci_ioat_enumerate(spdk_pci_enum_cb enum_cb, void *enum_ctx) +{ + return -1; +} + +int +spdk_pci_device_map_bar(struct spdk_pci_device *dev, uint32_t bar, + void **mapped_addr, uint64_t *phys_addr, uint64_t *size) +{ + *mapped_addr = NULL; + *phys_addr = 0; + *size = 0; + return 0; +} + +int +spdk_pci_device_unmap_bar(struct spdk_pci_device *dev, uint32_t bar, void *addr) +{ + return 0; +} + +int +spdk_pci_device_cfg_read32(struct spdk_pci_device *dev, uint32_t *value, + uint32_t offset) +{ + *value = 0xFFFFFFFFu; + return 0; +} + +int +spdk_pci_device_cfg_write32(struct spdk_pci_device *dev, uint32_t value, + uint32_t offset) +{ + return 0; +} + +static void ioat_state_check(void) +{ + /* + * CHANSTS's STATUS field is 3 bits (8 possible values), but only has 5 valid states: + * ACTIVE 0x0 + * IDLE 0x1 + * SUSPENDED 0x2 + * HALTED 0x3 + * ARMED 0x4 + */ + + CU_ASSERT(is_ioat_active(0) == 1); /* ACTIVE */ + CU_ASSERT(is_ioat_active(1) == 0); /* IDLE */ + CU_ASSERT(is_ioat_active(2) == 0); /* SUSPENDED */ + CU_ASSERT(is_ioat_active(3) == 0); /* HALTED */ + CU_ASSERT(is_ioat_active(4) == 0); /* ARMED */ + CU_ASSERT(is_ioat_active(5) == 0); /* reserved */ + CU_ASSERT(is_ioat_active(6) == 0); /* reserved */ + CU_ASSERT(is_ioat_active(7) == 0); /* reserved */ + + CU_ASSERT(is_ioat_idle(0) == 0); /* ACTIVE */ + CU_ASSERT(is_ioat_idle(1) == 1); /* IDLE */ + CU_ASSERT(is_ioat_idle(2) == 0); /* SUSPENDED */ + CU_ASSERT(is_ioat_idle(3) == 0); /* HALTED */ + CU_ASSERT(is_ioat_idle(4) == 0); /* ARMED */ + CU_ASSERT(is_ioat_idle(5) == 0); /* reserved */ + CU_ASSERT(is_ioat_idle(6) == 0); /* reserved */ + CU_ASSERT(is_ioat_idle(7) == 0); /* reserved */ + + CU_ASSERT(is_ioat_suspended(0) == 0); /* ACTIVE */ + CU_ASSERT(is_ioat_suspended(1) == 0); /* IDLE */ + CU_ASSERT(is_ioat_suspended(2) == 1); /* SUSPENDED */ + CU_ASSERT(is_ioat_suspended(3) == 0); /* HALTED */ + CU_ASSERT(is_ioat_suspended(4) == 0); /* ARMED */ + CU_ASSERT(is_ioat_suspended(5) == 0); /* reserved */ + CU_ASSERT(is_ioat_suspended(6) == 0); /* reserved */ + CU_ASSERT(is_ioat_suspended(7) == 0); /* reserved */ + + CU_ASSERT(is_ioat_halted(0) == 0); /* ACTIVE */ + CU_ASSERT(is_ioat_halted(1) == 0); /* IDLE */ + CU_ASSERT(is_ioat_halted(2) == 0); /* SUSPENDED */ + CU_ASSERT(is_ioat_halted(3) == 1); /* HALTED */ + CU_ASSERT(is_ioat_halted(4) == 0); /* ARMED */ + CU_ASSERT(is_ioat_halted(5) == 0); /* reserved */ + CU_ASSERT(is_ioat_halted(6) == 0); /* reserved */ + CU_ASSERT(is_ioat_halted(7) == 0); /* reserved */ +} + +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("ioat", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "ioat_state_check", ioat_state_check) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/iscsi/Makefile b/src/spdk/test/unit/lib/iscsi/Makefile new file mode 100644 index 00000000..396c5a05 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = conn.c init_grp.c iscsi.c param.c portal_grp.c tgt_node.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/iscsi/common.c b/src/spdk/test/unit/lib/iscsi/common.c new file mode 100644 index 00000000..9ef4f9ab --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/common.c @@ -0,0 +1,256 @@ +#include "iscsi/task.h" +#include "iscsi/iscsi.h" +#include "iscsi/conn.h" +#include "iscsi/acceptor.h" + +#include "spdk/env.h" +#include "spdk/event.h" +#include "spdk/sock.h" +#include "spdk_cunit.h" + +#include "spdk_internal/log.h" + +#include "scsi/scsi_internal.h" + +SPDK_LOG_REGISTER_COMPONENT("iscsi", SPDK_LOG_ISCSI) + +TAILQ_HEAD(, spdk_iscsi_pdu) g_write_pdu_list; + +struct spdk_iscsi_task * +spdk_iscsi_task_get(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *parent, + spdk_scsi_task_cpl cpl_fn) +{ + struct spdk_iscsi_task *task; + + task = calloc(1, sizeof(*task)); + + return task; +} + +void +spdk_scsi_task_put(struct spdk_scsi_task *task) +{ + free(task); +} + +void +spdk_put_pdu(struct spdk_iscsi_pdu *pdu) +{ + if (!pdu) { + return; + } + + pdu->ref--; + if (pdu->ref < 0) { + CU_FAIL("negative ref count"); + pdu->ref = 0; + } + + if (pdu->ref == 0) { + if (pdu->data && !pdu->data_from_mempool) { + free(pdu->data); + } + free(pdu); + } +} + +struct spdk_iscsi_pdu * +spdk_get_pdu(void) +{ + struct spdk_iscsi_pdu *pdu; + + pdu = malloc(sizeof(*pdu)); + if (!pdu) { + return NULL; + } + + memset(pdu, 0, offsetof(struct spdk_iscsi_pdu, ahs)); + pdu->ref = 1; + + return pdu; +} + +void +spdk_scsi_task_process_null_lun(struct spdk_scsi_task *task) +{ +} + +void +spdk_scsi_dev_queue_task(struct spdk_scsi_dev *dev, + struct spdk_scsi_task *task) +{ +} + +struct spdk_scsi_port * +spdk_scsi_dev_find_port_by_id(struct spdk_scsi_dev *dev, uint64_t id) +{ + return NULL; +} + +void +spdk_scsi_dev_queue_mgmt_task(struct spdk_scsi_dev *dev, + struct spdk_scsi_task *task, + enum spdk_scsi_task_func func) +{ +} + +const char * +spdk_scsi_dev_get_name(const struct spdk_scsi_dev *dev) +{ + if (dev != NULL) { + return dev->name; + } + + return NULL; +} + +void +spdk_iscsi_acceptor_start(struct spdk_iscsi_portal *p) +{ +} + +void +spdk_iscsi_acceptor_stop(struct spdk_iscsi_portal *p) +{ +} + +struct spdk_sock * +spdk_sock_listen(const char *ip, int port) +{ + static int g_sock; + + return (struct spdk_sock *)&g_sock; +} + +int +spdk_sock_close(struct spdk_sock **sock) +{ + *sock = NULL; + + return 0; +} + +static struct spdk_cpuset *g_app_core_mask; + +struct spdk_cpuset * +spdk_app_get_core_mask(void) +{ + int i; + if (!g_app_core_mask) { + g_app_core_mask = spdk_cpuset_alloc(); + for (i = 0; i < SPDK_CPUSET_SIZE; i++) { + spdk_cpuset_set_cpu(g_app_core_mask, i, true); + } + } + return g_app_core_mask; +} + +int +spdk_app_parse_core_mask(const char *mask, struct spdk_cpuset *cpumask) +{ + int rc; + + if (mask == NULL || cpumask == NULL) { + return -1; + } + + rc = spdk_cpuset_parse(cpumask, mask); + if (rc < 0) { + return -1; + } + return 0; +} + +uint32_t +spdk_env_get_current_core(void) +{ + return 0; +} + +struct spdk_event * +spdk_event_allocate(uint32_t core, spdk_event_fn fn, void *arg1, void *arg2) +{ + return NULL; +} + +struct spdk_scsi_dev * + spdk_scsi_dev_construct(const char *name, const char **bdev_name_list, + int *lun_id_list, int num_luns, uint8_t protocol_id, + void (*hotremove_cb)(const struct spdk_scsi_lun *, void *), + void *hotremove_ctx) +{ + return NULL; +} + +void +spdk_scsi_dev_destruct(struct spdk_scsi_dev *dev) +{ +} + +int +spdk_scsi_dev_add_port(struct spdk_scsi_dev *dev, uint64_t id, const char *name) +{ + return 0; +} + +int +spdk_iscsi_drop_conns(struct spdk_iscsi_conn *conn, const char *conn_match, + int drop_all) +{ + return 0; +} + +int +spdk_scsi_dev_delete_port(struct spdk_scsi_dev *dev, uint64_t id) +{ + return 0; +} + +void +spdk_shutdown_iscsi_conns(void) +{ +} + +void +spdk_iscsi_task_cpl(struct spdk_scsi_task *scsi_task) +{ + +} + +void +spdk_iscsi_task_mgmt_cpl(struct spdk_scsi_task *scsi_task) +{ + +} + +int +spdk_iscsi_conn_read_data(struct spdk_iscsi_conn *conn, int bytes, + void *buf) +{ + return 0; +} + +void +spdk_iscsi_conn_write_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + TAILQ_INSERT_TAIL(&g_write_pdu_list, pdu, tailq); +} + +void +spdk_iscsi_conn_logout(struct spdk_iscsi_conn *conn) +{ +} + +void +spdk_scsi_task_set_status(struct spdk_scsi_task *task, int sc, int sk, int asc, int ascq) +{ +} + +void +spdk_scsi_task_set_data(struct spdk_scsi_task *task, void *data, uint32_t len) +{ + SPDK_CU_ASSERT_FATAL(task->iovs != NULL); + task->iovs[0].iov_base = data; + task->iovs[0].iov_len = len; +} diff --git a/src/spdk/test/unit/lib/iscsi/conn.c/.gitignore b/src/spdk/test/unit/lib/iscsi/conn.c/.gitignore new file mode 100644 index 00000000..3bb0afd8 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/conn.c/.gitignore @@ -0,0 +1 @@ +conn_ut diff --git a/src/spdk/test/unit/lib/iscsi/conn.c/Makefile b/src/spdk/test/unit/lib/iscsi/conn.c/Makefile new file mode 100644 index 00000000..96f2f5d7 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/conn.c/Makefile @@ -0,0 +1,42 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +SPDK_LIB_LIST = trace + +TEST_FILE = conn_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/iscsi/conn.c/conn_ut.c b/src/spdk/test/unit/lib/iscsi/conn.c/conn_ut.c new file mode 100644 index 00000000..88d23423 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/conn.c/conn_ut.c @@ -0,0 +1,404 @@ +/*- + * 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 "common/lib/test_env.c" +#include "spdk_cunit.h" + +#include "iscsi/conn.c" + +SPDK_LOG_REGISTER_COMPONENT("iscsi", SPDK_LOG_ISCSI) + +#define DMIN32(A,B) ((uint32_t) ((uint32_t)(A) > (uint32_t)(B) ? (uint32_t)(B) : (uint32_t)(A))) + +struct spdk_iscsi_globals g_spdk_iscsi; +static TAILQ_HEAD(, spdk_iscsi_task) g_ut_read_tasks = TAILQ_HEAD_INITIALIZER(g_ut_read_tasks); + +int +spdk_app_get_shm_id(void) +{ + return 0; +} + +uint32_t +spdk_env_get_current_core(void) +{ + return 0; +} + +uint32_t +spdk_env_get_first_core(void) +{ + return 0; +} + +uint32_t +spdk_env_get_last_core(void) +{ + return 0; +} + +uint32_t +spdk_env_get_next_core(uint32_t prev_core) +{ + return 0; +} + +struct spdk_event * +spdk_event_allocate(uint32_t lcore, spdk_event_fn fn, void *arg1, void *arg2) +{ + return NULL; +} + +void +spdk_event_call(struct spdk_event *event) +{ +} + +int +spdk_sock_getaddr(struct spdk_sock *sock, char *saddr, int slen, uint16_t *sport, + char *caddr, int clen, uint16_t *cport) +{ + return 0; +} + +int +spdk_sock_close(struct spdk_sock **sock) +{ + *sock = NULL; + return 0; +} + +ssize_t +spdk_sock_recv(struct spdk_sock *sock, void *buf, size_t len) +{ + return 0; +} + +ssize_t +spdk_sock_writev(struct spdk_sock *sock, struct iovec *iov, int iovcnt) +{ + return 0; +} + +int +spdk_sock_set_recvlowat(struct spdk_sock *s, int nbytes) +{ + return 0; +} + +int +spdk_sock_set_recvbuf(struct spdk_sock *sock, int sz) +{ + return 0; +} + +int +spdk_sock_set_sendbuf(struct spdk_sock *sock, int sz) +{ + return 0; +} + +int +spdk_sock_group_add_sock(struct spdk_sock_group *group, struct spdk_sock *sock, + spdk_sock_cb cb_fn, void *cb_arg) +{ + return 0; +} + +int +spdk_sock_group_remove_sock(struct spdk_sock_group *group, struct spdk_sock *sock) +{ + return 0; +} + +void +spdk_scsi_task_put(struct spdk_scsi_task *task) +{ +} + +struct spdk_scsi_lun * +spdk_scsi_dev_get_lun(struct spdk_scsi_dev *dev, int lun_id) +{ + return NULL; +} + +bool +spdk_scsi_dev_has_pending_tasks(const struct spdk_scsi_dev *dev) +{ + return true; +} + +int +spdk_scsi_lun_open(struct spdk_scsi_lun *lun, spdk_scsi_remove_cb_t hotremove_cb, + void *hotremove_ctx, struct spdk_scsi_desc **desc) +{ + return 0; +} + +void +spdk_scsi_lun_close(struct spdk_scsi_desc *desc) +{ +} + +int spdk_scsi_lun_allocate_io_channel(struct spdk_scsi_desc *desc) +{ + return 0; +} + +void spdk_scsi_lun_free_io_channel(struct spdk_scsi_desc *desc) +{ +} + +int +spdk_scsi_lun_get_id(const struct spdk_scsi_lun *lun) +{ + return 0; +} + +const char * +spdk_scsi_port_get_name(const struct spdk_scsi_port *port) +{ + return NULL; +} + +void +spdk_scsi_task_copy_status(struct spdk_scsi_task *dst, + struct spdk_scsi_task *src) +{ +} + +void +spdk_put_pdu(struct spdk_iscsi_pdu *pdu) +{ +} + +void +spdk_iscsi_param_free(struct iscsi_param *params) +{ +} + +int +spdk_iscsi_conn_params_init(struct iscsi_param **params) +{ + return 0; +} + +void spdk_clear_all_transfer_task(struct spdk_iscsi_conn *conn, + struct spdk_scsi_lun *lun) +{ +} + +int +spdk_iscsi_build_iovecs(struct spdk_iscsi_conn *conn, struct iovec *iovec, + struct spdk_iscsi_pdu *pdu) +{ + return 0; +} + +bool spdk_iscsi_is_deferred_free_pdu(struct spdk_iscsi_pdu *pdu) +{ + return false; +} + +void spdk_iscsi_task_response(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task) +{ +} + +void +spdk_iscsi_task_mgmt_response(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task) +{ +} + +void spdk_iscsi_send_nopin(struct spdk_iscsi_conn *conn) +{ +} + +int +spdk_iscsi_execute(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + return 0; +} + +void spdk_del_transfer_task(struct spdk_iscsi_conn *conn, uint32_t task_tag) +{ +} + +int spdk_iscsi_conn_handle_queued_datain_tasks(struct spdk_iscsi_conn *conn) +{ + return 0; +} + +int +spdk_iscsi_read_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu **_pdu) +{ + return 0; +} + +void spdk_free_sess(struct spdk_iscsi_sess *sess) +{ +} + +int +spdk_iscsi_tgt_node_cleanup_luns(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target) +{ + return 0; +} + +void +spdk_shutdown_iscsi_conns_done(void) +{ +} + +static struct spdk_iscsi_task * +ut_conn_task_get(struct spdk_iscsi_task *parent) +{ + struct spdk_iscsi_task *task; + + task = calloc(1, sizeof(*task)); + SPDK_CU_ASSERT_FATAL(task != NULL); + + if (parent) { + task->parent = parent; + } + return task; +} + +static void +ut_conn_create_read_tasks(int transfer_len) +{ + struct spdk_iscsi_task *task, *subtask; + int32_t remaining_size = 0; + + task = ut_conn_task_get(NULL); + + task->scsi.transfer_len = transfer_len; + task->scsi.offset = 0; + task->scsi.length = DMIN32(SPDK_BDEV_LARGE_BUF_MAX_SIZE, task->scsi.transfer_len); + task->scsi.status = SPDK_SCSI_STATUS_GOOD; + + remaining_size = task->scsi.transfer_len - task->scsi.length; + task->current_datain_offset = 0; + + if (remaining_size == 0) { + TAILQ_INSERT_TAIL(&g_ut_read_tasks, task, link); + return; + } + + while (1) { + if (task->current_datain_offset == 0) { + task->current_datain_offset = task->scsi.length; + TAILQ_INSERT_TAIL(&g_ut_read_tasks, task, link); + continue; + } + + if (task->current_datain_offset < task->scsi.transfer_len) { + remaining_size = task->scsi.transfer_len - task->current_datain_offset; + + subtask = ut_conn_task_get(task); + + subtask->scsi.offset = task->current_datain_offset; + subtask->scsi.length = DMIN32(SPDK_BDEV_LARGE_BUF_MAX_SIZE, remaining_size); + subtask->scsi.status = SPDK_SCSI_STATUS_GOOD; + + task->current_datain_offset += subtask->scsi.length; + + TAILQ_INSERT_TAIL(&g_ut_read_tasks, subtask, link); + } + + if (task->current_datain_offset == task->scsi.transfer_len) { + break; + } + } +} + +static void +read_task_split_in_order_case(void) +{ + struct spdk_iscsi_task *primary, *task, *tmp; + + ut_conn_create_read_tasks(SPDK_BDEV_LARGE_BUF_MAX_SIZE * 8); + + TAILQ_FOREACH(task, &g_ut_read_tasks, link) { + primary = spdk_iscsi_task_get_primary(task); + process_read_task_completion(NULL, task, primary); + } + + primary = TAILQ_FIRST(&g_ut_read_tasks); + SPDK_CU_ASSERT_FATAL(primary != NULL); + + if (primary != NULL) { + CU_ASSERT(primary->bytes_completed == primary->scsi.transfer_len); + } + + TAILQ_FOREACH_SAFE(task, &g_ut_read_tasks, link, tmp) { + TAILQ_REMOVE(&g_ut_read_tasks, task, link); + free(task); + } + + CU_ASSERT(TAILQ_EMPTY(&g_ut_read_tasks)); +} + +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("conn_suite", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "read task split in order", read_task_split_in_order_case) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/iscsi/init_grp.c/.gitignore b/src/spdk/test/unit/lib/iscsi/init_grp.c/.gitignore new file mode 100644 index 00000000..8fbc2b63 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/init_grp.c/.gitignore @@ -0,0 +1 @@ +init_grp_ut diff --git a/src/spdk/test/unit/lib/iscsi/init_grp.c/Makefile b/src/spdk/test/unit/lib/iscsi/init_grp.c/Makefile new file mode 100644 index 00000000..9c87ef55 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/init_grp.c/Makefile @@ -0,0 +1,41 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +SPDK_LIB_LIST = conf +TEST_FILE = init_grp_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp.conf b/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp.conf new file mode 100644 index 00000000..aaa660de --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp.conf @@ -0,0 +1,31 @@ +[IG_Valid0] +# Success is expected. + InitiatorName iqn.2017-10.spdk.io:0001 + Netmask 192.168.2.0 + +[IG_Valid1] +# Success is expected. + InitiatorName iqn.2017-10.spdk.io:0001 + Netmask 192.168.2.0 + Netmask 192.168.2.1 + +[IG_Valid2] +# Success is expected. + InitiatorName iqn.2017-10.spdk.io:0001 + InitiatorName iqn.2017-10.spdk.io:0002 + Netmask 192.168.2.0 + +[IG_Valid3] +# Success is expected. + InitiatorName iqn.2017-10.spdk.io:0001 + InitiatorName iqn.2017-10.spdk.io:0002 + Netmask 192.168.2.0 + Netmask 192.168.2.1 + +[IG_Invalid0] +# Failure is expected. + InitiatorName iqn.2017-10.spdk.io:0001 + +[IG_Invalid1] +# Failure is expected. + Netmask 192.168.2.0 diff --git a/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp_ut.c b/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp_ut.c new file mode 100644 index 00000000..5fcce81b --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp_ut.c @@ -0,0 +1,702 @@ +/*- + * 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 "CUnit/Basic.h" + +#include "iscsi/init_grp.c" +#include "unit/lib/json_mock.c" + +SPDK_LOG_REGISTER_COMPONENT("iscsi", SPDK_LOG_ISCSI) + +struct spdk_iscsi_globals g_spdk_iscsi; + +const char *config_file; + +static int +test_setup(void) +{ + TAILQ_INIT(&g_spdk_iscsi.ig_head); + return 0; +} + +static void +create_from_config_file_cases(void) +{ + struct spdk_conf *config; + struct spdk_conf_section *sp; + char section_name[64]; + int section_index; + int rc; + + config = spdk_conf_allocate(); + + rc = spdk_conf_read(config, config_file); + CU_ASSERT(rc == 0); + + section_index = 0; + while (true) { + snprintf(section_name, sizeof(section_name), "IG_Valid%d", section_index); + + sp = spdk_conf_find_section(config, section_name); + if (sp == NULL) { + break; + } + + rc = spdk_iscsi_parse_init_grp(sp); + CU_ASSERT(rc == 0); + + spdk_iscsi_init_grps_destroy(); + + section_index++; + } + + section_index = 0; + while (true) { + snprintf(section_name, sizeof(section_name), "IG_Invalid%d", section_index); + + sp = spdk_conf_find_section(config, section_name); + if (sp == NULL) { + break; + } + + rc = spdk_iscsi_parse_init_grp(sp); + CU_ASSERT(rc != 0); + + spdk_iscsi_init_grps_destroy(); + + section_index++; + } + + spdk_conf_free(config); +} + + +static void +create_initiator_group_success_case(void) +{ + struct spdk_iscsi_init_grp *ig; + + ig = spdk_iscsi_init_grp_create(1); + CU_ASSERT(ig != NULL); + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +find_initiator_group_success_case(void) +{ + struct spdk_iscsi_init_grp *ig, *tmp; + int rc; + + ig = spdk_iscsi_init_grp_create(1); + CU_ASSERT(ig != NULL); + + rc = spdk_iscsi_init_grp_register(ig); + CU_ASSERT(rc == 0); + + ig = spdk_iscsi_init_grp_find_by_tag(1); + CU_ASSERT(ig != NULL); + + tmp = spdk_iscsi_init_grp_unregister(1); + CU_ASSERT(ig == tmp); + spdk_iscsi_init_grp_destroy(ig); + + ig = spdk_iscsi_init_grp_find_by_tag(1); + CU_ASSERT(ig == NULL); +} + +static void +register_initiator_group_twice_case(void) +{ + struct spdk_iscsi_init_grp *ig, *tmp; + int rc; + + ig = spdk_iscsi_init_grp_create(1); + CU_ASSERT(ig != NULL); + + rc = spdk_iscsi_init_grp_register(ig); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_init_grp_register(ig); + CU_ASSERT(rc != 0); + + ig = spdk_iscsi_init_grp_find_by_tag(1); + CU_ASSERT(ig != NULL); + + tmp = spdk_iscsi_init_grp_unregister(1); + CU_ASSERT(tmp == ig); + spdk_iscsi_init_grp_destroy(ig); + + ig = spdk_iscsi_init_grp_find_by_tag(1); + CU_ASSERT(ig == NULL); +} + +static void +add_initiator_name_success_case(void) +{ + + int rc; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_name *iname; + char *name1 = "iqn.2017-10.spdk.io:0001"; + char *name2 = "iqn.2017-10.spdk.io:0002"; + + ig = spdk_iscsi_init_grp_create(1); + CU_ASSERT(ig != NULL); + + /* add two different names to the empty name list */ + rc = spdk_iscsi_init_grp_add_initiator(ig, name1); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_init_grp_add_initiator(ig, name2); + CU_ASSERT(rc == 0); + + /* check if two names are added correctly. */ + iname = spdk_iscsi_init_grp_find_initiator(ig, name1); + CU_ASSERT(iname != NULL); + + iname = spdk_iscsi_init_grp_find_initiator(ig, name2); + CU_ASSERT(iname != NULL); + + /* restore the initial state */ + rc = spdk_iscsi_init_grp_delete_initiator(ig, name1); + CU_ASSERT(rc == 0); + + iname = spdk_iscsi_init_grp_find_initiator(ig, name1); + CU_ASSERT(iname == NULL); + + rc = spdk_iscsi_init_grp_delete_initiator(ig, name2); + CU_ASSERT(rc == 0); + + iname = spdk_iscsi_init_grp_find_initiator(ig, name2); + CU_ASSERT(iname == NULL); + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +add_initiator_name_fail_case(void) +{ + int rc; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_name *iname; + char *name1 = "iqn.2017-10.spdk.io:0001"; + + ig = spdk_iscsi_init_grp_create(1); + CU_ASSERT(ig != NULL); + + /* add an name to the full name list */ + ig->ninitiators = MAX_INITIATOR; + + rc = spdk_iscsi_init_grp_add_initiator(ig, name1); + CU_ASSERT(rc != 0); + + ig->ninitiators = 0; + + /* add the same name to the name list twice */ + rc = spdk_iscsi_init_grp_add_initiator(ig, name1); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_init_grp_add_initiator(ig, name1); + CU_ASSERT(rc != 0); + + /* restore the initial state */ + rc = spdk_iscsi_init_grp_delete_initiator(ig, name1); + CU_ASSERT(rc == 0); + + iname = spdk_iscsi_init_grp_find_initiator(ig, name1); + CU_ASSERT(iname == NULL); + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +delete_all_initiator_names_success_case(void) +{ + int rc; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_name *iname; + char *name1 = "iqn.2017-10.spdk.io:0001"; + char *name2 = "iqn.2017-10.spdk.io:0002"; + + ig = spdk_iscsi_init_grp_create(1); + CU_ASSERT(ig != NULL); + + /* add two different names to the empty name list */ + rc = spdk_iscsi_init_grp_add_initiator(ig, name1); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_init_grp_add_initiator(ig, name2); + CU_ASSERT(rc == 0); + + /* delete all initiator names */ + spdk_iscsi_init_grp_delete_all_initiators(ig); + + /* check if two names are deleted correctly. */ + iname = spdk_iscsi_init_grp_find_initiator(ig, name1); + CU_ASSERT(iname == NULL); + + iname = spdk_iscsi_init_grp_find_initiator(ig, name2); + CU_ASSERT(iname == NULL); + + /* restore the initial state */ + spdk_iscsi_init_grp_destroy(ig); +} + +static void +add_netmask_success_case(void) +{ + int rc; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_netmask *imask; + char *netmask1 = "192.168.2.0"; + char *netmask2 = "192.168.2.1"; + + ig = spdk_iscsi_init_grp_create(1); + CU_ASSERT(ig != NULL); + + /* add two different netmasks to the empty netmask list */ + rc = spdk_iscsi_init_grp_add_netmask(ig, netmask1); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_init_grp_add_netmask(ig, netmask2); + CU_ASSERT(rc == 0); + + /* check if two netmasks are added correctly. */ + imask = spdk_iscsi_init_grp_find_netmask(ig, netmask1); + CU_ASSERT(imask != NULL); + + imask = spdk_iscsi_init_grp_find_netmask(ig, netmask2); + CU_ASSERT(imask != NULL); + + /* restore the initial state */ + rc = spdk_iscsi_init_grp_delete_netmask(ig, netmask1); + CU_ASSERT(rc == 0); + + imask = spdk_iscsi_init_grp_find_netmask(ig, netmask1); + CU_ASSERT(imask == NULL); + + rc = spdk_iscsi_init_grp_delete_netmask(ig, netmask2); + CU_ASSERT(rc == 0); + + imask = spdk_iscsi_init_grp_find_netmask(ig, netmask2); + CU_ASSERT(imask == NULL); + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +add_netmask_fail_case(void) +{ + int rc; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_netmask *imask; + char *netmask1 = "192.168.2.0"; + + ig = spdk_iscsi_init_grp_create(1); + CU_ASSERT(ig != NULL); + + /* add an netmask to the full netmask list */ + ig->nnetmasks = MAX_NETMASK; + + rc = spdk_iscsi_init_grp_add_netmask(ig, netmask1); + CU_ASSERT(rc != 0); + + ig->nnetmasks = 0; + + /* add the same netmask to the netmask list twice */ + rc = spdk_iscsi_init_grp_add_netmask(ig, netmask1); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_init_grp_add_netmask(ig, netmask1); + CU_ASSERT(rc != 0); + + /* restore the initial state */ + rc = spdk_iscsi_init_grp_delete_netmask(ig, netmask1); + CU_ASSERT(rc == 0); + + imask = spdk_iscsi_init_grp_find_netmask(ig, netmask1); + CU_ASSERT(imask == NULL); + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +delete_all_netmasks_success_case(void) +{ + int rc; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_netmask *imask; + char *netmask1 = "192.168.2.0"; + char *netmask2 = "192.168.2.1"; + + ig = spdk_iscsi_init_grp_create(1); + CU_ASSERT(ig != NULL); + + /* add two different netmasks to the empty netmask list */ + rc = spdk_iscsi_init_grp_add_netmask(ig, netmask1); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_init_grp_add_netmask(ig, netmask2); + CU_ASSERT(rc == 0); + + /* delete all netmasks */ + spdk_iscsi_init_grp_delete_all_netmasks(ig); + + /* check if two netmasks are deleted correctly. */ + imask = spdk_iscsi_init_grp_find_netmask(ig, netmask1); + CU_ASSERT(imask == NULL); + + imask = spdk_iscsi_init_grp_find_netmask(ig, netmask2); + CU_ASSERT(imask == NULL); + + /* restore the initial state */ + spdk_iscsi_init_grp_destroy(ig); +} + +static void +initiator_name_overwrite_all_to_any_case(void) +{ + int rc; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_name *iname; + char *all = "ALL"; + char *any = "ANY"; + char *all_not = "!ALL"; + char *any_not = "!ANY"; + + ig = spdk_iscsi_init_grp_create(1); + CU_ASSERT(ig != NULL); + + rc = spdk_iscsi_init_grp_add_initiator(ig, all); + CU_ASSERT(rc == 0); + + iname = spdk_iscsi_init_grp_find_initiator(ig, all); + CU_ASSERT(iname == NULL); + + iname = spdk_iscsi_init_grp_find_initiator(ig, any); + CU_ASSERT(iname != NULL); + + rc = spdk_iscsi_init_grp_delete_initiator(ig, any); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_init_grp_add_initiator(ig, all_not); + CU_ASSERT(rc == 0); + + iname = spdk_iscsi_init_grp_find_initiator(ig, all_not); + CU_ASSERT(iname == NULL); + + iname = spdk_iscsi_init_grp_find_initiator(ig, any_not); + CU_ASSERT(iname != NULL); + + rc = spdk_iscsi_init_grp_delete_initiator(ig, any_not); + CU_ASSERT(rc == 0); + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +netmask_overwrite_all_to_any_case(void) +{ + int rc; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_netmask *imask; + char *all = "ALL"; + char *any = "ANY"; + + ig = spdk_iscsi_init_grp_create(1); + CU_ASSERT(ig != NULL); + + rc = spdk_iscsi_init_grp_add_netmask(ig, all); + CU_ASSERT(rc == 0); + + imask = spdk_iscsi_init_grp_find_netmask(ig, all); + CU_ASSERT(imask == NULL); + + imask = spdk_iscsi_init_grp_find_netmask(ig, any); + CU_ASSERT(imask != NULL); + + rc = spdk_iscsi_init_grp_delete_netmask(ig, any); + CU_ASSERT(rc == 0); + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +add_delete_initiator_names_case(void) +{ + int rc, i; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_name *iname; + char *names[3] = {"iqn.2018-02.spdk.io:0001", "iqn.2018-02.spdk.io:0002", "iqn.2018-02.spdk.io:0003"}; + + ig = spdk_iscsi_init_grp_create(1); + SPDK_CU_ASSERT_FATAL(ig != NULL); + + rc = spdk_iscsi_init_grp_add_initiators(ig, 3, names); + CU_ASSERT(rc == 0); + + for (i = 0; i < 3; i++) { + iname = spdk_iscsi_init_grp_find_initiator(ig, names[i]); + CU_ASSERT(iname != NULL); + } + + rc = spdk_iscsi_init_grp_delete_initiators(ig, 3, names); + CU_ASSERT(rc == 0); + + if (ig != NULL) { + CU_ASSERT(TAILQ_EMPTY(&ig->initiator_head)); + } + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +add_duplicated_initiator_names_case(void) +{ + int rc; + struct spdk_iscsi_init_grp *ig; + char *names[3] = {"iqn.2018-02.spdk.io:0001", "iqn.2018-02.spdk.io:0002", "iqn.2018-02.spdk.io:0001"}; + + ig = spdk_iscsi_init_grp_create(1); + SPDK_CU_ASSERT_FATAL(ig != NULL); + + rc = spdk_iscsi_init_grp_add_initiators(ig, 3, names); + CU_ASSERT(rc != 0); + + if (ig != NULL) { + CU_ASSERT(TAILQ_EMPTY(&ig->initiator_head)); + } + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +delete_nonexisting_initiator_names_case(void) +{ + int rc, i; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_name *iname; + char *names1[3] = {"iqn.2018-02.spdk.io:0001", "iqn.2018-02.spdk.io:0002", "iqn.2018-02.spdk.io:0003"}; + char *names2[3] = {"iqn.2018-02.spdk.io:0001", "iqn.2018-02.spdk.io:0002", "iqn.2018-02.spdk.io:0004"}; + + ig = spdk_iscsi_init_grp_create(1); + SPDK_CU_ASSERT_FATAL(ig != NULL); + + rc = spdk_iscsi_init_grp_add_initiators(ig, 3, names1); + CU_ASSERT(rc == 0); + + for (i = 0; i < 3; i++) { + iname = spdk_iscsi_init_grp_find_initiator(ig, names1[i]); + CU_ASSERT(iname != NULL); + } + + rc = spdk_iscsi_init_grp_delete_initiators(ig, 3, names2); + CU_ASSERT(rc != 0); + + for (i = 0; i < 3; i++) { + iname = spdk_iscsi_init_grp_find_initiator(ig, names1[i]); + CU_ASSERT(iname != NULL); + } + + rc = spdk_iscsi_init_grp_delete_initiators(ig, 3, names1); + CU_ASSERT(rc == 0); + + if (ig != NULL) { + CU_ASSERT(TAILQ_EMPTY(&ig->initiator_head)); + } + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +add_delete_netmasks_case(void) +{ + int rc, i; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_netmask *netmask; + char *netmasks[3] = {"192.168.2.0", "192.168.2.1", "192.168.2.2"}; + + ig = spdk_iscsi_init_grp_create(1); + SPDK_CU_ASSERT_FATAL(ig != NULL); + + rc = spdk_iscsi_init_grp_add_netmasks(ig, 3, netmasks); + CU_ASSERT(rc == 0); + + for (i = 0; i < 3; i++) { + netmask = spdk_iscsi_init_grp_find_netmask(ig, netmasks[i]); + CU_ASSERT(netmask != NULL); + } + + rc = spdk_iscsi_init_grp_delete_netmasks(ig, 3, netmasks); + CU_ASSERT(rc == 0); + + if (ig != NULL) { + CU_ASSERT(TAILQ_EMPTY(&ig->netmask_head)); + } + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +add_duplicated_netmasks_case(void) +{ + int rc; + struct spdk_iscsi_init_grp *ig; + char *netmasks[3] = {"192.168.2.0", "192.168.2.1", "192.168.2.0"}; + + ig = spdk_iscsi_init_grp_create(1); + SPDK_CU_ASSERT_FATAL(ig != NULL); + + rc = spdk_iscsi_init_grp_add_netmasks(ig, 3, netmasks); + CU_ASSERT(rc != 0); + + if (ig != NULL) { + CU_ASSERT(TAILQ_EMPTY(&ig->netmask_head)); + } + + spdk_iscsi_init_grp_destroy(ig); +} + +static void +delete_nonexisting_netmasks_case(void) +{ + int rc, i; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_netmask *netmask; + char *netmasks1[3] = {"192.168.2.0", "192.168.2.1", "192.168.2.2"}; + char *netmasks2[3] = {"192.168.2.0", "192.168.2.1", "192.168.2.3"}; + + ig = spdk_iscsi_init_grp_create(1); + SPDK_CU_ASSERT_FATAL(ig != NULL); + + rc = spdk_iscsi_init_grp_add_netmasks(ig, 3, netmasks1); + CU_ASSERT(rc == 0); + + for (i = 0; i < 3; i++) { + netmask = spdk_iscsi_init_grp_find_netmask(ig, netmasks1[i]); + CU_ASSERT(netmask != NULL); + } + + rc = spdk_iscsi_init_grp_delete_netmasks(ig, 3, netmasks2); + CU_ASSERT(rc != 0); + + for (i = 0; i < 3; i++) { + netmask = spdk_iscsi_init_grp_find_netmask(ig, netmasks1[i]); + CU_ASSERT(netmask != NULL); + } + + rc = spdk_iscsi_init_grp_delete_netmasks(ig, 3, netmasks1); + CU_ASSERT(rc == 0); + + if (ig != NULL) { + CU_ASSERT(TAILQ_EMPTY(&ig->netmask_head)); + } + + spdk_iscsi_init_grp_destroy(ig); +} + + +int +main(int argc, char **argv) +{ + CU_pSuite suite = NULL; + unsigned int num_failures; + + if (argc < 2) { + fprintf(stderr, "usage: %s <config file>\n", argv[0]); + exit(1); + } + + if (CU_initialize_registry() != CUE_SUCCESS) { + return CU_get_error(); + } + + config_file = argv[1]; + + suite = CU_add_suite("init_grp_suite", test_setup, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "create from config file cases", + create_from_config_file_cases) == NULL + || CU_add_test(suite, "create initiator group success case", + create_initiator_group_success_case) == NULL + || CU_add_test(suite, "find initiator group success case", + find_initiator_group_success_case) == NULL + || CU_add_test(suite, "register initiator group twice case", + register_initiator_group_twice_case) == NULL + || CU_add_test(suite, "add initiator name success case", + add_initiator_name_success_case) == NULL + || CU_add_test(suite, "add initiator name fail case", + add_initiator_name_fail_case) == NULL + || CU_add_test(suite, "delete all initiator names success case", + delete_all_initiator_names_success_case) == NULL + || CU_add_test(suite, "add initiator netmask success case", + add_netmask_success_case) == NULL + || CU_add_test(suite, "add initiator netmask fail case", + add_netmask_fail_case) == NULL + || CU_add_test(suite, "delete all initiator netmasks success case", + delete_all_netmasks_success_case) == NULL + || CU_add_test(suite, "overwrite all to any for name case", + initiator_name_overwrite_all_to_any_case) == NULL + || CU_add_test(suite, "overwrite all to any for netmask case", + netmask_overwrite_all_to_any_case) == NULL + || CU_add_test(suite, "add/delete initiator names case", + add_delete_initiator_names_case) == NULL + || CU_add_test(suite, "add duplicated initiator names case", + add_duplicated_initiator_names_case) == NULL + || CU_add_test(suite, "delete nonexisting initiator names case", + delete_nonexisting_initiator_names_case) == NULL + || CU_add_test(suite, "add/delete netmasks case", + add_delete_netmasks_case) == NULL + || CU_add_test(suite, "add duplicated netmasks case", + add_duplicated_netmasks_case) == NULL + || CU_add_test(suite, "delete nonexisting netmasks case", + delete_nonexisting_netmasks_case) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/iscsi/iscsi.c/.gitignore b/src/spdk/test/unit/lib/iscsi/iscsi.c/.gitignore new file mode 100644 index 00000000..4d41887c --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/iscsi.c/.gitignore @@ -0,0 +1 @@ +iscsi_ut diff --git a/src/spdk/test/unit/lib/iscsi/iscsi.c/Makefile b/src/spdk/test/unit/lib/iscsi/iscsi.c/Makefile new file mode 100644 index 00000000..bc9a9d8b --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/iscsi.c/Makefile @@ -0,0 +1,48 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +SPDK_LIB_LIST = trace conf util + +SCSI_OBJS = port +ISCSI_OBJS = md5 param +LIBS += $(SCSI_OBJS:%=$(SPDK_ROOT_DIR)/lib/scsi/%.o) +LIBS += $(ISCSI_OBJS:%=$(SPDK_ROOT_DIR)/lib/iscsi/%.o) +LIBS += -lcunit $(ENV_LINKER_ARGS) + +TEST_FILE = iscsi_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c b/src/spdk/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c new file mode 100644 index 00000000..4038a1e4 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c @@ -0,0 +1,972 @@ +/*- + * 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/endian.h" +#include "spdk/scsi.h" +#include "spdk_cunit.h" + +#include "CUnit/Basic.h" + +#include "iscsi/iscsi.c" + +#include "../common.c" +#include "iscsi/acceptor.h" +#include "iscsi/portal_grp.h" +#include "scsi/scsi_internal.h" + +#define UT_TARGET_NAME1 "iqn.2017-11.spdk.io:t0001" +#define UT_TARGET_NAME2 "iqn.2017-11.spdk.io:t0002" +#define UT_INITIATOR_NAME1 "iqn.2017-11.spdk.io:i0001" +#define UT_INITIATOR_NAME2 "iqn.2017-11.spdk.io:i0002" + +struct spdk_iscsi_tgt_node * +spdk_iscsi_find_tgt_node(const char *target_name) +{ + if (strcasecmp(target_name, UT_TARGET_NAME1) == 0) { + return (struct spdk_iscsi_tgt_node *)1; + } else { + return NULL; + } +} + +bool +spdk_iscsi_tgt_node_access(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target, + const char *iqn, const char *addr) +{ + if (strcasecmp(conn->initiator_name, UT_INITIATOR_NAME1) == 0) { + return true; + } else { + return false; + } +} + +int +spdk_iscsi_send_tgts(struct spdk_iscsi_conn *conn, const char *iiqn, + const char *iaddr, + const char *tiqn, uint8_t *data, int alloc_len, int data_len) +{ + return 0; +} + +void +spdk_iscsi_portal_grp_close_all(void) +{ +} + +void +spdk_iscsi_conn_migration(struct spdk_iscsi_conn *conn) +{ +} + +void +spdk_iscsi_conn_free_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ +} + +int +spdk_iscsi_chap_get_authinfo(struct iscsi_chap_auth *auth, const char *authuser, + int ag_tag) +{ + return 0; +} + +int +spdk_scsi_lun_get_id(const struct spdk_scsi_lun *lun) +{ + return lun->id; +} + +bool +spdk_scsi_lun_is_removing(const struct spdk_scsi_lun *lun) +{ + return true; +} + +struct spdk_scsi_lun * +spdk_scsi_dev_get_lun(struct spdk_scsi_dev *dev, int lun_id) +{ + if (lun_id < 0 || lun_id >= SPDK_SCSI_DEV_MAX_LUN) { + return NULL; + } + + return dev->lun[lun_id]; +} + +static void +op_login_check_target_test(void) +{ + struct spdk_iscsi_conn conn; + struct spdk_iscsi_pdu rsp_pdu; + struct spdk_iscsi_tgt_node *target; + int rc; + + /* expect success */ + snprintf(conn.initiator_name, sizeof(conn.initiator_name), + "%s", UT_INITIATOR_NAME1); + + rc = spdk_iscsi_op_login_check_target(&conn, &rsp_pdu, + UT_TARGET_NAME1, &target); + CU_ASSERT(rc == 0); + + /* expect failure */ + snprintf(conn.initiator_name, sizeof(conn.initiator_name), + "%s", UT_INITIATOR_NAME1); + + rc = spdk_iscsi_op_login_check_target(&conn, &rsp_pdu, + UT_TARGET_NAME2, &target); + CU_ASSERT(rc != 0); + + /* expect failure */ + snprintf(conn.initiator_name, sizeof(conn.initiator_name), + "%s", UT_INITIATOR_NAME2); + + rc = spdk_iscsi_op_login_check_target(&conn, &rsp_pdu, + UT_TARGET_NAME1, &target); + CU_ASSERT(rc != 0); +} + +static void +maxburstlength_test(void) +{ + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn conn; + struct spdk_scsi_dev dev; + struct spdk_scsi_lun lun; + struct spdk_iscsi_pdu *req_pdu, *data_out_pdu, *r2t_pdu; + struct iscsi_bhs_scsi_req *req; + struct iscsi_bhs_r2t *r2t; + struct iscsi_bhs_data_out *data_out; + struct spdk_iscsi_pdu *response_pdu; + int rc; + + memset(&sess, 0, sizeof(sess)); + memset(&conn, 0, sizeof(conn)); + memset(&dev, 0, sizeof(dev)); + memset(&lun, 0, sizeof(lun)); + + req_pdu = spdk_get_pdu(); + data_out_pdu = spdk_get_pdu(); + + sess.ExpCmdSN = 0; + sess.MaxCmdSN = 64; + sess.session_type = SESSION_TYPE_NORMAL; + sess.MaxBurstLength = 1024; + + lun.id = 0; + + dev.lun[0] = &lun; + + conn.full_feature = 1; + conn.sess = &sess; + conn.dev = &dev; + conn.state = ISCSI_CONN_STATE_RUNNING; + TAILQ_INIT(&conn.write_pdu_list); + TAILQ_INIT(&conn.active_r2t_tasks); + + TAILQ_INIT(&g_write_pdu_list); + + req_pdu->bhs.opcode = ISCSI_OP_SCSI; + req_pdu->data_segment_len = 0; + + req = (struct iscsi_bhs_scsi_req *)&req_pdu->bhs; + + to_be32(&req->cmd_sn, 0); + to_be32(&req->expected_data_xfer_len, 1028); + to_be32(&req->itt, 0x1234); + req->write_bit = 1; + req->final_bit = 1; + + rc = spdk_iscsi_execute(&conn, req_pdu); + CU_ASSERT(rc == 0); + + response_pdu = TAILQ_FIRST(&g_write_pdu_list); + SPDK_CU_ASSERT_FATAL(response_pdu != NULL); + + /* + * Confirm that a correct R2T reply was sent in response to the + * SCSI request. + */ + TAILQ_REMOVE(&g_write_pdu_list, response_pdu, tailq); + CU_ASSERT(response_pdu->bhs.opcode == ISCSI_OP_R2T); + r2t = (struct iscsi_bhs_r2t *)&response_pdu->bhs; + CU_ASSERT(from_be32(&r2t->desired_xfer_len) == 1024); + CU_ASSERT(from_be32(&r2t->buffer_offset) == 0); + CU_ASSERT(from_be32(&r2t->itt) == 0x1234); + + data_out_pdu->bhs.opcode = ISCSI_OP_SCSI_DATAOUT; + data_out_pdu->bhs.flags = ISCSI_FLAG_FINAL; + data_out_pdu->data_segment_len = 1028; + data_out = (struct iscsi_bhs_data_out *)&data_out_pdu->bhs; + data_out->itt = r2t->itt; + data_out->ttt = r2t->ttt; + DSET24(data_out->data_segment_len, 1028); + + rc = spdk_iscsi_execute(&conn, data_out_pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + SPDK_CU_ASSERT_FATAL(response_pdu->task != NULL); + spdk_iscsi_task_disassociate_pdu(response_pdu->task); + spdk_iscsi_task_put(response_pdu->task); + spdk_put_pdu(response_pdu); + + r2t_pdu = TAILQ_FIRST(&g_write_pdu_list); + CU_ASSERT(r2t_pdu != NULL); + TAILQ_REMOVE(&g_write_pdu_list, r2t_pdu, tailq); + spdk_put_pdu(r2t_pdu); + + spdk_put_pdu(data_out_pdu); + spdk_put_pdu(req_pdu); +} + +static void +underflow_for_read_transfer_test(void) +{ + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn conn; + struct spdk_iscsi_task task; + struct spdk_iscsi_pdu *pdu; + struct iscsi_bhs_scsi_req *scsi_req; + struct iscsi_bhs_data_in *datah; + uint32_t residual_count = 0; + + TAILQ_INIT(&g_write_pdu_list); + + memset(&sess, 0, sizeof(sess)); + memset(&conn, 0, sizeof(conn)); + memset(&task, 0, sizeof(task)); + + sess.MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH; + + conn.sess = &sess; + conn.MaxRecvDataSegmentLength = 8192; + + pdu = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu != NULL); + + scsi_req = (struct iscsi_bhs_scsi_req *)&pdu->bhs; + scsi_req->read_bit = 1; + + spdk_iscsi_task_set_pdu(&task, pdu); + task.parent = NULL; + + task.scsi.iovs = &task.scsi.iov; + task.scsi.iovcnt = 1; + task.scsi.length = 512; + task.scsi.transfer_len = 512; + task.bytes_completed = 512; + task.scsi.data_transferred = 256; + task.scsi.status = SPDK_SCSI_STATUS_GOOD; + + spdk_iscsi_task_response(&conn, &task); + spdk_put_pdu(pdu); + + /* + * In this case, a SCSI Data-In PDU should contain the Status + * for the data transfer. + */ + to_be32(&residual_count, 256); + + pdu = TAILQ_FIRST(&g_write_pdu_list); + SPDK_CU_ASSERT_FATAL(pdu != NULL); + + CU_ASSERT(pdu->bhs.opcode == ISCSI_OP_SCSI_DATAIN); + + datah = (struct iscsi_bhs_data_in *)&pdu->bhs; + + CU_ASSERT(datah->flags == (ISCSI_DATAIN_UNDERFLOW | ISCSI_FLAG_FINAL | ISCSI_DATAIN_STATUS)); + CU_ASSERT(datah->res_cnt == residual_count); + + TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq); + spdk_put_pdu(pdu); + + CU_ASSERT(TAILQ_EMPTY(&g_write_pdu_list)); +} + +static void +underflow_for_zero_read_transfer_test(void) +{ + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn conn; + struct spdk_iscsi_task task; + struct spdk_iscsi_pdu *pdu; + struct iscsi_bhs_scsi_req *scsi_req; + struct iscsi_bhs_scsi_resp *resph; + uint32_t residual_count = 0, data_segment_len; + + TAILQ_INIT(&g_write_pdu_list); + + memset(&sess, 0, sizeof(sess)); + memset(&conn, 0, sizeof(conn)); + memset(&task, 0, sizeof(task)); + + sess.MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH; + + conn.sess = &sess; + conn.MaxRecvDataSegmentLength = 8192; + + pdu = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu != NULL); + + scsi_req = (struct iscsi_bhs_scsi_req *)&pdu->bhs; + scsi_req->read_bit = 1; + + spdk_iscsi_task_set_pdu(&task, pdu); + task.parent = NULL; + + task.scsi.length = 512; + task.scsi.transfer_len = 512; + task.bytes_completed = 512; + task.scsi.data_transferred = 0; + task.scsi.status = SPDK_SCSI_STATUS_GOOD; + + spdk_iscsi_task_response(&conn, &task); + spdk_put_pdu(pdu); + + /* + * In this case, only a SCSI Response PDU is expected and + * underflow must be set in it. + * */ + to_be32(&residual_count, 512); + + pdu = TAILQ_FIRST(&g_write_pdu_list); + SPDK_CU_ASSERT_FATAL(pdu != NULL); + + CU_ASSERT(pdu->bhs.opcode == ISCSI_OP_SCSI_RSP); + + resph = (struct iscsi_bhs_scsi_resp *)&pdu->bhs; + + CU_ASSERT(resph->flags == (ISCSI_SCSI_UNDERFLOW | 0x80)); + + data_segment_len = DGET24(resph->data_segment_len); + CU_ASSERT(data_segment_len == 0); + CU_ASSERT(resph->res_cnt == residual_count); + + TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq); + spdk_put_pdu(pdu); + + CU_ASSERT(TAILQ_EMPTY(&g_write_pdu_list)); +} + +static void +underflow_for_request_sense_test(void) +{ + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn conn; + struct spdk_iscsi_task task; + struct spdk_iscsi_pdu *pdu1, *pdu2; + struct iscsi_bhs_scsi_req *scsi_req; + struct iscsi_bhs_data_in *datah; + struct iscsi_bhs_scsi_resp *resph; + uint32_t residual_count = 0, data_segment_len; + + TAILQ_INIT(&g_write_pdu_list); + + memset(&sess, 0, sizeof(sess)); + memset(&conn, 0, sizeof(conn)); + memset(&task, 0, sizeof(task)); + + sess.MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH; + + conn.sess = &sess; + conn.MaxRecvDataSegmentLength = 8192; + + pdu1 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu1 != NULL); + + scsi_req = (struct iscsi_bhs_scsi_req *)&pdu1->bhs; + scsi_req->read_bit = 1; + + spdk_iscsi_task_set_pdu(&task, pdu1); + task.parent = NULL; + + task.scsi.iovs = &task.scsi.iov; + task.scsi.iovcnt = 1; + task.scsi.length = 512; + task.scsi.transfer_len = 512; + task.bytes_completed = 512; + + task.scsi.sense_data_len = 18; + task.scsi.data_transferred = 18; + task.scsi.status = SPDK_SCSI_STATUS_GOOD; + + spdk_iscsi_task_response(&conn, &task); + spdk_put_pdu(pdu1); + + /* + * In this case, a SCSI Data-In PDU and a SCSI Response PDU are returned. + * Sense data are set both in payload and sense area. + * The SCSI Data-In PDU sets FINAL and the SCSI Response PDU sets UNDERFLOW. + * + * Probably there will be different implementation but keeping current SPDK + * implementation by adding UT will be valuable for any implementation. + */ + to_be32(&residual_count, 494); + + pdu1 = TAILQ_FIRST(&g_write_pdu_list); + SPDK_CU_ASSERT_FATAL(pdu1 != NULL); + + CU_ASSERT(pdu1->bhs.opcode == ISCSI_OP_SCSI_DATAIN); + + datah = (struct iscsi_bhs_data_in *)&pdu1->bhs; + + CU_ASSERT(datah->flags == ISCSI_FLAG_FINAL); + + data_segment_len = DGET24(datah->data_segment_len); + CU_ASSERT(data_segment_len == 18); + CU_ASSERT(datah->res_cnt == 0); + + TAILQ_REMOVE(&g_write_pdu_list, pdu1, tailq); + spdk_put_pdu(pdu1); + + pdu2 = TAILQ_FIRST(&g_write_pdu_list); + /* inform scan-build (clang 6) that these pointers are not the same */ + SPDK_CU_ASSERT_FATAL(pdu1 != pdu2); + SPDK_CU_ASSERT_FATAL(pdu2 != NULL); + + CU_ASSERT(pdu2->bhs.opcode == ISCSI_OP_SCSI_RSP); + + resph = (struct iscsi_bhs_scsi_resp *)&pdu2->bhs; + + CU_ASSERT(resph->flags == (ISCSI_SCSI_UNDERFLOW | 0x80)); + + data_segment_len = DGET24(resph->data_segment_len); + CU_ASSERT(data_segment_len == task.scsi.sense_data_len + 2); + CU_ASSERT(resph->res_cnt == residual_count); + + TAILQ_REMOVE(&g_write_pdu_list, pdu2, tailq); + spdk_put_pdu(pdu2); + + CU_ASSERT(TAILQ_EMPTY(&g_write_pdu_list)); +} + +static void +underflow_for_check_condition_test(void) +{ + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn conn; + struct spdk_iscsi_task task; + struct spdk_iscsi_pdu *pdu; + struct iscsi_bhs_scsi_req *scsi_req; + struct iscsi_bhs_scsi_resp *resph; + uint32_t data_segment_len; + + TAILQ_INIT(&g_write_pdu_list); + + memset(&sess, 0, sizeof(sess)); + memset(&conn, 0, sizeof(conn)); + memset(&task, 0, sizeof(task)); + + sess.MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH; + + conn.sess = &sess; + conn.MaxRecvDataSegmentLength = 8192; + + pdu = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu != NULL); + + scsi_req = (struct iscsi_bhs_scsi_req *)&pdu->bhs; + scsi_req->read_bit = 1; + + spdk_iscsi_task_set_pdu(&task, pdu); + task.parent = NULL; + + task.scsi.iovs = &task.scsi.iov; + task.scsi.iovcnt = 1; + task.scsi.length = 512; + task.scsi.transfer_len = 512; + task.bytes_completed = 512; + + task.scsi.sense_data_len = 18; + task.scsi.data_transferred = 18; + task.scsi.status = SPDK_SCSI_STATUS_CHECK_CONDITION; + + spdk_iscsi_task_response(&conn, &task); + spdk_put_pdu(pdu); + + /* + * In this case, a SCSI Response PDU is returned. + * Sense data is set in sense area. + * Underflow is not set. + */ + pdu = TAILQ_FIRST(&g_write_pdu_list); + SPDK_CU_ASSERT_FATAL(pdu != NULL); + + CU_ASSERT(pdu->bhs.opcode == ISCSI_OP_SCSI_RSP); + + resph = (struct iscsi_bhs_scsi_resp *)&pdu->bhs; + + CU_ASSERT(resph->flags == 0x80); + + data_segment_len = DGET24(resph->data_segment_len); + CU_ASSERT(data_segment_len == task.scsi.sense_data_len + 2); + CU_ASSERT(resph->res_cnt == 0); + + TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq); + spdk_put_pdu(pdu); + + CU_ASSERT(TAILQ_EMPTY(&g_write_pdu_list)); +} + +static void +add_transfer_task_test(void) +{ + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn conn; + struct spdk_iscsi_task task; + struct spdk_iscsi_pdu *pdu, *tmp; + struct iscsi_bhs_r2t *r2th; + int rc, count = 0; + uint32_t buffer_offset, desired_xfer_len; + + memset(&sess, 0, sizeof(sess)); + memset(&conn, 0, sizeof(conn)); + memset(&task, 0, sizeof(task)); + + sess.MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH; /* 1M */ + sess.MaxOutstandingR2T = DEFAULT_MAXR2T; /* 4 */ + + conn.sess = &sess; + TAILQ_INIT(&conn.queued_r2t_tasks); + TAILQ_INIT(&conn.active_r2t_tasks); + + pdu = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu != NULL); + + pdu->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; /* 64K */ + task.scsi.transfer_len = 16 * 1024 * 1024; + spdk_iscsi_task_set_pdu(&task, pdu); + + /* The following tests if the task is queued because R2T tasks are full. */ + conn.pending_r2t = DEFAULT_MAXR2T; + + rc = spdk_add_transfer_task(&conn, &task); + + CU_ASSERT(rc == SPDK_SUCCESS); + CU_ASSERT(TAILQ_FIRST(&conn.queued_r2t_tasks) == &task); + + TAILQ_REMOVE(&conn.queued_r2t_tasks, &task, link); + CU_ASSERT(TAILQ_EMPTY(&conn.queued_r2t_tasks)); + + /* The following tests if multiple R2Ts are issued. */ + conn.pending_r2t = 0; + + rc = spdk_add_transfer_task(&conn, &task); + + CU_ASSERT(rc == SPDK_SUCCESS); + CU_ASSERT(TAILQ_FIRST(&conn.active_r2t_tasks) == &task); + + TAILQ_REMOVE(&conn.active_r2t_tasks, &task, link); + CU_ASSERT(TAILQ_EMPTY(&conn.active_r2t_tasks)); + + CU_ASSERT(conn.data_out_cnt == 255); + CU_ASSERT(conn.pending_r2t == 1); + CU_ASSERT(conn.outstanding_r2t_tasks[0] == &task); + CU_ASSERT(conn.ttt == 1); + + CU_ASSERT(task.data_out_cnt == 255); + CU_ASSERT(task.ttt == 1); + CU_ASSERT(task.outstanding_r2t == sess.MaxOutstandingR2T); + CU_ASSERT(task.next_r2t_offset == + pdu->data_segment_len + sess.MaxBurstLength * sess.MaxOutstandingR2T); + + + while (!TAILQ_EMPTY(&g_write_pdu_list)) { + tmp = TAILQ_FIRST(&g_write_pdu_list); + TAILQ_REMOVE(&g_write_pdu_list, tmp, tailq); + + r2th = (struct iscsi_bhs_r2t *)&tmp->bhs; + + buffer_offset = from_be32(&r2th->buffer_offset); + CU_ASSERT(buffer_offset == pdu->data_segment_len + sess.MaxBurstLength * count); + + desired_xfer_len = from_be32(&r2th->desired_xfer_len); + CU_ASSERT(desired_xfer_len == sess.MaxBurstLength); + + spdk_put_pdu(tmp); + count++; + } + + CU_ASSERT(count == DEFAULT_MAXR2T); + + spdk_put_pdu(pdu); +} + +static void +get_transfer_task_test(void) +{ + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn conn; + struct spdk_iscsi_task task1, task2, *task; + struct spdk_iscsi_pdu *pdu1, *pdu2, *pdu; + int rc; + + memset(&sess, 0, sizeof(sess)); + memset(&conn, 0, sizeof(conn)); + memset(&task1, 0, sizeof(task1)); + memset(&task2, 0, sizeof(task2)); + + sess.MaxBurstLength = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + sess.MaxOutstandingR2T = 1; + + conn.sess = &sess; + TAILQ_INIT(&conn.active_r2t_tasks); + + pdu1 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu1 != NULL); + + pdu1->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task1.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + spdk_iscsi_task_set_pdu(&task1, pdu1); + + rc = spdk_add_transfer_task(&conn, &task1); + CU_ASSERT(rc == SPDK_SUCCESS); + + pdu2 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu2 != NULL); + + pdu2->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task2.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + spdk_iscsi_task_set_pdu(&task2, pdu2); + + rc = spdk_add_transfer_task(&conn, &task2); + CU_ASSERT(rc == SPDK_SUCCESS); + + task = spdk_get_transfer_task(&conn, 1); + CU_ASSERT(task == &task1); + + task = spdk_get_transfer_task(&conn, 2); + CU_ASSERT(task == &task2); + + while (!TAILQ_EMPTY(&conn.active_r2t_tasks)) { + task = TAILQ_FIRST(&conn.active_r2t_tasks); + TAILQ_REMOVE(&conn.active_r2t_tasks, task, link); + } + + while (!TAILQ_EMPTY(&g_write_pdu_list)) { + pdu = TAILQ_FIRST(&g_write_pdu_list); + TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq); + spdk_put_pdu(pdu); + } + + spdk_put_pdu(pdu2); + spdk_put_pdu(pdu1); +} + +static void +del_transfer_task_test(void) +{ + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn conn; + struct spdk_iscsi_task task1, task2, task3, task4, task5, *task; + struct spdk_iscsi_pdu *pdu1, *pdu2, *pdu3, *pdu4, *pdu5, *pdu; + int rc; + + memset(&sess, 0, sizeof(sess)); + memset(&conn, 0, sizeof(conn)); + memset(&task1, 0, sizeof(task1)); + memset(&task2, 0, sizeof(task2)); + memset(&task3, 0, sizeof(task3)); + memset(&task4, 0, sizeof(task4)); + memset(&task5, 0, sizeof(task5)); + + sess.MaxBurstLength = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + sess.MaxOutstandingR2T = 1; + + conn.sess = &sess; + TAILQ_INIT(&conn.active_r2t_tasks); + TAILQ_INIT(&conn.queued_r2t_tasks); + + pdu1 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu1 != NULL); + + pdu1->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task1.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + spdk_iscsi_task_set_pdu(&task1, pdu1); + task1.tag = 11; + + rc = spdk_add_transfer_task(&conn, &task1); + CU_ASSERT(rc == SPDK_SUCCESS); + + pdu2 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu2 != NULL); + + pdu2->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task2.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + spdk_iscsi_task_set_pdu(&task2, pdu2); + task2.tag = 12; + + rc = spdk_add_transfer_task(&conn, &task2); + CU_ASSERT(rc == SPDK_SUCCESS); + + pdu3 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu3 != NULL); + + pdu3->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task3.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + spdk_iscsi_task_set_pdu(&task3, pdu3); + task3.tag = 13; + + rc = spdk_add_transfer_task(&conn, &task3); + CU_ASSERT(rc == SPDK_SUCCESS); + + pdu4 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu4 != NULL); + + pdu4->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task4.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + spdk_iscsi_task_set_pdu(&task4, pdu4); + task4.tag = 14; + + rc = spdk_add_transfer_task(&conn, &task4); + CU_ASSERT(rc == SPDK_SUCCESS); + + pdu5 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu5 != NULL); + + pdu5->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task5.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + spdk_iscsi_task_set_pdu(&task5, pdu5); + task5.tag = 15; + + rc = spdk_add_transfer_task(&conn, &task5); + CU_ASSERT(rc == SPDK_SUCCESS); + + CU_ASSERT(spdk_get_transfer_task(&conn, 1) == &task1); + CU_ASSERT(spdk_get_transfer_task(&conn, 5) == NULL); + spdk_del_transfer_task(&conn, 11); + CU_ASSERT(spdk_get_transfer_task(&conn, 1) == NULL); + CU_ASSERT(spdk_get_transfer_task(&conn, 5) == &task5); + + CU_ASSERT(spdk_get_transfer_task(&conn, 2) == &task2); + spdk_del_transfer_task(&conn, 12); + CU_ASSERT(spdk_get_transfer_task(&conn, 2) == NULL); + + CU_ASSERT(spdk_get_transfer_task(&conn, 3) == &task3); + spdk_del_transfer_task(&conn, 13); + CU_ASSERT(spdk_get_transfer_task(&conn, 3) == NULL); + + CU_ASSERT(spdk_get_transfer_task(&conn, 4) == &task4); + spdk_del_transfer_task(&conn, 14); + CU_ASSERT(spdk_get_transfer_task(&conn, 4) == NULL); + + CU_ASSERT(spdk_get_transfer_task(&conn, 5) == &task5); + spdk_del_transfer_task(&conn, 15); + CU_ASSERT(spdk_get_transfer_task(&conn, 5) == NULL); + + while (!TAILQ_EMPTY(&conn.active_r2t_tasks)) { + task = TAILQ_FIRST(&conn.active_r2t_tasks); + TAILQ_REMOVE(&conn.active_r2t_tasks, task, link); + } + + while (!TAILQ_EMPTY(&g_write_pdu_list)) { + pdu = TAILQ_FIRST(&g_write_pdu_list); + TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq); + spdk_put_pdu(pdu); + } + + spdk_put_pdu(pdu5); + spdk_put_pdu(pdu4); + spdk_put_pdu(pdu3); + spdk_put_pdu(pdu2); + spdk_put_pdu(pdu1); +} + +static void +clear_all_transfer_tasks_test(void) +{ + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn conn; + struct spdk_iscsi_task *task1, *task2, *task3, *task4, *task5; + struct spdk_iscsi_pdu *pdu1, *pdu2, *pdu3, *pdu4, *pdu5, *pdu; + struct spdk_scsi_lun lun1, lun2; + int rc; + + memset(&sess, 0, sizeof(sess)); + memset(&conn, 0, sizeof(conn)); + memset(&lun1, 0, sizeof(lun1)); + memset(&lun2, 0, sizeof(lun2)); + + sess.MaxBurstLength = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + sess.MaxOutstandingR2T = 1; + + conn.sess = &sess; + TAILQ_INIT(&conn.active_r2t_tasks); + TAILQ_INIT(&conn.queued_r2t_tasks); + + task1 = spdk_iscsi_task_get(&conn, NULL, NULL); + SPDK_CU_ASSERT_FATAL(task1 != NULL); + pdu1 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu1 != NULL); + + pdu1->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task1->scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task1->scsi.lun = &lun1; + spdk_iscsi_task_set_pdu(task1, pdu1); + + rc = spdk_add_transfer_task(&conn, task1); + CU_ASSERT(rc == SPDK_SUCCESS); + + task2 = spdk_iscsi_task_get(&conn, NULL, NULL); + SPDK_CU_ASSERT_FATAL(task2 != NULL); + pdu2 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu2 != NULL); + + pdu2->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task2->scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task2->scsi.lun = &lun1; + spdk_iscsi_task_set_pdu(task2, pdu2); + + rc = spdk_add_transfer_task(&conn, task2); + CU_ASSERT(rc == SPDK_SUCCESS); + + task3 = spdk_iscsi_task_get(&conn, NULL, NULL); + SPDK_CU_ASSERT_FATAL(task3 != NULL); + pdu3 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu3 != NULL); + + pdu3->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task3->scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task3->scsi.lun = &lun1; + spdk_iscsi_task_set_pdu(task3, pdu3); + + rc = spdk_add_transfer_task(&conn, task3); + CU_ASSERT(rc == SPDK_SUCCESS); + + task4 = spdk_iscsi_task_get(&conn, NULL, NULL); + SPDK_CU_ASSERT_FATAL(task4 != NULL); + pdu4 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu4 != NULL); + + pdu4->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task4->scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task4->scsi.lun = &lun2; + spdk_iscsi_task_set_pdu(task4, pdu4); + + rc = spdk_add_transfer_task(&conn, task4); + CU_ASSERT(rc == SPDK_SUCCESS); + + task5 = spdk_iscsi_task_get(&conn, NULL, NULL); + SPDK_CU_ASSERT_FATAL(task5 != NULL); + pdu5 = spdk_get_pdu(); + SPDK_CU_ASSERT_FATAL(pdu5 != NULL); + + pdu5->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task5->scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + task5->scsi.lun = &lun2; + spdk_iscsi_task_set_pdu(task5, pdu5); + + rc = spdk_add_transfer_task(&conn, task5); + CU_ASSERT(rc == SPDK_SUCCESS); + + CU_ASSERT(conn.ttt == 4); + + CU_ASSERT(spdk_get_transfer_task(&conn, 1) == task1); + CU_ASSERT(spdk_get_transfer_task(&conn, 2) == task2); + CU_ASSERT(spdk_get_transfer_task(&conn, 3) == task3); + CU_ASSERT(spdk_get_transfer_task(&conn, 4) == task4); + CU_ASSERT(spdk_get_transfer_task(&conn, 5) == NULL); + + spdk_clear_all_transfer_task(&conn, &lun1); + + CU_ASSERT(TAILQ_EMPTY(&conn.queued_r2t_tasks)); + CU_ASSERT(spdk_get_transfer_task(&conn, 1) == NULL); + CU_ASSERT(spdk_get_transfer_task(&conn, 2) == NULL); + CU_ASSERT(spdk_get_transfer_task(&conn, 3) == NULL); + CU_ASSERT(spdk_get_transfer_task(&conn, 4) == task4); + CU_ASSERT(spdk_get_transfer_task(&conn, 5) == task5); + + spdk_clear_all_transfer_task(&conn, NULL); + + CU_ASSERT(spdk_get_transfer_task(&conn, 4) == NULL); + CU_ASSERT(spdk_get_transfer_task(&conn, 5) == NULL); + + CU_ASSERT(TAILQ_EMPTY(&conn.active_r2t_tasks)); + while (!TAILQ_EMPTY(&g_write_pdu_list)) { + pdu = TAILQ_FIRST(&g_write_pdu_list); + TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq); + spdk_put_pdu(pdu); + } + + spdk_put_pdu(pdu5); + spdk_put_pdu(pdu4); + spdk_put_pdu(pdu3); + spdk_put_pdu(pdu2); + spdk_put_pdu(pdu1); +} + +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("iscsi_suite", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "login check target test", op_login_check_target_test) == NULL + || CU_add_test(suite, "maxburstlength test", maxburstlength_test) == NULL + || CU_add_test(suite, "underflow for read transfer test", + underflow_for_read_transfer_test) == NULL + || CU_add_test(suite, "underflow for zero read transfer test", + underflow_for_zero_read_transfer_test) == NULL + || CU_add_test(suite, "underflow for request sense test", + underflow_for_request_sense_test) == NULL + || CU_add_test(suite, "underflow for check condition test", + underflow_for_check_condition_test) == NULL + || CU_add_test(suite, "add transfer task test", add_transfer_task_test) == NULL + || CU_add_test(suite, "get transfer task test", get_transfer_task_test) == NULL + || CU_add_test(suite, "del transfer task test", del_transfer_task_test) == NULL + || CU_add_test(suite, "clear all transfer tasks test", + clear_all_transfer_tasks_test) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/iscsi/param.c/.gitignore b/src/spdk/test/unit/lib/iscsi/param.c/.gitignore new file mode 100644 index 00000000..26992146 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/param.c/.gitignore @@ -0,0 +1 @@ +param_ut diff --git a/src/spdk/test/unit/lib/iscsi/param.c/Makefile b/src/spdk/test/unit/lib/iscsi/param.c/Makefile new file mode 100644 index 00000000..bc944ae9 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/param.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = param_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/iscsi/param.c/param_ut.c b/src/spdk/test/unit/lib/iscsi/param.c/param_ut.c new file mode 100644 index 00000000..40941923 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/param.c/param_ut.c @@ -0,0 +1,397 @@ +/*- + * 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/scsi.h" + +#include "spdk_cunit.h" + +#include "../common.c" +#include "iscsi/param.c" + +struct spdk_iscsi_globals g_spdk_iscsi; + +struct spdk_iscsi_tgt_node * +spdk_iscsi_find_tgt_node(const char *target_name) +{ + return NULL; +} + +bool +spdk_iscsi_tgt_node_access(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target, + const char *iqn, const char *addr) +{ + return false; +} + +int +spdk_iscsi_send_tgts(struct spdk_iscsi_conn *conn, const char *iiqn, + const char *iaddr, + const char *tiqn, uint8_t *data, int alloc_len, int data_len) +{ + return 0; +} + +static void +burst_length_param_negotation(int FirstBurstLength, int MaxBurstLength, + int initialR2T) +{ + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn conn; + struct iscsi_param *params; + struct iscsi_param **params_p; + char data[8192]; + int rc; + int total, len; + + total = 0; + params = NULL; + params_p = ¶ms; + + memset(&sess, 0, sizeof(sess)); + memset(&conn, 0, sizeof(conn)); + memset(data, 0, 8192); + + sess.ExpCmdSN = 0; + sess.MaxCmdSN = 64; + sess.session_type = SESSION_TYPE_NORMAL; + sess.params = NULL; + sess.MaxBurstLength = 65536; + sess.InitialR2T = true; + sess.FirstBurstLength = SPDK_ISCSI_FIRST_BURST_LENGTH; + sess.MaxOutstandingR2T = 1; + + /* set default params */ + rc = spdk_iscsi_sess_params_init(&sess.params); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_param_set_int(sess.params, "FirstBurstLength", + sess.FirstBurstLength); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_param_set_int(sess.params, "MaxBurstLength", + sess.MaxBurstLength); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_param_set(sess.params, "InitialR2T", + sess.InitialR2T ? "Yes" : "No"); + CU_ASSERT(rc == 0); + + conn.full_feature = 1; + conn.sess = &sess; + conn.MaxRecvDataSegmentLength = 65536; + + rc = spdk_iscsi_conn_params_init(&conn.params); + CU_ASSERT(rc == 0); + + /* construct the data */ + len = snprintf(data + total, 8192 - total, "%s=%d", + "FirstBurstLength", FirstBurstLength); + total += len + 1; + + len = snprintf(data + total, 8192 - total, "%s=%d", + "MaxBurstLength", MaxBurstLength); + total += len + 1; + + len = snprintf(data + total, 8192 - total, "%s=%d", + "InitialR2T", initialR2T); + total += len + 1; + + /* add one extra NUL byte at the end to match real iSCSI params */ + total++; + + /* store incoming parameters */ + rc = spdk_iscsi_parse_params(params_p, data, total, false, NULL); + CU_ASSERT(rc == 0); + + /* negotiate parameters */ + rc = spdk_iscsi_negotiate_params(&conn, params_p, + data, 8192, rc); + CU_ASSERT(rc > 0); + + rc = spdk_iscsi_copy_param2var(&conn); + CU_ASSERT(rc == 0); + CU_ASSERT(conn.sess->FirstBurstLength <= SPDK_ISCSI_FIRST_BURST_LENGTH); + CU_ASSERT(conn.sess->FirstBurstLength <= conn.sess->MaxBurstLength); + CU_ASSERT(conn.sess->MaxBurstLength <= SPDK_ISCSI_MAX_BURST_LENGTH); + CU_ASSERT(conn.sess->MaxOutstandingR2T == 1); + + spdk_iscsi_param_free(sess.params); + spdk_iscsi_param_free(conn.params); + spdk_iscsi_param_free(*params_p); +} + +static void +param_negotiation_test(void) +{ + burst_length_param_negotation(8192, 16384, 0); + burst_length_param_negotation(8192, 16384, 1); + burst_length_param_negotation(8192, 1024, 1); + burst_length_param_negotation(8192, 1024, 0); + burst_length_param_negotation(512, 1024, 1); + burst_length_param_negotation(512, 1024, 0); +} + +static void +list_negotiation_test(void) +{ + int add_param_value = 0; + struct iscsi_param param = {}; + char *new_val; + char valid_list_buf[1024]; + char in_val_buf[1024]; + +#define TEST_LIST(valid_list, in_val, expected_result) \ + do { \ + snprintf(valid_list_buf, sizeof(valid_list_buf), "%s", valid_list); \ + snprintf(in_val_buf, sizeof(in_val_buf), "%s", in_val); \ + new_val = spdk_iscsi_negotiate_param_list(&add_param_value, ¶m, valid_list_buf, in_val_buf, NULL); \ + if (expected_result) { \ + SPDK_CU_ASSERT_FATAL(new_val != NULL); \ + CU_ASSERT_STRING_EQUAL(new_val, expected_result); \ + } \ + } while (0) + + TEST_LIST("None", "None", "None"); + TEST_LIST("CHAP,None", "None", "None"); + TEST_LIST("CHAP,None", "CHAP", "CHAP"); + TEST_LIST("KRB5,SRP,CHAP,None", "SRP,CHAP,None", "SRP"); + TEST_LIST("KRB5,SRP,CHAP,None", "CHAP,SRP,None", "CHAP"); + TEST_LIST("KRB5,SRP,CHAP,None", "SPKM1,SRP,CHAP,None", "SRP"); + TEST_LIST("KRB5,SRP,None", "CHAP,None", "None"); +} + +#define PARSE(strconst, partial_enabled, partial_text) \ + data = strconst; \ + len = sizeof(strconst); \ + rc = spdk_iscsi_parse_params(¶ms, data, len, partial_enabled, partial_text) + +#define EXPECT_VAL(key, expected_value) \ + { \ + const char *val = spdk_iscsi_param_get_val(params, key); \ + CU_ASSERT(val != NULL); \ + if (val != NULL) { \ + CU_ASSERT(strcmp(val, expected_value) == 0); \ + } \ + } + +#define EXPECT_NULL(key) \ + CU_ASSERT(spdk_iscsi_param_get_val(params, key) == NULL) + +static void +parse_valid_test(void) +{ + struct iscsi_param *params = NULL; + int rc; + char *data; + int len; + char *partial_parameter = NULL; + + /* simple test with a single key=value */ + PARSE("Abc=def\0", false, NULL); + CU_ASSERT(rc == 0); + EXPECT_VAL("Abc", "def"); + + /* multiple key=value pairs */ + PARSE("Aaa=bbbbbb\0Xyz=test\0", false, NULL); + CU_ASSERT(rc == 0); + EXPECT_VAL("Aaa", "bbbbbb"); + EXPECT_VAL("Xyz", "test"); + + /* value with embedded '=' */ + PARSE("A=b=c\0", false, NULL); + CU_ASSERT(rc == 0); + EXPECT_VAL("A", "b=c"); + + /* CHAP_C=AAAA.... with value length 8192 */ + len = strlen("CHAP_C=") + ISCSI_TEXT_MAX_VAL_LEN + 1/* null terminators */; + data = malloc(len); + SPDK_CU_ASSERT_FATAL(data != NULL); + memset(data, 'A', len); + memcpy(data, "CHAP_C", 6); + data[6] = '='; + data[len - 1] = '\0'; + rc = spdk_iscsi_parse_params(¶ms, data, len, false, NULL); + CU_ASSERT(rc == 0); + free(data); + + /* partial parameter: value is partial */ + PARSE("C=AAA\0D=B", true, &partial_parameter); + SPDK_CU_ASSERT_FATAL(partial_parameter != NULL); + CU_ASSERT_STRING_EQUAL(partial_parameter, "D=B"); + CU_ASSERT(rc == 0); + EXPECT_VAL("C", "AAA"); + EXPECT_NULL("D"); + PARSE("XXXX\0E=UUUU\0", false, &partial_parameter); + CU_ASSERT(rc == 0); + EXPECT_VAL("D", "BXXXX"); + EXPECT_VAL("E", "UUUU"); + CU_ASSERT_PTR_NULL(partial_parameter); + + /* partial parameter: key is partial */ + PARSE("IAMAFAK", true, &partial_parameter); + CU_ASSERT_STRING_EQUAL(partial_parameter, "IAMAFAK"); + CU_ASSERT(rc == 0); + EXPECT_NULL("IAMAFAK"); + PARSE("EDKEY=TTTT\0F=IIII", false, &partial_parameter); + CU_ASSERT(rc == 0); + EXPECT_VAL("IAMAFAKEDKEY", "TTTT"); + EXPECT_VAL("F", "IIII"); + CU_ASSERT_PTR_NULL(partial_parameter); + + /* Second partial parameter is the only parameter */ + PARSE("OOOO", true, &partial_parameter); + CU_ASSERT_STRING_EQUAL(partial_parameter, "OOOO"); + CU_ASSERT(rc == 0); + EXPECT_NULL("OOOO"); + PARSE("LL=MMMM", false, &partial_parameter); + CU_ASSERT(rc == 0); + EXPECT_VAL("OOOOLL", "MMMM"); + CU_ASSERT_PTR_NULL(partial_parameter); + + spdk_iscsi_param_free(params); +} + +static void +parse_invalid_test(void) +{ + struct iscsi_param *params = NULL; + int rc; + char *data; + int len; + + /* key without '=' */ + PARSE("Abc\0", false, NULL); + CU_ASSERT(rc != 0); + EXPECT_NULL("Abc"); + + /* multiple key=value pairs, one missing '=' */ + PARSE("Abc=def\0Xyz\0Www=test\0", false, NULL); + CU_ASSERT(rc != 0); + EXPECT_VAL("Abc", "def"); + EXPECT_NULL("Xyz"); + EXPECT_NULL("Www"); + + /* empty key */ + PARSE("=abcdef", false, NULL); + CU_ASSERT(rc != 0); + EXPECT_NULL(""); + + /* CHAP_C=AAAA.... with value length 8192 + 1 */ + len = strlen("CHAP_C=") + ISCSI_TEXT_MAX_VAL_LEN + 1 /* max value len + 1 */ + + 1 /* null terminators */; + data = malloc(len); + SPDK_CU_ASSERT_FATAL(data != NULL); + memset(data, 'A', len); + memcpy(data, "CHAP_C", 6); + data[6] = '='; + data[len - 1] = '\0'; + rc = spdk_iscsi_parse_params(¶ms, data, len, false, NULL); + free(data); + CU_ASSERT(rc != 0); + EXPECT_NULL("CHAP_C"); + + /* Test simple value, length of value bigger than 255 */ + len = strlen("A=") + ISCSI_TEXT_MAX_SIMPLE_VAL_LEN + 1 /* max simple value len + 1 */ + + 1 /* null terminators */; + data = malloc(len); + SPDK_CU_ASSERT_FATAL(data != NULL); + memset(data, 'A', len); + data[1] = '='; + data[len - 1] = '\0'; + rc = spdk_iscsi_parse_params(¶ms, data, len, false, NULL); + free(data); + CU_ASSERT(rc != 0); + EXPECT_NULL("A"); + + /* key length bigger than 63 */ + len = ISCSI_TEXT_MAX_KEY_LEN + 1 /* max key length + 1 */ + 1 /* = */ + 1 /* A */ + + 1 /* null terminators */; + data = malloc(len); + SPDK_CU_ASSERT_FATAL(data != NULL); + memset(data, 'A', len); + data[64] = '='; + data[len - 1] = '\0'; + rc = spdk_iscsi_parse_params(¶ms, data, len, false, NULL); + free(data); + CU_ASSERT(rc != 0); + EXPECT_NULL("A"); + + /* duplicated key */ + PARSE("B=BB", false, NULL); + CU_ASSERT(rc == 0); + PARSE("B=BBBB", false, NULL); + CU_ASSERT(rc != 0); + EXPECT_VAL("B", "BB"); + + spdk_iscsi_param_free(params); +} + +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("iscsi_suite", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "param negotiation test", + param_negotiation_test) == NULL || + CU_add_test(suite, "list negotiation test", + list_negotiation_test) == NULL || + CU_add_test(suite, "parse valid test", + parse_valid_test) == NULL || + CU_add_test(suite, "parse invalid test", + parse_invalid_test) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/iscsi/portal_grp.c/.gitignore b/src/spdk/test/unit/lib/iscsi/portal_grp.c/.gitignore new file mode 100644 index 00000000..106ffebc --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/portal_grp.c/.gitignore @@ -0,0 +1 @@ +portal_grp_ut diff --git a/src/spdk/test/unit/lib/iscsi/portal_grp.c/Makefile b/src/spdk/test/unit/lib/iscsi/portal_grp.c/Makefile new file mode 100644 index 00000000..ab28cabb --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/portal_grp.c/Makefile @@ -0,0 +1,42 @@ +# +## 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +SPDK_LIB_LIST = conf + +TEST_FILE = portal_grp_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/iscsi/portal_grp.c/portal_grp_ut.c b/src/spdk/test/unit/lib/iscsi/portal_grp.c/portal_grp_ut.c new file mode 100644 index 00000000..77351f0a --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/portal_grp.c/portal_grp_ut.c @@ -0,0 +1,477 @@ +/*- + * 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/event.h" + +#include "spdk_cunit.h" + +#include "../common.c" +#include "iscsi/portal_grp.c" +#include "unit/lib/json_mock.c" + +struct spdk_iscsi_globals g_spdk_iscsi; + +static int +test_setup(void) +{ + TAILQ_INIT(&g_spdk_iscsi.portal_head); + TAILQ_INIT(&g_spdk_iscsi.pg_head); + pthread_mutex_init(&g_spdk_iscsi.mutex, NULL); + return 0; +} + +static void +portal_create_ipv4_normal_case(void) +{ + struct spdk_iscsi_portal *p; + + const char *host = "192.168.2.0"; + const char *port = "3260"; + const char *cpumask = "1"; + + p = spdk_iscsi_portal_create(host, port, cpumask); + CU_ASSERT(p != NULL); + + spdk_iscsi_portal_destroy(p); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +portal_create_ipv6_normal_case(void) +{ + struct spdk_iscsi_portal *p; + + const char *host = "[2001:ad6:1234::]"; + const char *port = "3260"; + const char *cpumask = "1"; + + p = spdk_iscsi_portal_create(host, port, cpumask); + CU_ASSERT(p != NULL); + + spdk_iscsi_portal_destroy(p); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +portal_create_ipv4_wildcard_case(void) +{ + struct spdk_iscsi_portal *p; + + const char *host = "*"; + const char *port = "3260"; + const char *cpumask = "1"; + + p = spdk_iscsi_portal_create(host, port, cpumask); + CU_ASSERT(p != NULL); + + spdk_iscsi_portal_destroy(p); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +portal_create_ipv6_wildcard_case(void) +{ + struct spdk_iscsi_portal *p; + + const char *host = "[*]"; + const char *port = "3260"; + const char *cpumask = "1"; + + p = spdk_iscsi_portal_create(host, port, cpumask); + CU_ASSERT(p != NULL); + + spdk_iscsi_portal_destroy(p); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +portal_create_cpumask_null_case(void) +{ + struct spdk_iscsi_portal *p; + + const char *host = "192.168.2.0"; + const char *port = "3260"; + const char *cpumask = NULL; + + p = spdk_iscsi_portal_create(host, port, cpumask); + CU_ASSERT(p != NULL); + + spdk_iscsi_portal_destroy(p); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +portal_create_cpumask_no_bit_on_case(void) +{ + struct spdk_iscsi_portal *p; + + const char *host = "192.168.2.0"; + const char *port = "3260"; + const char *cpumask = "0"; + + p = spdk_iscsi_portal_create(host, port, cpumask); + CU_ASSERT(p == NULL); +} + +static void +portal_create_twice_case(void) +{ + struct spdk_iscsi_portal *p1, *p2; + + const char *host = "192.168.2.0"; + const char *port = "3260"; + const char *cpumask = "1"; + + p1 = spdk_iscsi_portal_create(host, port, cpumask); + CU_ASSERT(p1 != NULL); + + p2 = spdk_iscsi_portal_create(host, port, cpumask); + CU_ASSERT(p2 == NULL); + + spdk_iscsi_portal_destroy(p1); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +parse_portal_ipv4_normal_case(void) +{ + const char *string = "192.168.2.0:3260@1"; + const char *host_str = "192.168.2.0"; + const char *port_str = "3260"; + struct spdk_cpuset *cpumask_val; + struct spdk_iscsi_portal *p = NULL; + int rc; + + cpumask_val = spdk_cpuset_alloc(); + SPDK_CU_ASSERT_FATAL(cpumask_val != NULL); + + spdk_cpuset_set_cpu(cpumask_val, 0, true); + + rc = spdk_iscsi_parse_portal(string, &p, 0); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(p != NULL); + CU_ASSERT(strcmp(p->host, host_str) == 0); + CU_ASSERT(strcmp(p->port, port_str) == 0); + CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val)); + + spdk_iscsi_portal_destroy(p); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); + + spdk_cpuset_free(cpumask_val); +} + +static void +parse_portal_ipv6_normal_case(void) +{ + const char *string = "[2001:ad6:1234::]:3260@1"; + const char *host_str = "[2001:ad6:1234::]"; + const char *port_str = "3260"; + struct spdk_cpuset *cpumask_val; + struct spdk_iscsi_portal *p = NULL; + int rc; + + cpumask_val = spdk_cpuset_alloc(); + SPDK_CU_ASSERT_FATAL(cpumask_val != NULL); + + spdk_cpuset_set_cpu(cpumask_val, 0, true); + + rc = spdk_iscsi_parse_portal(string, &p, 0); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(p != NULL); + CU_ASSERT(strcmp(p->host, host_str) == 0); + CU_ASSERT(strcmp(p->port, port_str) == 0); + CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val)); + + spdk_iscsi_portal_destroy(p); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); + + spdk_cpuset_free(cpumask_val); +} + +static void +parse_portal_ipv4_skip_cpumask_case(void) +{ + const char *string = "192.168.2.0:3260"; + const char *host_str = "192.168.2.0"; + const char *port_str = "3260"; + struct spdk_cpuset *cpumask_val; + struct spdk_iscsi_portal *p = NULL; + int rc; + + cpumask_val = spdk_app_get_core_mask(); + + rc = spdk_iscsi_parse_portal(string, &p, 0); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(p != NULL); + CU_ASSERT(strcmp(p->host, host_str) == 0); + CU_ASSERT(strcmp(p->port, port_str) == 0); + CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val)); + + spdk_iscsi_portal_destroy(p); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +parse_portal_ipv6_skip_cpumask_case(void) +{ + const char *string = "[2001:ad6:1234::]:3260"; + const char *host_str = "[2001:ad6:1234::]"; + const char *port_str = "3260"; + struct spdk_cpuset *cpumask_val; + struct spdk_iscsi_portal *p = NULL; + int rc; + + cpumask_val = spdk_app_get_core_mask(); + + rc = spdk_iscsi_parse_portal(string, &p, 0); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(p != NULL); + CU_ASSERT(strcmp(p->host, host_str) == 0); + CU_ASSERT(strcmp(p->port, port_str) == 0); + CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val)); + + spdk_iscsi_portal_destroy(p); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +parse_portal_ipv4_skip_port_and_cpumask_case(void) +{ + const char *string = "192.168.2.0"; + const char *host_str = "192.168.2.0"; + const char *port_str = "3260"; + struct spdk_cpuset *cpumask_val; + struct spdk_iscsi_portal *p = NULL; + int rc; + + cpumask_val = spdk_app_get_core_mask(); + + rc = spdk_iscsi_parse_portal(string, &p, 0); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(p != NULL); + CU_ASSERT(strcmp(p->host, host_str) == 0); + CU_ASSERT(strcmp(p->port, port_str) == 0); + CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val)); + + spdk_iscsi_portal_destroy(p); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +parse_portal_ipv6_skip_port_and_cpumask_case(void) +{ + const char *string = "[2001:ad6:1234::]"; + const char *host_str = "[2001:ad6:1234::]"; + const char *port_str = "3260"; + struct spdk_cpuset *cpumask_val; + struct spdk_iscsi_portal *p = NULL; + int rc; + + cpumask_val = spdk_app_get_core_mask(); + + rc = spdk_iscsi_parse_portal(string, &p, 0); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(p != NULL); + CU_ASSERT(strcmp(p->host, host_str) == 0); + CU_ASSERT(strcmp(p->port, port_str) == 0); + CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val)); + + spdk_iscsi_portal_destroy(p); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +portal_grp_register_unregister_case(void) +{ + struct spdk_iscsi_portal *p; + struct spdk_iscsi_portal_grp *pg1, *pg2; + int rc; + const char *host = "192.168.2.0"; + const char *port = "3260"; + const char *cpumask = "1"; + + pg1 = spdk_iscsi_portal_grp_create(1); + CU_ASSERT(pg1 != NULL); + + p = spdk_iscsi_portal_create(host, port, cpumask); + CU_ASSERT(p != NULL); + + spdk_iscsi_portal_grp_add_portal(pg1, p); + + rc = spdk_iscsi_portal_grp_register(pg1); + CU_ASSERT(rc == 0); + + pg2 = spdk_iscsi_portal_grp_unregister(1); + CU_ASSERT(pg2 != NULL); + CU_ASSERT(pg1 == pg2); + + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.pg_head)); + + spdk_iscsi_portal_grp_destroy(pg1); + + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +portal_grp_register_twice_case(void) +{ + struct spdk_iscsi_portal *p; + struct spdk_iscsi_portal_grp *pg1, *pg2; + int rc; + const char *host = "192.168.2.0"; + const char *port = "3260"; + const char *cpumask = "1"; + + pg1 = spdk_iscsi_portal_grp_create(1); + CU_ASSERT(pg1 != NULL); + + p = spdk_iscsi_portal_create(host, port, cpumask); + CU_ASSERT(p != NULL); + + spdk_iscsi_portal_grp_add_portal(pg1, p); + + rc = spdk_iscsi_portal_grp_register(pg1); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_portal_grp_register(pg1); + CU_ASSERT(rc != 0); + + pg2 = spdk_iscsi_portal_grp_unregister(1); + CU_ASSERT(pg2 != NULL); + CU_ASSERT(pg1 == pg2); + + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.pg_head)); + + spdk_iscsi_portal_grp_destroy(pg1); + + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); +} + +static void +portal_grp_add_delete_case(void) +{ + struct spdk_iscsi_portal_grp *pg1, *pg2; + struct spdk_iscsi_portal *p; + int rc; + + const char *host = "192.168.2.0"; + const char *port = "3260"; + const char *cpumask = "1"; + + /* internal of add_portal_group */ + pg1 = spdk_iscsi_portal_grp_create(1); + CU_ASSERT(pg1 != NULL); + + p = spdk_iscsi_portal_create(host, port, cpumask); + CU_ASSERT(p != NULL); + + spdk_iscsi_portal_grp_add_portal(pg1, p); + + rc = spdk_iscsi_portal_grp_open(pg1); + CU_ASSERT(rc == 0); + + rc = spdk_iscsi_portal_grp_register(pg1); + CU_ASSERT(rc == 0); + + /* internal of delete_portal_group */ + pg2 = spdk_iscsi_portal_grp_unregister(1); + CU_ASSERT(pg2 != NULL); + CU_ASSERT(pg1 == pg2); + + spdk_iscsi_portal_grp_release(pg2); + + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head)); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.pg_head)); +} + +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("portal_grp_suite", test_setup, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "portal create ipv4 normal case", + portal_create_ipv4_normal_case) == NULL + || CU_add_test(suite, "portal create ipv6 normal case", + portal_create_ipv6_normal_case) == NULL + || CU_add_test(suite, "portal create ipv4 wildcard case", + portal_create_ipv4_wildcard_case) == NULL + || CU_add_test(suite, "portal create ipv6 wildcard case", + portal_create_ipv6_wildcard_case) == NULL + || CU_add_test(suite, "portal create cpumask NULL case", + portal_create_cpumask_null_case) == NULL + || CU_add_test(suite, "portal create cpumask no bit on case", + portal_create_cpumask_no_bit_on_case) == NULL + || CU_add_test(suite, "portal create twice case", + portal_create_twice_case) == NULL + || CU_add_test(suite, "parse portal ipv4 normal case", + parse_portal_ipv4_normal_case) == NULL + || CU_add_test(suite, "parse portal ipv6 normal case", + parse_portal_ipv6_normal_case) == NULL + || CU_add_test(suite, "parse portal ipv4 skip cpumask case", + parse_portal_ipv4_skip_cpumask_case) == NULL + || CU_add_test(suite, "parse portal ipv6 skip cpumask case", + parse_portal_ipv6_skip_cpumask_case) == NULL + || CU_add_test(suite, "parse portal ipv4 skip port and cpumask case", + parse_portal_ipv4_skip_port_and_cpumask_case) == NULL + || CU_add_test(suite, "parse portal ipv6 skip port and cpumask case", + parse_portal_ipv6_skip_port_and_cpumask_case) == NULL + || CU_add_test(suite, "portal group register/unregister case", + portal_grp_register_unregister_case) == NULL + || CU_add_test(suite, "portal group register twice case", + portal_grp_register_twice_case) == NULL + || CU_add_test(suite, "portal group add/delete case", + portal_grp_add_delete_case) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/iscsi/tgt_node.c/.gitignore b/src/spdk/test/unit/lib/iscsi/tgt_node.c/.gitignore new file mode 100644 index 00000000..010d84b8 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/tgt_node.c/.gitignore @@ -0,0 +1 @@ +tgt_node_ut diff --git a/src/spdk/test/unit/lib/iscsi/tgt_node.c/Makefile b/src/spdk/test/unit/lib/iscsi/tgt_node.c/Makefile new file mode 100644 index 00000000..8cdf3ef3 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/tgt_node.c/Makefile @@ -0,0 +1,41 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +SPDK_LIB_LIST = conf +TEST_FILE = tgt_node_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node.conf b/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node.conf new file mode 100644 index 00000000..6bf5aa66 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node.conf @@ -0,0 +1,95 @@ +[Global] + +# Test that parsing fails if there is no TargetName +[Failure0] + TargetAlias "Data Disk1" + Mapping PortalGroup1 InitiatorGroup1 + AuthMethod Auto + AuthGroup AuthGroup1 + UseDigest Auto + QueueDepth 128 + LUN0 Malloc0 + LUN1 Malloc1 + +# Test that parsing fails if there is no Mapping +[Failure1] + TargetName target1 + TargetAlias "Data Disk1" + AuthMethod Auto + AuthGroup AuthGroup1 + UseDigest Auto + QueueDepth 128 + LUN0 Malloc0 + LUN1 Malloc1 + +# Test that parsing fails if Mapping does not define Portal or InitiatorGroup +[Failure2] + TargetName target1 + TargetAlias "Data Disk1" + Mapping + AuthMethod Auto + AuthGroup AuthGroup1 + UseDigest Auto + QueueDepth 128 + LUN0 Malloc0 + LUN1 Malloc1 + +# Test that parsing fails if Mapping does not define InitiatorGroup +[Failure3] + TargetName target1 + TargetAlias "Data Disk1" + Mapping PortalGroup1 + AuthMethod Auto + AuthGroup AuthGroup1 + UseDigest Auto + QueueDepth 128 + LUN0 Malloc0 + LUN1 Malloc1 + +# Test that parsing fails if Mapping switches PortalGroup/InitiatorGroup order +[Failure4] + TargetName target1 + TargetAlias "Data Disk1" + Mapping InitiatorGroup1 PortalGroup1 + AuthMethod Auto + AuthGroup AuthGroup1 + UseDigest Auto + QueueDepth 128 + LUN0 Malloc0 + LUN1 Malloc1 + +# Test that parsing fails if Mapping uses invalid InitiatorGroup0 +[Failure5] + TargetName target1 + TargetAlias "Data Disk1" + Mapping PortalGroup1 InitiatorGroup0 + AuthMethod Auto + AuthGroup AuthGroup1 + UseDigest Auto + QueueDepth 128 + LUN0 Malloc0 + LUN1 Malloc1 + +# Test that parsing fails if Mapping uses invalid PortalGroup0 +[Failure6] + TargetName target1 + TargetAlias "Data Disk1" + Mapping PortalGroup0 InitiatorGroup1 + AuthMethod Auto + AuthGroup AuthGroup1 + UseDigest Auto + QueueDepth 128 + LUN0 Malloc0 + LUN1 Malloc1 + +# Test that parsing fails if AuthMethod is invalid +[Failure7] + TargetName target1 + TargetAlias "Data Disk1" + Mapping PortalGroup1 InitiatorGroup1 + AuthMethod SomeGarbage + AuthGroup AuthGroup1 + UseDigest Auto + QueueDepth 128 + LUN0 Malloc0 + LUN1 Malloc1 diff --git a/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node_ut.c b/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node_ut.c new file mode 100644 index 00000000..eda02db6 --- /dev/null +++ b/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node_ut.c @@ -0,0 +1,886 @@ +/*- + * 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/scsi.h" + +#include "CUnit/Basic.h" +#include "spdk_internal/mock.h" + +#include "../common.c" +#include "iscsi/tgt_node.c" +#include "scsi/scsi_internal.h" +#include "unit/lib/json_mock.c" + +struct spdk_iscsi_globals g_spdk_iscsi; + +const char *config_file; + +DEFINE_STUB(spdk_scsi_dev_get_id, + int, + (const struct spdk_scsi_dev *dev), + 0); + +DEFINE_STUB(spdk_scsi_lun_get_bdev_name, + const char *, + (const struct spdk_scsi_lun *lun), + NULL); + +DEFINE_STUB(spdk_scsi_lun_get_id, + int, + (const struct spdk_scsi_lun *lun), + 0); + +bool +spdk_sock_is_ipv6(struct spdk_sock *sock) +{ + return false; +} + +bool +spdk_sock_is_ipv4(struct spdk_sock *sock) +{ + return false; +} + +struct spdk_iscsi_portal_grp * +spdk_iscsi_portal_grp_find_by_tag(int tag) +{ + return NULL; +} + +struct spdk_iscsi_init_grp * +spdk_iscsi_init_grp_find_by_tag(int tag) +{ + return NULL; +} + +struct spdk_scsi_lun * +spdk_scsi_dev_get_lun(struct spdk_scsi_dev *dev, int lun_id) +{ + if (lun_id < 0 || lun_id >= SPDK_SCSI_DEV_MAX_LUN) { + return NULL; + } + + return dev->lun[lun_id]; +} + +int +spdk_scsi_dev_add_lun(struct spdk_scsi_dev *dev, const char *bdev_name, int lun_id, + void (*hotremove_cb)(const struct spdk_scsi_lun *, void *), + void *hotremove_ctx) +{ + if (bdev_name == NULL) { + return -1; + } else { + return 0; + } +} + +static void +add_lun_test_cases(void) +{ + struct spdk_iscsi_tgt_node tgtnode; + int lun_id = 0; + char *bdev_name = NULL; + struct spdk_scsi_dev scsi_dev; + int rc; + + memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node)); + memset(&scsi_dev, 0, sizeof(struct spdk_scsi_dev)); + + /* case 1 */ + tgtnode.num_active_conns = 1; + + rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id); + CU_ASSERT(rc != 0); + + /* case 2 */ + tgtnode.num_active_conns = 0; + lun_id = -2; + + rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id); + CU_ASSERT(rc != 0); + + /* case 3 */ + lun_id = SPDK_SCSI_DEV_MAX_LUN; + + rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id); + CU_ASSERT(rc != 0); + + /* case 4 */ + lun_id = -1; + tgtnode.dev = NULL; + + rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id); + CU_ASSERT(rc != 0); + + /* case 5 */ + tgtnode.dev = &scsi_dev; + + rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id); + CU_ASSERT(rc != 0); + + /* case 6 */ + bdev_name = "LUN0"; + + rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id); + CU_ASSERT(rc == 0); +} + +static void +config_file_fail_cases(void) +{ + struct spdk_conf *config; + struct spdk_conf_section *sp; + char section_name[64]; + int section_index; + int rc; + + config = spdk_conf_allocate(); + + rc = spdk_conf_read(config, config_file); + CU_ASSERT(rc == 0); + + section_index = 0; + while (true) { + snprintf(section_name, sizeof(section_name), "Failure%d", section_index); + sp = spdk_conf_find_section(config, section_name); + if (sp == NULL) { + break; + } + rc = spdk_iscsi_parse_tgt_node(sp); + CU_ASSERT(rc < 0); + section_index++; + } + + spdk_conf_free(config); +} + +static void +allow_any_allowed(void) +{ + bool result; + char *netmask; + char *addr1, *addr2; + + netmask = "ANY"; + addr1 = "2001:ad6:1234:5678:9abc::"; + addr2 = "192.168.2.1"; + + result = spdk_iscsi_netmask_allow_addr(netmask, addr1); + CU_ASSERT(result == true); + + result = spdk_iscsi_netmask_allow_addr(netmask, addr2); + CU_ASSERT(result == true); +} + +static void +allow_ipv6_allowed(void) +{ + bool result; + char *netmask; + char *addr; + + netmask = "[2001:ad6:1234::]/48"; + addr = "2001:ad6:1234:5678:9abc::"; + + result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == true); + + result = spdk_iscsi_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == true); + + /* Netmask prefix bits == 128 (all bits must match) */ + netmask = "[2001:ad6:1234:5678:9abc::1]/128"; + addr = "2001:ad6:1234:5678:9abc::1"; + result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == true); +} + +static void +allow_ipv6_denied(void) +{ + bool result; + char *netmask; + char *addr; + + netmask = "[2001:ad6:1234::]/56"; + addr = "2001:ad6:1234:5678:9abc::"; + + result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); + + result = spdk_iscsi_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); + + /* Netmask prefix bits == 128 (all bits must match) */ + netmask = "[2001:ad6:1234:5678:9abc::1]/128"; + addr = "2001:ad6:1234:5678:9abc::2"; + result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); +} + +static void +allow_ipv6_invalid(void) +{ + bool result; + char *netmask; + char *addr; + + /* Netmask prefix bits > 128 */ + netmask = "[2001:ad6:1234::]/129"; + addr = "2001:ad6:1234:5678:9abc::"; + result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); + + /* Netmask prefix bits == 0 */ + netmask = "[2001:ad6:1234::]/0"; + addr = "2001:ad6:1234:5678:9abc::"; + result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); + + /* Netmask prefix bits < 0 */ + netmask = "[2001:ad6:1234::]/-1"; + addr = "2001:ad6:1234:5678:9abc::"; + result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); +} + +static void +allow_ipv4_allowed(void) +{ + bool result; + char *netmask; + char *addr; + + netmask = "192.168.2.0/24"; + addr = "192.168.2.1"; + + result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == true); + + result = spdk_iscsi_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == true); + + /* Netmask prefix == 32 (all bits must match) */ + netmask = "192.168.2.1/32"; + addr = "192.168.2.1"; + result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == true); +} + +static void +allow_ipv4_denied(void) +{ + bool result; + char *netmask; + char *addr; + + netmask = "192.168.2.0"; + addr = "192.168.2.1"; + + result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); + + result = spdk_iscsi_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); + + /* Netmask prefix == 32 (all bits must match) */ + netmask = "192.168.2.1/32"; + addr = "192.168.2.2"; + result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); +} + +static void +allow_ipv4_invalid(void) +{ + bool result; + char *netmask; + char *addr; + + /* Netmask prefix bits > 32 */ + netmask = "192.168.2.0/33"; + addr = "192.168.2.1"; + result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); + + /* Netmask prefix bits == 0 */ + netmask = "192.168.2.0/0"; + addr = "192.168.2.1"; + result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); + + /* Netmask prefix bits < 0 */ + netmask = "192.168.2.0/-1"; + addr = "192.168.2.1"; + result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr); + CU_ASSERT(result == false); +} + +static void +node_access_allowed(void) +{ + struct spdk_iscsi_tgt_node tgtnode; + struct spdk_iscsi_portal_grp pg; + struct spdk_iscsi_init_grp ig; + struct spdk_iscsi_conn conn; + struct spdk_iscsi_portal portal; + struct spdk_iscsi_initiator_name iname; + struct spdk_iscsi_initiator_netmask imask; + struct spdk_scsi_dev scsi_dev; + struct spdk_iscsi_pg_map *pg_map; + char *iqn, *addr; + bool result; + + /* portal group initialization */ + memset(&pg, 0, sizeof(struct spdk_iscsi_portal_grp)); + pg.tag = 1; + + /* initiator group initialization */ + memset(&ig, 0, sizeof(struct spdk_iscsi_init_grp)); + ig.tag = 1; + + ig.ninitiators = 1; + iname.name = "iqn.2017-10.spdk.io:0001"; + TAILQ_INIT(&ig.initiator_head); + TAILQ_INSERT_TAIL(&ig.initiator_head, &iname, tailq); + + ig.nnetmasks = 1; + imask.mask = "192.168.2.0/24"; + TAILQ_INIT(&ig.netmask_head); + TAILQ_INSERT_TAIL(&ig.netmask_head, &imask, tailq); + + /* target initialization */ + memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node)); + tgtnode.name = "iqn.2017-10.spdk.io:0001"; + TAILQ_INIT(&tgtnode.pg_map_head); + + memset(&scsi_dev, 0, sizeof(struct spdk_scsi_dev)); + snprintf(scsi_dev.name, sizeof(scsi_dev.name), "iqn.2017-10.spdk.io:0001"); + tgtnode.dev = &scsi_dev; + + pg_map = spdk_iscsi_tgt_node_add_pg_map(&tgtnode, &pg); + spdk_iscsi_pg_map_add_ig_map(pg_map, &ig); + + /* portal initialization */ + memset(&portal, 0, sizeof(struct spdk_iscsi_portal)); + portal.group = &pg; + portal.host = "192.168.2.0"; + portal.port = "3260"; + + /* input for UT */ + memset(&conn, 0, sizeof(struct spdk_iscsi_conn)); + conn.portal = &portal; + + iqn = "iqn.2017-10.spdk.io:0001"; + addr = "192.168.2.1"; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == true); + + spdk_iscsi_pg_map_delete_ig_map(pg_map, &ig); + spdk_iscsi_tgt_node_delete_pg_map(&tgtnode, &pg); +} + +static void +node_access_denied_by_empty_netmask(void) +{ + struct spdk_iscsi_tgt_node tgtnode; + struct spdk_iscsi_portal_grp pg; + struct spdk_iscsi_init_grp ig; + struct spdk_iscsi_conn conn; + struct spdk_iscsi_portal portal; + struct spdk_iscsi_initiator_name iname; + struct spdk_scsi_dev scsi_dev; + struct spdk_iscsi_pg_map *pg_map; + char *iqn, *addr; + bool result; + + /* portal group initialization */ + memset(&pg, 0, sizeof(struct spdk_iscsi_portal_grp)); + pg.tag = 1; + + /* initiator group initialization */ + memset(&ig, 0, sizeof(struct spdk_iscsi_init_grp)); + ig.tag = 1; + + ig.ninitiators = 1; + iname.name = "iqn.2017-10.spdk.io:0001"; + TAILQ_INIT(&ig.initiator_head); + TAILQ_INSERT_TAIL(&ig.initiator_head, &iname, tailq); + + ig.nnetmasks = 0; + TAILQ_INIT(&ig.netmask_head); + + /* target initialization */ + memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node)); + tgtnode.name = "iqn.2017-10.spdk.io:0001"; + TAILQ_INIT(&tgtnode.pg_map_head); + + memset(&scsi_dev, 0, sizeof(struct spdk_scsi_dev)); + snprintf(scsi_dev.name, sizeof(scsi_dev.name), "iqn.2017-10.spdk.io:0001"); + tgtnode.dev = &scsi_dev; + + pg_map = spdk_iscsi_tgt_node_add_pg_map(&tgtnode, &pg); + spdk_iscsi_pg_map_add_ig_map(pg_map, &ig); + + /* portal initialization */ + memset(&portal, 0, sizeof(struct spdk_iscsi_portal)); + portal.group = &pg; + portal.host = "192.168.2.0"; + portal.port = "3260"; + + /* input for UT */ + memset(&conn, 0, sizeof(struct spdk_iscsi_conn)); + conn.portal = &portal; + + iqn = "iqn.2017-10.spdk.io:0001"; + addr = "192.168.3.1"; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == false); + + spdk_iscsi_pg_map_delete_ig_map(pg_map, &ig); + spdk_iscsi_tgt_node_delete_pg_map(&tgtnode, &pg); +} + +#define IQN1 "iqn.2017-11.spdk.io:0001" +#define NO_IQN1 "!iqn.2017-11.spdk.io:0001" +#define IQN2 "iqn.2017-11.spdk.io:0002" +#define IP1 "192.168.2.0" +#define IP2 "192.168.2.1" + +static void +node_access_multi_initiator_groups_cases(void) +{ + struct spdk_iscsi_tgt_node tgtnode; + struct spdk_iscsi_conn conn; + struct spdk_iscsi_portal_grp pg; + struct spdk_iscsi_portal portal; + struct spdk_iscsi_init_grp ig1, ig2; + struct spdk_iscsi_initiator_name iname1, iname2; + struct spdk_iscsi_initiator_netmask imask1, imask2; + struct spdk_scsi_dev scsi_dev; + struct spdk_iscsi_pg_map *pg_map; + char *iqn, *addr; + bool result; + + /* target initialization */ + memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node)); + tgtnode.name = IQN1; + TAILQ_INIT(&tgtnode.pg_map_head); + + memset(&scsi_dev, 0, sizeof(struct spdk_scsi_dev)); + snprintf(scsi_dev.name, sizeof(scsi_dev.name), IQN1); + tgtnode.dev = &scsi_dev; + + /* initiator group initialization */ + memset(&ig1, 0, sizeof(struct spdk_iscsi_init_grp)); + ig1.tag = 1; + TAILQ_INIT(&ig1.initiator_head); + TAILQ_INIT(&ig1.netmask_head); + + ig1.ninitiators = 1; + iname1.name = NULL; + TAILQ_INSERT_TAIL(&ig1.initiator_head, &iname1, tailq); + + ig1.nnetmasks = 1; + imask1.mask = NULL; + TAILQ_INSERT_TAIL(&ig1.netmask_head, &imask1, tailq); + + memset(&ig2, 0, sizeof(struct spdk_iscsi_init_grp)); + ig2.tag = 2; + TAILQ_INIT(&ig2.initiator_head); + TAILQ_INIT(&ig2.netmask_head); + + ig2.ninitiators = 1; + iname2.name = NULL; + TAILQ_INSERT_TAIL(&ig2.initiator_head, &iname2, tailq); + + ig2.nnetmasks = 1; + imask2.mask = NULL; + TAILQ_INSERT_TAIL(&ig2.netmask_head, &imask2, tailq); + + /* portal group initialization */ + memset(&pg, 0, sizeof(struct spdk_iscsi_portal_grp)); + pg.tag = 1; + + pg_map = spdk_iscsi_tgt_node_add_pg_map(&tgtnode, &pg); + spdk_iscsi_pg_map_add_ig_map(pg_map, &ig1); + spdk_iscsi_pg_map_add_ig_map(pg_map, &ig2); + + /* portal initialization */ + memset(&portal, 0, sizeof(struct spdk_iscsi_portal)); + portal.group = &pg; + portal.host = IP1; + portal.port = "3260"; + + /* connection initialization */ + memset(&conn, 0, sizeof(struct spdk_iscsi_conn)); + conn.portal = &portal; + + iqn = IQN1; + addr = IP1; + + /* + * case 1: + * +-------------------------------------------+---------+ + * | IG1 | IG2 | | + * +-------------------------------------------+ | + * | name | addr | name | addr | result | + * +-------------------------------------------+---------+ + * +-------------------------------------------+---------+ + * | denied | - | - | - | denied | + * +-------------------------------------------+---------+ + */ + iname1.name = NO_IQN1; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == false); + + /* + * case 2: + * +-------------------------------------------+---------+ + * | IG1 | IG2 | | + * +-------------------------------------------+ | + * | name | addr | name | addr | result | + * +-------------------------------------------+---------+ + * +-------------------------------------------+---------+ + * | allowed | allowed | - | - | allowed | + * +-------------------------------------------+---------+ + */ + iname1.name = IQN1; + imask1.mask = IP1; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == true); + + /* + * case 3: + * +-------------------------------------------+---------+ + * | IG1 | IG2 | | + * +-------------------------------------------+ | + * | name | addr | name | addr | result | + * +-------------------------------------------+---------+ + * +-------------------------------------------+---------+ + * | allowed | denied | denied | - | denied | + * +-------------------------------------------+---------+ + */ + iname1.name = IQN1; + imask1.mask = IP2; + iname2.name = NO_IQN1; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == false); + + /* + * case 4: + * +-------------------------------------------+---------+ + * | IG1 | IG2 | | + * +-------------------------------------------+ | + * | name | addr | name | addr | result | + * +-------------------------------------------+---------+ + * +-------------------------------------------+---------+ + * | allowed | denied | allowed | allowed | allowed | + * +-------------------------------------------+---------+ + */ + iname1.name = IQN1; + imask1.mask = IP2; + iname2.name = IQN1; + imask2.mask = IP1; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == true); + + /* + * case 5: + * +---------------------------------------------+---------+ + * | IG1 | IG2 | | + * +---------------------------------------------+ | + * | name | addr | name | addr | result | + * +---------------------------------------------+---------+ + * +---------------------------------------------+---------+ + * | allowed | denied | allowed | denied | denied | + * +---------------------------------------------+---------+ + */ + iname1.name = IQN1; + imask1.mask = IP2; + iname2.name = IQN1; + imask2.mask = IP2; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == false); + + /* + * case 6: + * +---------------------------------------------+---------+ + * | IG1 | IG2 | | + * +---------------------------------------------+ | + * | name | addr | name | addr | result | + * +---------------------------------------------+---------+ + * +---------------------------------------------+---------+ + * | allowed | denied | not found | - | denied | + * +---------------------------------------------+---------+ + */ + iname1.name = IQN1; + imask1.mask = IP2; + iname2.name = IQN2; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == false); + + /* + * case 7: + * +---------------------------------------------+---------+ + * | IG1 | IG2 | | + * +---------------------------------------------+ | + * | name | addr | name | addr | result | + * +---------------------------------------------+---------+ + * +---------------------------------------------+---------+ + * | not found | - | denied | - | denied | + * +---------------------------------------------+---------+ + */ + iname1.name = IQN2; + iname2.name = NO_IQN1; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == false); + + /* + * case 8: + * +---------------------------------------------+---------+ + * | IG1 | IG2 | | + * +---------------------------------------------+ | + * | name | addr | name | addr | result | + * +---------------------------------------------+---------+ + * +---------------------------------------------+---------+ + * | not found | - | allowed | allowed | allowed | + * +---------------------------------------------+---------+ + */ + iname1.name = IQN2; + iname2.name = IQN1; + imask2.mask = IP1; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == true); + + /* + * case 9: + * +---------------------------------------------+---------+ + * | IG1 | IG2 | | + * +---------------------------------------------+ | + * | name | addr | name | addr | result | + * +---------------------------------------------+---------+ + * +---------------------------------------------+---------+ + * | not found | - | allowed | denied | denied | + * +---------------------------------------------+---------+ + */ + iname1.name = IQN2; + iname2.name = IQN1; + imask2.mask = IP2; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == false); + + /* + * case 10: + * +---------------------------------------------+---------+ + * | IG1 | IG2 | | + * +---------------------------------------------+ | + * | name | addr | name | addr | result | + * +---------------------------------------------+---------+ + * +---------------------------------------------+---------+ + * | not found | - | not found | - | denied | + * +---------------------------------------------+---------+ + */ + iname1.name = IQN2; + iname2.name = IQN2; + + result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr); + CU_ASSERT(result == false); + + spdk_iscsi_pg_map_delete_ig_map(pg_map, &ig1); + spdk_iscsi_pg_map_delete_ig_map(pg_map, &ig2); + spdk_iscsi_tgt_node_delete_pg_map(&tgtnode, &pg); +} + +static void +allow_iscsi_name_multi_maps_case(void) +{ + struct spdk_iscsi_tgt_node tgtnode; + struct spdk_iscsi_portal_grp pg1, pg2; + struct spdk_iscsi_init_grp ig; + struct spdk_iscsi_initiator_name iname; + struct spdk_iscsi_pg_map *pg_map1, *pg_map2; + struct spdk_scsi_dev scsi_dev; + char *iqn; + bool result; + + /* target initialization */ + memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node)); + TAILQ_INIT(&tgtnode.pg_map_head); + + memset(&scsi_dev, 0, sizeof(struct spdk_scsi_dev)); + snprintf(scsi_dev.name, sizeof(scsi_dev.name), IQN1); + tgtnode.dev = &scsi_dev; + + /* initiator group initialization */ + memset(&ig, 0, sizeof(struct spdk_iscsi_init_grp)); + TAILQ_INIT(&ig.initiator_head); + + ig.ninitiators = 1; + iname.name = NULL; + TAILQ_INSERT_TAIL(&ig.initiator_head, &iname, tailq); + + /* portal group initialization */ + memset(&pg1, 0, sizeof(struct spdk_iscsi_portal_grp)); + pg1.tag = 1; + memset(&pg2, 0, sizeof(struct spdk_iscsi_portal_grp)); + pg2.tag = 1; + + pg_map1 = spdk_iscsi_tgt_node_add_pg_map(&tgtnode, &pg1); + pg_map2 = spdk_iscsi_tgt_node_add_pg_map(&tgtnode, &pg2); + spdk_iscsi_pg_map_add_ig_map(pg_map1, &ig); + spdk_iscsi_pg_map_add_ig_map(pg_map2, &ig); + + /* test for IG1 <-> PG1, PG2 case */ + iqn = IQN1; + + iname.name = IQN1; + + result = spdk_iscsi_tgt_node_allow_iscsi_name(&tgtnode, iqn); + CU_ASSERT(result == true); + + iname.name = IQN2; + + result = spdk_iscsi_tgt_node_allow_iscsi_name(&tgtnode, iqn); + CU_ASSERT(result == false); + + spdk_iscsi_pg_map_delete_ig_map(pg_map1, &ig); + spdk_iscsi_pg_map_delete_ig_map(pg_map2, &ig); + spdk_iscsi_tgt_node_delete_pg_map(&tgtnode, &pg1); + spdk_iscsi_tgt_node_delete_pg_map(&tgtnode, &pg2); +} + +/* + * static bool + * spdk_iscsi_check_chap_params(bool disable_chap, bool require_chap, + * bool mutual_chap, int chap_group); + */ +static void +chap_param_test_cases(void) +{ + /* Auto */ + CU_ASSERT(spdk_iscsi_check_chap_params(false, false, false, 0) == true); + + /* None */ + CU_ASSERT(spdk_iscsi_check_chap_params(true, false, false, 0) == true); + + /* CHAP */ + CU_ASSERT(spdk_iscsi_check_chap_params(false, true, false, 0) == true); + + /* CHAP Mutual */ + CU_ASSERT(spdk_iscsi_check_chap_params(false, true, true, 0) == true); + + /* Check mutual exclusiveness of disabled and required */ + CU_ASSERT(spdk_iscsi_check_chap_params(true, true, false, 0) == false); + + /* Mutual requires Required */ + CU_ASSERT(spdk_iscsi_check_chap_params(false, false, true, 0) == false); + + /* Remaining combinations */ + CU_ASSERT(spdk_iscsi_check_chap_params(true, false, true, 0) == false); + CU_ASSERT(spdk_iscsi_check_chap_params(true, true, true, 0) == false); + + /* Valid auth group ID */ + CU_ASSERT(spdk_iscsi_check_chap_params(false, false, false, 1) == true); + + /* Invalid auth group ID */ + CU_ASSERT(spdk_iscsi_check_chap_params(false, false, false, -1) == false); +} + +int +main(int argc, char **argv) +{ + CU_pSuite suite = NULL; + unsigned int num_failures; + + if (argc < 2) { + fprintf(stderr, "usage: %s <config file>\n", argv[0]); + exit(1); + } + + if (CU_initialize_registry() != CUE_SUCCESS) { + return CU_get_error(); + } + + config_file = argv[1]; + + suite = CU_add_suite("iscsi_target_node_suite", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "add lun test cases", add_lun_test_cases) == NULL + || CU_add_test(suite, "config file fail cases", config_file_fail_cases) == NULL + || CU_add_test(suite, "allow any allowed case", allow_any_allowed) == NULL + || CU_add_test(suite, "allow ipv6 allowed case", allow_ipv6_allowed) == NULL + || CU_add_test(suite, "allow ipv6 denied case", allow_ipv6_denied) == NULL + || CU_add_test(suite, "allow ipv6 invalid case", allow_ipv6_invalid) == NULL + || CU_add_test(suite, "allow ipv4 allowed case", allow_ipv4_allowed) == NULL + || CU_add_test(suite, "allow ipv4 denied case", allow_ipv4_denied) == NULL + || CU_add_test(suite, "allow ipv4 invalid case", allow_ipv4_invalid) == NULL + || CU_add_test(suite, "node access allowed case", node_access_allowed) == NULL + || CU_add_test(suite, "node access denied case (empty netmask)", + node_access_denied_by_empty_netmask) == NULL + || CU_add_test(suite, "node access multiple initiator groups cases", + node_access_multi_initiator_groups_cases) == NULL + || CU_add_test(suite, "allow iscsi name case", + allow_iscsi_name_multi_maps_case) == NULL + || CU_add_test(suite, "chap param test cases", chap_param_test_cases) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/json/Makefile b/src/spdk/test/unit/lib/json/Makefile new file mode 100644 index 00000000..db38f27d --- /dev/null +++ b/src/spdk/test/unit/lib/json/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = json_parse.c json_util.c json_write.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/json/json_parse.c/.gitignore b/src/spdk/test/unit/lib/json/json_parse.c/.gitignore new file mode 100644 index 00000000..2b4445fd --- /dev/null +++ b/src/spdk/test/unit/lib/json/json_parse.c/.gitignore @@ -0,0 +1 @@ +json_parse_ut diff --git a/src/spdk/test/unit/lib/json/json_parse.c/Makefile b/src/spdk/test/unit/lib/json/json_parse.c/Makefile new file mode 100644 index 00000000..3d410024 --- /dev/null +++ b/src/spdk/test/unit/lib/json/json_parse.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 = json_parse_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/json/json_parse.c/json_parse_ut.c b/src/spdk/test/unit/lib/json/json_parse.c/json_parse_ut.c new file mode 100644 index 00000000..dae80476 --- /dev/null +++ b/src/spdk/test/unit/lib/json/json_parse.c/json_parse_ut.c @@ -0,0 +1,940 @@ +/*- + * 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 "json/json_parse.c" + +static uint8_t g_buf[1000]; +static void *g_end; +static struct spdk_json_val g_vals[100]; +static int g_cur_val; + +/* Fill buf with raw data */ +#define BUF_SETUP(in) \ + memset(g_buf, 0, sizeof(g_buf)); \ + if (sizeof(in) > 1) { \ + memcpy(g_buf, in, sizeof(in) - 1); \ + } \ + g_end = NULL + +/* + * Do two checks - first pass NULL for values to ensure the count is correct, + * then pass g_vals to get the actual values. + */ +#define PARSE_PASS_FLAGS(in, num_vals, trailing, flags) \ + BUF_SETUP(in); \ + CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, NULL, 0, &g_end, flags) == num_vals); \ + memset(g_vals, 0, sizeof(g_vals)); \ + CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, g_vals, sizeof(g_vals), &g_end, flags | SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE) == num_vals); \ + CU_ASSERT(g_end == g_buf + sizeof(in) - sizeof(trailing)); \ + CU_ASSERT(memcmp(g_end, trailing, sizeof(trailing) - 1) == 0); \ + g_cur_val = 0 + +#define PARSE_PASS(in, num_vals, trailing) \ + PARSE_PASS_FLAGS(in, num_vals, trailing, 0) + +#define PARSE_FAIL_FLAGS(in, retval, flags) \ + BUF_SETUP(in); \ + CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, NULL, 0, &g_end, flags) == retval) + +#define PARSE_FAIL(in, retval) \ + PARSE_FAIL_FLAGS(in, retval, 0) + +#define VAL_STRING_MATCH(str, var_type) \ + CU_ASSERT(g_vals[g_cur_val].type == var_type); \ + CU_ASSERT(g_vals[g_cur_val].len == sizeof(str) - 1); \ + if (g_vals[g_cur_val].len == sizeof(str) - 1 && sizeof(str) > 1) { \ + CU_ASSERT(memcmp(g_vals[g_cur_val].start, str, g_vals[g_cur_val].len) == 0); \ + } \ + g_cur_val++ + +#define VAL_STRING(str) VAL_STRING_MATCH(str, SPDK_JSON_VAL_STRING) +#define VAL_NAME(str) VAL_STRING_MATCH(str, SPDK_JSON_VAL_NAME) +#define VAL_NUMBER(num) VAL_STRING_MATCH(num, SPDK_JSON_VAL_NUMBER) + +#define VAL_LITERAL(str, val_type) \ + CU_ASSERT(g_vals[g_cur_val].type == val_type); \ + CU_ASSERT(g_vals[g_cur_val].len == strlen(str)); \ + if (g_vals[g_cur_val].len == strlen(str)) { \ + CU_ASSERT(memcmp(g_vals[g_cur_val].start, str, g_vals[g_cur_val].len) == 0); \ + } \ + g_cur_val++ + +#define VAL_TRUE() VAL_LITERAL("true", SPDK_JSON_VAL_TRUE) +#define VAL_FALSE() VAL_LITERAL("false", SPDK_JSON_VAL_FALSE) +#define VAL_NULL() VAL_LITERAL("null", SPDK_JSON_VAL_NULL) + +#define VAL_ARRAY_BEGIN(count) \ + CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_ARRAY_BEGIN); \ + CU_ASSERT(g_vals[g_cur_val].len == count); \ + g_cur_val++ + +#define VAL_ARRAY_END() \ + CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_ARRAY_END); \ + g_cur_val++ + +#define VAL_OBJECT_BEGIN(count) \ + CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_OBJECT_BEGIN); \ + CU_ASSERT(g_vals[g_cur_val].len == count); \ + g_cur_val++ + +#define VAL_OBJECT_END() \ + CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_OBJECT_END); \ + g_cur_val++ + +/* Simplified macros for string-only testing */ +#define STR_PASS(in, out) \ + PARSE_PASS("\"" in "\"", 1, ""); \ + VAL_STRING(out) + +#define STR_FAIL(in, retval) \ + PARSE_FAIL("\"" in "\"", retval) + +/* Simplified macros for number-only testing (no whitespace allowed) */ +#define NUM_PASS(in) \ + PARSE_PASS(in, 1, ""); \ + VAL_NUMBER(in) + +#define NUM_FAIL(in, retval) \ + PARSE_FAIL(in, retval) + +static void +test_parse_literal(void) +{ + PARSE_PASS("true", 1, ""); + VAL_TRUE(); + + PARSE_PASS(" true ", 1, ""); + VAL_TRUE(); + + PARSE_PASS("false", 1, ""); + VAL_FALSE(); + + PARSE_PASS("null", 1, ""); + VAL_NULL(); + + PARSE_PASS("trueaaa", 1, "aaa"); + VAL_TRUE(); + + PARSE_PASS("truefalse", 1, "false"); + VAL_TRUE(); + + PARSE_PASS("true false", 1, "false"); + VAL_TRUE(); + + PARSE_PASS("true,false", 1, ",false"); + VAL_TRUE(); + + PARSE_PASS("true,", 1, ","); + VAL_TRUE(); + + PARSE_FAIL("True", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("abcdef", SPDK_JSON_PARSE_INVALID); + + PARSE_FAIL("t", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("tru", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("f", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("fals", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("n", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("nul", SPDK_JSON_PARSE_INCOMPLETE); + + PARSE_FAIL("taaaaa", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("faaaaa", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("naaaaa", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_string_simple(void) +{ + PARSE_PASS("\"\"", 1, ""); + VAL_STRING(""); + + PARSE_PASS("\"hello world\"", 1, ""); + VAL_STRING("hello world"); + + PARSE_PASS(" \"hello world\" ", 1, ""); + VAL_STRING("hello world"); + + /* Unterminated string */ + PARSE_FAIL("\"hello world", SPDK_JSON_PARSE_INCOMPLETE); + + /* Trailing comma */ + PARSE_PASS("\"hello world\",", 1, ","); + VAL_STRING("hello world"); +} + +static void +test_parse_string_control_chars(void) +{ + /* U+0000 through U+001F must be escaped */ + STR_FAIL("\x00", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x01", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x02", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x03", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x04", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x05", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x06", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x07", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x08", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x09", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0A", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0B", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0C", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0D", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0E", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0F", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x10", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x11", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x12", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x13", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x14", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x15", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x16", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x17", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x18", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x19", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1A", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1B", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1C", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1D", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1E", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1F", SPDK_JSON_PARSE_INVALID); + STR_PASS(" ", " "); /* \x20 (first valid unescaped char) */ + + /* Test control chars in the middle of a string */ + STR_FAIL("abc\ndef", SPDK_JSON_PARSE_INVALID); + STR_FAIL("abc\tdef", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_string_utf8(void) +{ + /* Valid one-, two-, three-, and four-byte sequences */ + STR_PASS("\x41", "A"); + STR_PASS("\xC3\xB6", "\xC3\xB6"); + STR_PASS("\xE2\x88\x9A", "\xE2\x88\x9A"); + STR_PASS("\xF0\xA0\x9C\x8E", "\xF0\xA0\x9C\x8E"); + + /* Examples from RFC 3629 */ + STR_PASS("\x41\xE2\x89\xA2\xCE\x91\x2E", "\x41\xE2\x89\xA2\xCE\x91\x2E"); + STR_PASS("\xED\x95\x9C\xEA\xB5\xAD\xEC\x96\xB4", "\xED\x95\x9C\xEA\xB5\xAD\xEC\x96\xB4"); + STR_PASS("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E", "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E"); + STR_PASS("\xEF\xBB\xBF\xF0\xA3\x8E\xB4", "\xEF\xBB\xBF\xF0\xA3\x8E\xB4"); + + /* Edge cases */ + STR_PASS("\x7F", "\x7F"); + STR_FAIL("\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xC1", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xC2", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xC2\x80", "\xC2\x80"); + STR_PASS("\xC2\xBF", "\xC2\xBF"); + STR_PASS("\xDF\x80", "\xDF\x80"); + STR_PASS("\xDF\xBF", "\xDF\xBF"); + STR_FAIL("\xDF", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE0\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE0\x1F", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE0\x1F\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE0", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE0\xA0", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xE0\xA0\x80", "\xE0\xA0\x80"); + STR_PASS("\xE0\xA0\xBF", "\xE0\xA0\xBF"); + STR_FAIL("\xE0\xA0\xC0", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xE0\xBF\x80", "\xE0\xBF\x80"); + STR_PASS("\xE0\xBF\xBF", "\xE0\xBF\xBF"); + STR_FAIL("\xE0\xC0\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE1", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE1\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE1\x7F\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE1\x80\x7F", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xE1\x80\x80", "\xE1\x80\x80"); + STR_PASS("\xE1\x80\xBF", "\xE1\x80\xBF"); + STR_PASS("\xE1\xBF\x80", "\xE1\xBF\x80"); + STR_PASS("\xE1\xBF\xBF", "\xE1\xBF\xBF"); + STR_FAIL("\xE1\xC0\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE1\x80\xC0", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xEF\x80\x80", "\xEF\x80\x80"); + STR_PASS("\xEF\xBF\xBF", "\xEF\xBF\xBF"); + STR_FAIL("\xF0", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF0\x90", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF0\x90\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF0\x80\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF0\x8F\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xF0\x90\x80\x80", "\xF0\x90\x80\x80"); + STR_PASS("\xF0\x90\x80\xBF", "\xF0\x90\x80\xBF"); + STR_PASS("\xF0\x90\xBF\x80", "\xF0\x90\xBF\x80"); + STR_PASS("\xF0\xBF\x80\x80", "\xF0\xBF\x80\x80"); + STR_FAIL("\xF0\xC0\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF1", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF1\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF1\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF1\x80\x80\x7F", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xF1\x80\x80\x80", "\xF1\x80\x80\x80"); + STR_PASS("\xF1\x80\x80\xBF", "\xF1\x80\x80\xBF"); + STR_PASS("\xF1\x80\xBF\x80", "\xF1\x80\xBF\x80"); + STR_PASS("\xF1\xBF\x80\x80", "\xF1\xBF\x80\x80"); + STR_PASS("\xF3\x80\x80\x80", "\xF3\x80\x80\x80"); + STR_FAIL("\xF3\xC0\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF3\x80\xC0\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF3\x80\x80\xC0", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF4", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF4\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF4\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xF4\x80\x80\x80", "\xF4\x80\x80\x80"); + STR_PASS("\xF4\x8F\x80\x80", "\xF4\x8F\x80\x80"); + STR_PASS("\xF4\x8F\xBF\xBF", "\xF4\x8F\xBF\xBF"); + STR_FAIL("\xF4\x90\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF5", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF5\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF5\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF5\x80\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF5\x80\x80\x80\x80", SPDK_JSON_PARSE_INVALID); + + /* Overlong encodings */ + STR_FAIL("\xC0\x80", SPDK_JSON_PARSE_INVALID); + + /* Surrogate pairs */ + STR_FAIL("\xED\xA0\x80", SPDK_JSON_PARSE_INVALID); /* U+D800 First high surrogate */ + STR_FAIL("\xED\xAF\xBF", SPDK_JSON_PARSE_INVALID); /* U+DBFF Last high surrogate */ + STR_FAIL("\xED\xB0\x80", SPDK_JSON_PARSE_INVALID); /* U+DC00 First low surrogate */ + STR_FAIL("\xED\xBF\xBF", SPDK_JSON_PARSE_INVALID); /* U+DFFF Last low surrogate */ + STR_FAIL("\xED\xA1\x8C\xED\xBE\xB4", + SPDK_JSON_PARSE_INVALID); /* U+233B4 (invalid surrogate pair encoding) */ +} + +static void +test_parse_string_escapes_twochar(void) +{ + STR_PASS("\\\"", "\""); + STR_PASS("\\\\", "\\"); + STR_PASS("\\/", "/"); + STR_PASS("\\b", "\b"); + STR_PASS("\\f", "\f"); + STR_PASS("\\n", "\n"); + STR_PASS("\\r", "\r"); + STR_PASS("\\t", "\t"); + + STR_PASS("abc\\tdef", "abc\tdef"); + STR_PASS("abc\\\"def", "abc\"def"); + + /* Backslash at end of string (will be treated as escaped quote) */ + STR_FAIL("\\", SPDK_JSON_PARSE_INCOMPLETE); + STR_FAIL("abc\\", SPDK_JSON_PARSE_INCOMPLETE); + + /* Invalid C-like escapes */ + STR_FAIL("\\a", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\v", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\'", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\?", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\0", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\x00", SPDK_JSON_PARSE_INVALID); + + /* Other invalid escapes */ + STR_FAIL("\\B", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\z", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_string_escapes_unicode(void) +{ + STR_PASS("\\u0000", "\0"); + STR_PASS("\\u0001", "\1"); + STR_PASS("\\u0041", "A"); + STR_PASS("\\uAAAA", "\xEA\xAA\xAA"); + STR_PASS("\\uaaaa", "\xEA\xAA\xAA"); + STR_PASS("\\uAaAa", "\xEA\xAA\xAA"); + + STR_FAIL("\\u", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\u0", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\u00", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\u000", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\u000g", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\U", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\U0000", SPDK_JSON_PARSE_INVALID); + + PARSE_FAIL("\"\\u", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\u0", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\u00", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\u000", SPDK_JSON_PARSE_INCOMPLETE); + + /* Surrogate pair */ + STR_PASS("\\uD834\\uDD1E", "\xF0\x9D\x84\x9E"); + + /* Low surrogate without high */ + STR_FAIL("\\uDC00", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\uDC00\\uDC00", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\uDC00abcdef", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\uDEAD", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("\"\\uD834", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\uD834\\", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\uD834\\u", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\uD834\\uD", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\uD834\\uDD1", SPDK_JSON_PARSE_INCOMPLETE); + + /* High surrogate without low */ + STR_FAIL("\\uD800", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\uD800abcdef", SPDK_JSON_PARSE_INVALID); + + /* High surrogate followed by high surrogate */ + STR_FAIL("\\uD800\\uD800", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_number(void) +{ + NUM_PASS("0"); + NUM_PASS("1"); + NUM_PASS("100"); + NUM_PASS("-1"); + NUM_PASS("-0"); + NUM_PASS("3.0"); + NUM_PASS("3.00"); + NUM_PASS("3.001"); + NUM_PASS("3.14159"); + NUM_PASS("3.141592653589793238462643383279"); + NUM_PASS("1e400"); + NUM_PASS("1E400"); + NUM_PASS("0e10"); + NUM_PASS("0e0"); + NUM_PASS("-0e0"); + NUM_PASS("-0e+0"); + NUM_PASS("-0e-0"); + NUM_PASS("1e+400"); + NUM_PASS("1e-400"); + NUM_PASS("6.022e23"); + NUM_PASS("-1.234e+56"); + NUM_PASS("1.23e+56"); + NUM_PASS("-1.23e-56"); + NUM_PASS("1.23e-56"); + NUM_PASS("1e04"); + + /* Trailing garbage */ + PARSE_PASS("0A", 1, "A"); + VAL_NUMBER("0"); + + PARSE_PASS("0,", 1, ","); + VAL_NUMBER("0"); + + PARSE_PASS("0true", 1, "true"); + VAL_NUMBER("0"); + + PARSE_PASS("00", 1, "0"); + VAL_NUMBER("0"); + PARSE_FAIL("[00", SPDK_JSON_PARSE_INVALID); + + PARSE_PASS("007", 1, "07"); + VAL_NUMBER("0"); + PARSE_FAIL("[007]", SPDK_JSON_PARSE_INVALID); + + PARSE_PASS("345.678.1", 1, ".1"); + VAL_NUMBER("345.678"); + PARSE_FAIL("[345.678.1]", SPDK_JSON_PARSE_INVALID); + + PARSE_PASS("3.2e-4+5", 1, "+5"); + VAL_NUMBER("3.2e-4"); + PARSE_FAIL("[3.2e-4+5]", SPDK_JSON_PARSE_INVALID); + + PARSE_PASS("3.4.5", 1, ".5"); + VAL_NUMBER("3.4"); + PARSE_FAIL("[3.4.5]", SPDK_JSON_PARSE_INVALID); + + NUM_FAIL("345.", SPDK_JSON_PARSE_INCOMPLETE); + NUM_FAIL("+1", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("--1", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("3.", SPDK_JSON_PARSE_INCOMPLETE); + NUM_FAIL("3.+4", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("3.2e+-4", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("3.2e-+4", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("3e+", SPDK_JSON_PARSE_INCOMPLETE); + NUM_FAIL("3e-", SPDK_JSON_PARSE_INCOMPLETE); + NUM_FAIL("3.e4", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("3.2eX", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("-", SPDK_JSON_PARSE_INCOMPLETE); + NUM_FAIL("NaN", SPDK_JSON_PARSE_INVALID); + NUM_FAIL(".123", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_array(void) +{ + char buffer[SPDK_JSON_MAX_NESTING_DEPTH + 2] = {0}; + + PARSE_PASS("[]", 2, ""); + VAL_ARRAY_BEGIN(0); + VAL_ARRAY_END(); + + PARSE_PASS("[true]", 3, ""); + VAL_ARRAY_BEGIN(1); + VAL_TRUE(); + VAL_ARRAY_END(); + + PARSE_PASS("[true, false]", 4, ""); + VAL_ARRAY_BEGIN(2); + VAL_TRUE(); + VAL_FALSE(); + VAL_ARRAY_END(); + + PARSE_PASS("[\"hello\"]", 3, ""); + VAL_ARRAY_BEGIN(1); + VAL_STRING("hello"); + VAL_ARRAY_END(); + + PARSE_PASS("[[]]", 4, ""); + VAL_ARRAY_BEGIN(2); + VAL_ARRAY_BEGIN(0); + VAL_ARRAY_END(); + VAL_ARRAY_END(); + + PARSE_PASS("[\"hello\", \"world\"]", 4, ""); + VAL_ARRAY_BEGIN(2); + VAL_STRING("hello"); + VAL_STRING("world"); + VAL_ARRAY_END(); + + PARSE_PASS("[],", 2, ","); + VAL_ARRAY_BEGIN(0); + VAL_ARRAY_END(); + + PARSE_FAIL("]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("[true", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("[\"hello", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("[\"hello\"", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("[true,]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[,]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[,true]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[true}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[true,,true]", SPDK_JSON_PARSE_INVALID); + + /* Nested arrays exactly up to the allowed nesting depth */ + memset(buffer, '[', SPDK_JSON_MAX_NESTING_DEPTH); + buffer[SPDK_JSON_MAX_NESTING_DEPTH] = ' '; + PARSE_FAIL(buffer, SPDK_JSON_PARSE_INCOMPLETE); + + /* Nested arrays exceeding the maximum allowed nesting depth for this implementation */ + buffer[SPDK_JSON_MAX_NESTING_DEPTH] = '['; + PARSE_FAIL(buffer, SPDK_JSON_PARSE_MAX_DEPTH_EXCEEDED); +} + +static void +test_parse_object(void) +{ + PARSE_PASS("{}", 2, ""); + VAL_OBJECT_BEGIN(0); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\": true}", 4, ""); + VAL_OBJECT_BEGIN(2); + VAL_NAME("a"); + VAL_TRUE(); + VAL_OBJECT_END(); + + PARSE_PASS("{\"abc\": \"def\"}", 4, ""); + VAL_OBJECT_BEGIN(2); + VAL_NAME("abc"); + VAL_STRING("def"); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\": true, \"b\": false}", 6, ""); + VAL_OBJECT_BEGIN(4); + VAL_NAME("a"); + VAL_TRUE(); + VAL_NAME("b"); + VAL_FALSE(); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\": { \"b\": true } }", 7, ""); + VAL_OBJECT_BEGIN(5); + VAL_NAME("a"); + VAL_OBJECT_BEGIN(2); + VAL_NAME("b"); + VAL_TRUE(); + VAL_OBJECT_END(); + VAL_OBJECT_END(); + + PARSE_PASS("{\"{test\": 0}", 4, ""); + VAL_OBJECT_BEGIN(2); + VAL_NAME("{test"); + VAL_NUMBER("0"); + VAL_OBJECT_END(); + + PARSE_PASS("{\"test}\": 1}", 4, ""); + VAL_OBJECT_BEGIN(2); + VAL_NAME("test}"); + VAL_NUMBER("1"); + VAL_OBJECT_END(); + + PARSE_PASS("{\"\\\"\": 2}", 4, ""); + VAL_OBJECT_BEGIN(2); + VAL_NAME("\""); + VAL_NUMBER("2"); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\":true},", 4, ","); + VAL_OBJECT_BEGIN(2); + VAL_NAME("a"); + VAL_TRUE(); + VAL_OBJECT_END(); + + /* Object end without object begin (trailing garbage) */ + PARSE_PASS("true}", 1, "}"); + VAL_TRUE(); + + PARSE_PASS("0}", 1, "}"); + VAL_NUMBER("0"); + + PARSE_PASS("\"a\"}", 1, "}"); + VAL_STRING("a"); + + PARSE_FAIL("}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\"", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":true", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":true,", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":true]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{\"a\":true,}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{\"a\":true,\"}", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":true,\"b}", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":true,\"b\"}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{\"a\":true,\"b\":}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{\"a\":true,\"b\",}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{\"a\",}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{,\"a\": true}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{a:true}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{'a':true}", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_nesting(void) +{ + PARSE_PASS("[[[[[[[[]]]]]]]]", 16, ""); + + PARSE_PASS("{\"a\": [0, 1, 2]}", 8, ""); + VAL_OBJECT_BEGIN(6); + VAL_NAME("a"); + VAL_ARRAY_BEGIN(3); + VAL_NUMBER("0"); + VAL_NUMBER("1"); + VAL_NUMBER("2"); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\": [0, 1, 2], \"b\": 3 }", 10, ""); + VAL_OBJECT_BEGIN(8); + VAL_NAME("a"); + VAL_ARRAY_BEGIN(3); + VAL_NUMBER("0"); + VAL_NUMBER("1"); + VAL_NUMBER("2"); + VAL_ARRAY_END(); + VAL_NAME("b"); + VAL_NUMBER("3"); + VAL_OBJECT_END(); + + PARSE_PASS("[0, 1, {\"a\": 3}, 4, 5]", 10, ""); + VAL_ARRAY_BEGIN(8); + VAL_NUMBER("0"); + VAL_NUMBER("1"); + VAL_OBJECT_BEGIN(2); + VAL_NAME("a"); + VAL_NUMBER("3"); + VAL_OBJECT_END(); + VAL_NUMBER("4"); + VAL_NUMBER("5"); + VAL_ARRAY_END(); + + PARSE_PASS("\t[ { \"a\": {\"b\": [ {\"c\": 1}, 2 ],\n\"d\": 3}, \"e\" : 4}, 5 ] ", 20, ""); + VAL_ARRAY_BEGIN(18); + VAL_OBJECT_BEGIN(15); + VAL_NAME("a"); + VAL_OBJECT_BEGIN(10); + VAL_NAME("b"); + VAL_ARRAY_BEGIN(5); + VAL_OBJECT_BEGIN(2); + VAL_NAME("c"); + VAL_NUMBER("1"); + VAL_OBJECT_END(); + VAL_NUMBER("2"); + VAL_ARRAY_END(); + VAL_NAME("d"); + VAL_NUMBER("3"); + VAL_OBJECT_END(); + VAL_NAME("e"); + VAL_NUMBER("4"); + VAL_OBJECT_END(); + VAL_NUMBER("5"); + VAL_ARRAY_END(); + + /* Examples from RFC 7159 */ + PARSE_PASS( + "{\n" + " \"Image\": {\n" + " \"Width\": 800,\n" + " \"Height\": 600,\n" + " \"Title\": \"View from 15th Floor\",\n" + " \"Thumbnail\": {\n" + " \"Url\": \"http://www.example.com/image/481989943\",\n" + " \"Height\": 125,\n" + " \"Width\": 100\n" + " },\n" + " \"Animated\" : false,\n" + " \"IDs\": [116, 943, 234, 38793]\n" + " }\n" + "}\n", + 29, ""); + + VAL_OBJECT_BEGIN(27); + VAL_NAME("Image"); + VAL_OBJECT_BEGIN(24); + VAL_NAME("Width"); + VAL_NUMBER("800"); + VAL_NAME("Height"); + VAL_NUMBER("600"); + VAL_NAME("Title"); + VAL_STRING("View from 15th Floor"); + VAL_NAME("Thumbnail"); + VAL_OBJECT_BEGIN(6); + VAL_NAME("Url"); + VAL_STRING("http://www.example.com/image/481989943"); + VAL_NAME("Height"); + VAL_NUMBER("125"); + VAL_NAME("Width"); + VAL_NUMBER("100"); + VAL_OBJECT_END(); + VAL_NAME("Animated"); + VAL_FALSE(); + VAL_NAME("IDs"); + VAL_ARRAY_BEGIN(4); + VAL_NUMBER("116"); + VAL_NUMBER("943"); + VAL_NUMBER("234"); + VAL_NUMBER("38793"); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + VAL_OBJECT_END(); + + PARSE_PASS( + "[\n" + " {\n" + " \"precision\": \"zip\",\n" + " \"Latitude\": 37.7668,\n" + " \"Longitude\": -122.3959,\n" + " \"Address\": \"\",\n" + " \"City\": \"SAN FRANCISCO\",\n" + " \"State\": \"CA\",\n" + " \"Zip\": \"94107\",\n" + " \"Country\": \"US\"\n" + " },\n" + " {\n" + " \"precision\": \"zip\",\n" + " \"Latitude\": 37.371991,\n" + " \"Longitude\": -122.026020,\n" + " \"Address\": \"\",\n" + " \"City\": \"SUNNYVALE\",\n" + " \"State\": \"CA\",\n" + " \"Zip\": \"94085\",\n" + " \"Country\": \"US\"\n" + " }\n" + "]", + 38, ""); + + VAL_ARRAY_BEGIN(36); + VAL_OBJECT_BEGIN(16); + VAL_NAME("precision"); + VAL_STRING("zip"); + VAL_NAME("Latitude"); + VAL_NUMBER("37.7668"); + VAL_NAME("Longitude"); + VAL_NUMBER("-122.3959"); + VAL_NAME("Address"); + VAL_STRING(""); + VAL_NAME("City"); + VAL_STRING("SAN FRANCISCO"); + VAL_NAME("State"); + VAL_STRING("CA"); + VAL_NAME("Zip"); + VAL_STRING("94107"); + VAL_NAME("Country"); + VAL_STRING("US"); + VAL_OBJECT_END(); + VAL_OBJECT_BEGIN(16); + VAL_NAME("precision"); + VAL_STRING("zip"); + VAL_NAME("Latitude"); + VAL_NUMBER("37.371991"); + VAL_NAME("Longitude"); + VAL_NUMBER("-122.026020"); + VAL_NAME("Address"); + VAL_STRING(""); + VAL_NAME("City"); + VAL_STRING("SUNNYVALE"); + VAL_NAME("State"); + VAL_STRING("CA"); + VAL_NAME("Zip"); + VAL_STRING("94085"); + VAL_NAME("Country"); + VAL_STRING("US"); + VAL_OBJECT_END(); + VAL_ARRAY_END(); + + /* Trailing garbage */ + PARSE_PASS("{\"a\": [0, 1, 2]}]", 8, "]"); + VAL_OBJECT_BEGIN(6); + VAL_NAME("a"); + VAL_ARRAY_BEGIN(3); + VAL_NUMBER("0"); + VAL_NUMBER("1"); + VAL_NUMBER("2"); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\": [0, 1, 2]}}", 8, "}"); + PARSE_PASS("{\"a\": [0, 1, 2]}]", 8, "]"); + VAL_OBJECT_BEGIN(6); + VAL_NAME("a"); + VAL_ARRAY_BEGIN(3); + VAL_NUMBER("0"); + VAL_NUMBER("1"); + VAL_NUMBER("2"); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + + PARSE_FAIL("{\"a\": [0, 1, 2}]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{\"a\": [0, 1, 2]", SPDK_JSON_PARSE_INCOMPLETE); +} + + +static void +test_parse_comment(void) +{ + /* Comments are not allowed by the JSON RFC */ + PARSE_PASS("[0]", 3, ""); + PARSE_FAIL("/* test */[0]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[/* test */0]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[0/* test */]", SPDK_JSON_PARSE_INVALID); + + /* + * This is allowed since the parser stops once it reads a complete JSON object. + * The next parse call would fail (see tests above) when parsing the comment. + */ + PARSE_PASS("[0]/* test */", 3, "/* test */"); + + /* + * Test with non-standard comments enabled. + */ + PARSE_PASS_FLAGS("/* test */[0]", 3, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + VAL_ARRAY_BEGIN(1); + VAL_NUMBER("0"); + VAL_ARRAY_END(); + + PARSE_PASS_FLAGS("[/* test */0]", 3, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + VAL_ARRAY_BEGIN(1); + VAL_NUMBER("0"); + VAL_ARRAY_END(); + + PARSE_PASS_FLAGS("[0/* test */]", 3, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + VAL_ARRAY_BEGIN(1); + VAL_NUMBER("0"); + VAL_ARRAY_END(); + + PARSE_FAIL_FLAGS("/* test */", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + PARSE_FAIL_FLAGS("[/* test */", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + PARSE_FAIL_FLAGS("[0/* test */", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + + /* + * Single-line comments + */ + PARSE_PASS_FLAGS("// test\n0", 1, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + VAL_NUMBER("0"); + + PARSE_PASS_FLAGS("// test\r\n0", 1, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + VAL_NUMBER("0"); + + PARSE_PASS_FLAGS("// [0] test\n0", 1, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + VAL_NUMBER("0"); + + PARSE_FAIL_FLAGS("//", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + PARSE_FAIL_FLAGS("// test", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + PARSE_FAIL_FLAGS("//\n", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + + /* Invalid character following slash */ + PARSE_FAIL_FLAGS("[0/x", SPDK_JSON_PARSE_INVALID, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + + /* Single slash at end of buffer */ + PARSE_FAIL_FLAGS("[0/", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); +} + +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("json", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "parse_literal", test_parse_literal) == NULL || + CU_add_test(suite, "parse_string_simple", test_parse_string_simple) == NULL || + CU_add_test(suite, "parse_string_control_chars", test_parse_string_control_chars) == NULL || + CU_add_test(suite, "parse_string_utf8", test_parse_string_utf8) == NULL || + CU_add_test(suite, "parse_string_escapes_twochar", test_parse_string_escapes_twochar) == NULL || + CU_add_test(suite, "parse_string_escapes_unicode", test_parse_string_escapes_unicode) == NULL || + CU_add_test(suite, "parse_number", test_parse_number) == NULL || + CU_add_test(suite, "parse_array", test_parse_array) == NULL || + CU_add_test(suite, "parse_object", test_parse_object) == NULL || + CU_add_test(suite, "parse_nesting", test_parse_nesting) == NULL || + CU_add_test(suite, "parse_comment", test_parse_comment) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/json/json_util.c/.gitignore b/src/spdk/test/unit/lib/json/json_util.c/.gitignore new file mode 100644 index 00000000..02f6d50c --- /dev/null +++ b/src/spdk/test/unit/lib/json/json_util.c/.gitignore @@ -0,0 +1 @@ +json_util_ut diff --git a/src/spdk/test/unit/lib/json/json_util.c/Makefile b/src/spdk/test/unit/lib/json/json_util.c/Makefile new file mode 100644 index 00000000..c9a28208 --- /dev/null +++ b/src/spdk/test/unit/lib/json/json_util.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 = json_util_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/json/json_util.c/json_util_ut.c b/src/spdk/test/unit/lib/json/json_util.c/json_util_ut.c new file mode 100644 index 00000000..203b744e --- /dev/null +++ b/src/spdk/test/unit/lib/json/json_util.c/json_util_ut.c @@ -0,0 +1,963 @@ +/*- + * 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 "json/json_util.c" + +/* For spdk_json_parse() */ +#include "json/json_parse.c" + +#define NUM_SETUP(x) \ + snprintf(buf, sizeof(buf), "%s", x); \ + v.type = SPDK_JSON_VAL_NUMBER; \ + v.start = buf; \ + v.len = sizeof(x) - 1 + +#define NUM_UINT16_PASS(s, i) \ + NUM_SETUP(s); \ + CU_ASSERT(spdk_json_number_to_uint16(&v, &u16) == 0); \ + CU_ASSERT(u16 == i) + +#define NUM_UINT16_FAIL(s) \ + NUM_SETUP(s); \ + CU_ASSERT(spdk_json_number_to_uint16(&v, &u16) != 0) + +#define NUM_INT32_PASS(s, i) \ + NUM_SETUP(s); \ + CU_ASSERT(spdk_json_number_to_int32(&v, &i32) == 0); \ + CU_ASSERT(i32 == i) + +#define NUM_INT32_FAIL(s) \ + NUM_SETUP(s); \ + CU_ASSERT(spdk_json_number_to_int32(&v, &i32) != 0) + +#define NUM_UINT64_PASS(s, i) \ + NUM_SETUP(s); \ + CU_ASSERT(spdk_json_number_to_uint64(&v, &u64) == 0); \ + CU_ASSERT(u64 == i) + +#define NUM_UINT64_FAIL(s) \ + NUM_SETUP(s); \ + CU_ASSERT(spdk_json_number_to_uint64(&v, &u64) != 0) + +static void +test_strequal(void) +{ + struct spdk_json_val v; + + v.type = SPDK_JSON_VAL_STRING; + v.start = "test"; + v.len = sizeof("test") - 1; + CU_ASSERT(spdk_json_strequal(&v, "test") == true); + CU_ASSERT(spdk_json_strequal(&v, "TEST") == false); + CU_ASSERT(spdk_json_strequal(&v, "hello") == false); + CU_ASSERT(spdk_json_strequal(&v, "t") == false); + + v.type = SPDK_JSON_VAL_NAME; + CU_ASSERT(spdk_json_strequal(&v, "test") == true); + + v.type = SPDK_JSON_VAL_NUMBER; + CU_ASSERT(spdk_json_strequal(&v, "test") == false); + + v.type = SPDK_JSON_VAL_STRING; + v.start = "test\0hello"; + v.len = sizeof("test\0hello") - 1; + CU_ASSERT(spdk_json_strequal(&v, "test") == false); +} + +static void +test_num_to_uint16(void) +{ + struct spdk_json_val v; + char buf[100]; + uint16_t u16 = 0; + + NUM_SETUP("1234"); + CU_ASSERT(spdk_json_number_to_uint16(&v, &u16) == 0); + CU_ASSERT(u16 == 1234); + + NUM_UINT16_PASS("0", 0); + NUM_UINT16_PASS("1234", 1234); + NUM_UINT16_PASS("1234.00000", 1234); + NUM_UINT16_PASS("1.2e1", 12); + NUM_UINT16_PASS("12340e-1", 1234); + + NUM_UINT16_FAIL("1.2"); + NUM_UINT16_FAIL("-1234"); + NUM_UINT16_FAIL("1.2E0"); + NUM_UINT16_FAIL("1.234e1"); + NUM_UINT16_FAIL("12341e-1"); +} + +static void +test_num_to_int32(void) +{ + struct spdk_json_val v; + char buf[100]; + int32_t i32 = 0; + + NUM_SETUP("1234"); + CU_ASSERT(spdk_json_number_to_int32(&v, &i32) == 0); + CU_ASSERT(i32 == 1234); + + + NUM_INT32_PASS("0", 0); + NUM_INT32_PASS("1234", 1234); + NUM_INT32_PASS("-1234", -1234); + NUM_INT32_PASS("1234.00000", 1234); + NUM_INT32_PASS("1.2e1", 12); + NUM_INT32_PASS("12340e-1", 1234); + NUM_INT32_PASS("-0", 0); + + NUM_INT32_FAIL("1.2"); + NUM_INT32_FAIL("1.2E0"); + NUM_INT32_FAIL("1.234e1"); + NUM_INT32_FAIL("12341e-1"); +} + +static void +test_num_to_uint64(void) +{ + struct spdk_json_val v; + char buf[100]; + uint64_t u64 = 0; + + NUM_SETUP("1234"); + CU_ASSERT(spdk_json_number_to_uint64(&v, &u64) == 0); + CU_ASSERT(u64 == 1234); + + + NUM_UINT64_PASS("0", 0); + NUM_UINT64_PASS("1234", 1234); + NUM_UINT64_PASS("1234.00000", 1234); + NUM_UINT64_PASS("1.2e1", 12); + NUM_UINT64_PASS("12340e-1", 1234); + NUM_UINT64_PASS("123456780e-1", 12345678); + + NUM_UINT64_FAIL("1.2"); + NUM_UINT64_FAIL("-1234"); + NUM_UINT64_FAIL("1.2E0"); + NUM_UINT64_FAIL("1.234e1"); + NUM_UINT64_FAIL("12341e-1"); + NUM_UINT64_FAIL("123456781e-1"); +} + +static void +test_decode_object(void) +{ + struct my_object { + char *my_name; + uint32_t my_int; + bool my_bool; + }; + struct spdk_json_val object[] = { + {"", 6, SPDK_JSON_VAL_OBJECT_BEGIN}, + {"first", 5, SPDK_JSON_VAL_NAME}, + {"HELLO", 5, SPDK_JSON_VAL_STRING}, + {"second", 6, SPDK_JSON_VAL_NAME}, + {"234", 3, SPDK_JSON_VAL_NUMBER}, + {"third", 5, SPDK_JSON_VAL_NAME}, + {"", 1, SPDK_JSON_VAL_TRUE}, + {"", 0, SPDK_JSON_VAL_OBJECT_END}, + }; + + struct spdk_json_object_decoder decoders[] = { + {"first", offsetof(struct my_object, my_name), spdk_json_decode_string, false}, + {"second", offsetof(struct my_object, my_int), spdk_json_decode_uint32, false}, + {"third", offsetof(struct my_object, my_bool), spdk_json_decode_bool, false}, + {"fourth", offsetof(struct my_object, my_bool), spdk_json_decode_bool, true}, + }; + struct my_object output = { + .my_name = NULL, + .my_int = 0, + .my_bool = false, + }; + uint32_t answer = 234; + char *answer_str = "HELLO"; + bool answer_bool = true; + + /* Passing Test: object containing simple types */ + CU_ASSERT(spdk_json_decode_object(object, decoders, 4, &output) == 0); + SPDK_CU_ASSERT_FATAL(output.my_name != NULL); + CU_ASSERT(memcmp(output.my_name, answer_str, 6) == 0); + CU_ASSERT(output.my_int == answer); + CU_ASSERT(output.my_bool == answer_bool); + + /* Failing Test: member with no matching decoder */ + /* i.e. I remove the matching decoder from the boolean argument */ + CU_ASSERT(spdk_json_decode_object(object, decoders, 2, &output) != 0); + + /* Failing Test: non-optional decoder with no corresponding member */ + + decoders[3].optional = false; + CU_ASSERT(spdk_json_decode_object(object, decoders, 4, &output) != 0); + + /* return to base state */ + decoders[3].optional = true; + + /* Failing Test: duplicated names for json values */ + object[3].start = "first"; + object[3].len = 5; + CU_ASSERT(spdk_json_decode_object(object, decoders, 3, &output) != 0); + + /* return to base state */ + object[3].start = "second"; + object[3].len = 6; + + /* Failing Test: invalid value for decoder */ + object[2].start = "HELO"; + CU_ASSERT(spdk_json_decode_object(object, decoders, 3, &output) != 0); + + /* return to base state */ + object[2].start = "HELLO"; + + /* Failing Test: not an object */ + object[0].type = SPDK_JSON_VAL_ARRAY_BEGIN; + CU_ASSERT(spdk_json_decode_object(object, decoders, 3, &output) != 0); + + free(output.my_name); +} + +static void +test_decode_array(void) +{ + struct spdk_json_val values[4]; + uint32_t my_int[2] = {0, 0}; + char *my_string[2] = {NULL, NULL}; + size_t out_size; + + /* passing integer test */ + values[0].type = SPDK_JSON_VAL_ARRAY_BEGIN; + values[0].len = 2; + values[1].type = SPDK_JSON_VAL_NUMBER; + values[1].len = 4; + values[1].start = "1234"; + values[2].type = SPDK_JSON_VAL_NUMBER; + values[2].len = 4; + values[2].start = "5678"; + values[3].type = SPDK_JSON_VAL_ARRAY_END; + CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_uint32, my_int, 2, &out_size, + sizeof(uint32_t)) == 0); + CU_ASSERT(my_int[0] == 1234); + CU_ASSERT(my_int[1] == 5678); + CU_ASSERT(out_size == 2); + + /* array length exceeds max */ + values[0].len = 3; + CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_uint32, my_int, 2, &out_size, + sizeof(uint32_t)) != 0); + + /* mixed types */ + values[0].len = 2; + values[2].type = SPDK_JSON_VAL_STRING; + values[2].len = 5; + values[2].start = "HELLO"; + CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_uint32, my_int, 2, &out_size, + sizeof(uint32_t)) != 0); + + /* no array start */ + values[0].type = SPDK_JSON_VAL_NUMBER; + values[2].type = SPDK_JSON_VAL_NUMBER; + values[2].len = 4; + values[2].start = "5678"; + CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_uint32, my_int, 2, &out_size, + sizeof(uint32_t)) != 0); + + /* mismatched array type and parser */ + values[0].type = SPDK_JSON_VAL_ARRAY_BEGIN; + values[1].type = SPDK_JSON_VAL_STRING; + values[1].len = 5; + values[1].start = "HELLO"; + values[2].type = SPDK_JSON_VAL_STRING; + values[2].len = 5; + values[2].start = "WORLD"; + CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_uint32, my_int, 2, &out_size, + sizeof(uint32_t)) != 0); + + /* passing String example */ + CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_string, my_string, 2, &out_size, + sizeof(char *)) == 0); + SPDK_CU_ASSERT_FATAL(my_string[0] != NULL); + SPDK_CU_ASSERT_FATAL(my_string[1] != NULL); + CU_ASSERT(memcmp(my_string[0], "HELLO", 6) == 0); + CU_ASSERT(memcmp(my_string[1], "WORLD", 6) == 0); + CU_ASSERT(out_size == 2); + + free(my_string[0]); + free(my_string[1]); +} + +static void +test_decode_bool(void) +{ + struct spdk_json_val v; + bool b; + + /* valid bool (true) */ + v.type = SPDK_JSON_VAL_TRUE; + b = false; + CU_ASSERT(spdk_json_decode_bool(&v, &b) == 0); + CU_ASSERT(b == true); + + /* valid bool (false) */ + v.type = SPDK_JSON_VAL_FALSE; + b = true; + CU_ASSERT(spdk_json_decode_bool(&v, &b) == 0); + CU_ASSERT(b == false); + + /* incorrect type */ + v.type = SPDK_JSON_VAL_NULL; + CU_ASSERT(spdk_json_decode_bool(&v, &b) != 0); +} + +static void +test_decode_int32(void) +{ + struct spdk_json_val v; + int32_t i; + + /* correct type and valid value */ + v.type = SPDK_JSON_VAL_NUMBER; + v.start = "33"; + v.len = 2; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0); + CU_ASSERT(i == 33) + + /* correct type and invalid value (float) */ + v.start = "32.45"; + v.len = 5; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0); + + /* incorrect type */ + v.type = SPDK_JSON_VAL_STRING; + v.start = "String"; + v.len = 6; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0); + + /* incorrect type */ + v.type = SPDK_JSON_VAL_TRUE; + CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0); + + /* edge case (integer max) */ + v.type = SPDK_JSON_VAL_NUMBER; + v.start = "2147483647"; + v.len = 10; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0); + CU_ASSERT(i == 2147483647); + + /* invalid value (overflow) */ + v.start = "2147483648"; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0); + + /* edge case (integer min) */ + v.type = SPDK_JSON_VAL_NUMBER; + v.start = "-2147483648"; + v.len = 11; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0); + CU_ASSERT(i == -2147483648); + + /* invalid value (overflow) */ + v.start = "-2147483649"; + CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0); + + /* valid exponent */ + v.start = "4e3"; + v.len = 3; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0); + CU_ASSERT(i == 4000); + + /* invalid negative exponent */ + v.start = "-400e-4"; + v.len = 7; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0); + + /* invalid negative exponent */ + v.start = "400e-4"; + v.len = 6; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0); + + /* valid negative exponent */ + v.start = "-400e-2"; + v.len = 7; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0); + CU_ASSERT(i == -4) + + /* invalid exponent (overflow) */ + v.start = "-2e32"; + v.len = 5; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0); + + /* valid exponent with decimal */ + v.start = "2.13e2"; + v.len = 6; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0); + CU_ASSERT(i == 213) + + /* invalid exponent with decimal */ + v.start = "2.134e2"; + v.len = 7; + i = 0; + CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0); +} + +static void +test_decode_uint16(void) +{ + struct spdk_json_val v; + uint32_t i; + + /* incorrect type */ + v.type = SPDK_JSON_VAL_STRING; + v.start = "Strin"; + v.len = 5; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0); + + /* invalid value (float) */ + v.type = SPDK_JSON_VAL_NUMBER; + v.start = "123.4"; + v.len = 5; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0); + + /* edge case (0) */ + v.start = "0"; + v.len = 1; + i = 456; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) == 0); + CU_ASSERT(i == 0); + + /* invalid value (negative) */ + v.start = "-1"; + v.len = 2; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0); + + /* edge case (maximum) */ + v.start = "65535"; + v.len = 5; + i = 0; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) == 0); + CU_ASSERT(i == 65535); + + /* invalid value (overflow) */ + v.start = "65536"; + v.len = 5; + i = 0; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0); + + /* valid exponent */ + v.start = "66E2"; + v.len = 4; + i = 0; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) == 0); + CU_ASSERT(i == 6600); + + /* invalid exponent (overflow) */ + v.start = "66E3"; + v.len = 4; + i = 0; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0); + + /* invalid exponent (decimal) */ + v.start = "65.535E2"; + v.len = 7; + i = 0; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0); + + /* valid exponent with decimal */ + v.start = "65.53E2"; + v.len = 7; + i = 0; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) == 0); + CU_ASSERT(i == 6553); + + /* invalid negative exponent */ + v.start = "40e-2"; + v.len = 5; + i = 0; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0); + + /* invalid negative exponent */ + v.start = "-40e-1"; + v.len = 6; + i = 0; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0); + + /* valid negative exponent */ + v.start = "40e-1"; + v.len = 5; + i = 0; + CU_ASSERT(spdk_json_decode_uint16(&v, &i) == 0); + CU_ASSERT(i == 4); +} + +static void +test_decode_uint32(void) +{ + struct spdk_json_val v; + uint32_t i; + + /* incorrect type */ + v.type = SPDK_JSON_VAL_STRING; + v.start = "String"; + v.len = 6; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0); + + /* invalid value (float) */ + v.type = SPDK_JSON_VAL_NUMBER; + v.start = "123.45"; + v.len = 6; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0); + + /* edge case (0) */ + v.start = "0"; + v.len = 1; + i = 456; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0); + CU_ASSERT(i == 0); + + /* invalid value (negative) */ + v.start = "-1"; + v.len = 2; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0); + + /* edge case (maximum) */ + v.start = "4294967295"; + v.len = 10; + i = 0; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0); + CU_ASSERT(i == 4294967295); + + /* invalid value (overflow) */ + v.start = "4294967296"; + v.len = 10; + i = 0; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0); + + /* valid exponent */ + v.start = "42E2"; + v.len = 4; + i = 0; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0); + CU_ASSERT(i == 4200); + + /* invalid exponent (overflow) */ + v.start = "42e32"; + v.len = 5; + i = 0; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0); + + /* invalid exponent (decimal) */ + v.start = "42.323E2"; + v.len = 8; + i = 0; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0); + + /* valid exponent with decimal */ + v.start = "42.32E2"; + v.len = 7; + i = 0; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0); + CU_ASSERT(i == 4232); + + /* invalid negative exponent */ + v.start = "400e-4"; + v.len = 6; + i = 0; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0); + + /* invalid negative exponent */ + v.start = "-400e-2"; + v.len = 7; + i = 0; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0); + + /* valid negative exponent */ + v.start = "400e-2"; + v.len = 6; + i = 0; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0); + CU_ASSERT(i == 4); + + /* valid negative exponent */ + v.start = "10e-1"; + v.len = 5; + i = 0; + CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0); + CU_ASSERT(i == 1) +} + +static void +test_decode_uint64(void) +{ + struct spdk_json_val v; + uint64_t i; + + /* incorrect type */ + v.type = SPDK_JSON_VAL_STRING; + v.start = "String"; + v.len = 6; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0); + + /* invalid value (float) */ + v.type = SPDK_JSON_VAL_NUMBER; + v.start = "123.45"; + v.len = 6; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0); + + /* edge case (0) */ + v.start = "0"; + v.len = 1; + i = 456; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) == 0); + CU_ASSERT(i == 0); + + /* invalid value (negative) */ + v.start = "-1"; + v.len = 2; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0); + + /* edge case (maximum) */ + v.start = "18446744073709551615"; + v.len = 20; + i = 0; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) == 0); + CU_ASSERT(i == 18446744073709551615U); + + /* invalid value (overflow) */ + v.start = "18446744073709551616"; + v.len = 20; + i = 0; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0); + + /* valid exponent */ + v.start = "42E2"; + v.len = 4; + i = 0; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) == 0); + CU_ASSERT(i == 4200); + + /* invalid exponent (overflow) */ + v.start = "42e64"; + v.len = 5; + i = 0; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0); + + /* invalid exponent (decimal) */ + v.start = "42.323E2"; + v.len = 8; + i = 0; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0); + + /* valid exponent with decimal */ + v.start = "42.32E2"; + v.len = 7; + i = 0; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) == 0); + CU_ASSERT(i == 4232); + + /* invalid negative exponent */ + v.start = "400e-4"; + v.len = 6; + i = 0; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0); + + /* invalid negative exponent */ + v.start = "-400e-2"; + v.len = 7; + i = 0; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0); + + /* valid negative exponent */ + v.start = "400e-2"; + v.len = 6; + i = 0; + CU_ASSERT(spdk_json_decode_uint64(&v, &i) == 0); + CU_ASSERT(i == 4) +} + +static void +test_decode_string(void) +{ + struct spdk_json_val v; + char *value = NULL; + + /* Passing Test: Standard */ + v.type = SPDK_JSON_VAL_STRING; + v.start = "HELLO"; + v.len = 5; + CU_ASSERT(spdk_json_decode_string(&v, &value) == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(memcmp(value, v.start, 6) == 0); + + /* Edge Test: Empty String */ + v.start = ""; + v.len = 0; + CU_ASSERT(spdk_json_decode_string(&v, &value) == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(memcmp(value, v.start, 1) == 0); + + /* + * Failing Test: Null Terminator In String + * It is valid for a json string to contain \u0000 and the parser will accept it. + * However, a null terminated C string cannot contain '\0' and should be rejected + * if that character is found before the end of the string. + */ + v.start = "HELO"; + v.len = 5; + CU_ASSERT(spdk_json_decode_string(&v, &value) != 0); + + /* Failing Test: Wrong Type */ + v.start = "45673"; + v.type = SPDK_JSON_VAL_NUMBER; + CU_ASSERT(spdk_json_decode_string(&v, &value) != 0); + + /* Passing Test: Special Characters */ + v.type = SPDK_JSON_VAL_STRING; + v.start = "HE\bLL\tO\\WORLD"; + v.len = 13; + CU_ASSERT(spdk_json_decode_string(&v, &value) == 0); + SPDK_CU_ASSERT_FATAL(value != NULL); + CU_ASSERT(memcmp(value, v.start, 14) == 0); + + free(value); +} + +char ut_json_text[] = + "{" + " \"string\": \"Some string data\"," + " \"object\": { " + " \"another_string\": \"Yet anoter string data\"," + " \"array name with space\": [1, [], {} ]" + " }," + " \"array\": [ \"Text\", 2, {} ]" + "}" + ; + +static void +test_find(void) +{ + struct spdk_json_val *values, *key, *val, *key2, *val2; + ssize_t values_cnt; + ssize_t rc; + + values_cnt = spdk_json_parse(ut_json_text, strlen(ut_json_text), NULL, 0, NULL, 0); + SPDK_CU_ASSERT_FATAL(values_cnt > 0); + + values = calloc(values_cnt, sizeof(struct spdk_json_val)); + SPDK_CU_ASSERT_FATAL(values != NULL); + + rc = spdk_json_parse(ut_json_text, strlen(ut_json_text), values, values_cnt, NULL, 0); + SPDK_CU_ASSERT_FATAL(values_cnt == rc); + + key = val = NULL; + rc = spdk_json_find(values, "string", &key, &val, SPDK_JSON_VAL_STRING); + CU_ASSERT(rc == 0); + + CU_ASSERT(key != NULL && spdk_json_strequal(key, "string") == true); + CU_ASSERT(val != NULL && spdk_json_strequal(val, "Some string data") == true) + + key = val = NULL; + rc = spdk_json_find(values, "object", &key, &val, SPDK_JSON_VAL_OBJECT_BEGIN); + CU_ASSERT(rc == 0); + + CU_ASSERT(key != NULL && spdk_json_strequal(key, "object") == true); + + /* Find key in "object" by passing SPDK_JSON_VAL_ANY to match any type */ + key2 = val2 = NULL; + rc = spdk_json_find(val, "array name with space", &key2, &val2, SPDK_JSON_VAL_ANY); + CU_ASSERT(rc == 0); + CU_ASSERT(key2 != NULL && spdk_json_strequal(key2, "array name with space") == true); + CU_ASSERT(val2 != NULL && val2->type == SPDK_JSON_VAL_ARRAY_BEGIN); + + /* Find the "array" key in "object" by passing SPDK_JSON_VAL_ARRAY_BEGIN to match only array */ + key2 = val2 = NULL; + rc = spdk_json_find(val, "array name with space", &key2, &val2, SPDK_JSON_VAL_ARRAY_BEGIN); + CU_ASSERT(rc == 0); + CU_ASSERT(key2 != NULL && spdk_json_strequal(key2, "array name with space") == true); + CU_ASSERT(val2 != NULL && val2->type == SPDK_JSON_VAL_ARRAY_BEGIN); + + /* Negative test - key doesn't exist */ + key2 = val2 = NULL; + rc = spdk_json_find(val, "this_key_does_not_exist", &key2, &val2, SPDK_JSON_VAL_ANY); + CU_ASSERT(rc == -ENOENT); + + /* Negative test - key type doesn't match */ + key2 = val2 = NULL; + rc = spdk_json_find(val, "another_string", &key2, &val2, SPDK_JSON_VAL_ARRAY_BEGIN); + CU_ASSERT(rc == -EDOM); + + free(values); +} + +static void +test_iterating(void) +{ + struct spdk_json_val *values; + struct spdk_json_val *string_key; + struct spdk_json_val *object_key, *object_val; + struct spdk_json_val *array_key, *array_val; + struct spdk_json_val *another_string_key; + struct spdk_json_val *array_name_with_space_key, *array_name_with_space_val; + struct spdk_json_val *it; + ssize_t values_cnt; + ssize_t rc; + + values_cnt = spdk_json_parse(ut_json_text, strlen(ut_json_text), NULL, 0, NULL, 0); + SPDK_CU_ASSERT_FATAL(values_cnt > 0); + + values = calloc(values_cnt, sizeof(struct spdk_json_val)); + SPDK_CU_ASSERT_FATAL(values != NULL); + + rc = spdk_json_parse(ut_json_text, strlen(ut_json_text), values, values_cnt, NULL, 0); + SPDK_CU_ASSERT_FATAL(values_cnt == rc); + + /* Iterate over object keys. JSON spec doesn't guarantee order of keys in object but + * SPDK implementation implicitly does. + */ + string_key = spdk_json_object_first(values); + CU_ASSERT(spdk_json_strequal(string_key, "string") == true); + + object_key = spdk_json_next(string_key); + object_val = spdk_json_value(object_key); + CU_ASSERT(spdk_json_strequal(object_key, "object") == true); + + array_key = spdk_json_next(object_key); + array_val = spdk_json_value(array_key); + CU_ASSERT(spdk_json_strequal(array_key, "array") == true); + + /* NULL '}' */ + CU_ASSERT(spdk_json_next(array_key) == NULL); + + /* Iterate over subobjects */ + another_string_key = spdk_json_object_first(object_val); + CU_ASSERT(spdk_json_strequal(another_string_key, "another_string") == true); + + array_name_with_space_key = spdk_json_next(another_string_key); + array_name_with_space_val = spdk_json_value(array_name_with_space_key); + CU_ASSERT(spdk_json_strequal(array_name_with_space_key, "array name with space") == true); + + CU_ASSERT(spdk_json_next(array_name_with_space_key) == NULL); + + /* Iterate over array in subobject */ + it = spdk_json_array_first(array_name_with_space_val); + SPDK_CU_ASSERT_FATAL(it != NULL); + CU_ASSERT(it->type == SPDK_JSON_VAL_NUMBER); + + it = spdk_json_next(it); + SPDK_CU_ASSERT_FATAL(it != NULL); + CU_ASSERT(it->type == SPDK_JSON_VAL_ARRAY_BEGIN); + + it = spdk_json_next(it); + SPDK_CU_ASSERT_FATAL(it != NULL); + CU_ASSERT(it->type == SPDK_JSON_VAL_OBJECT_BEGIN); + + it = spdk_json_next(it); + CU_ASSERT(it == NULL); + + /* Iterate over array in root object */ + it = spdk_json_array_first(array_val); + SPDK_CU_ASSERT_FATAL(it != NULL); + CU_ASSERT(it->type == SPDK_JSON_VAL_STRING); + + it = spdk_json_next(it); + SPDK_CU_ASSERT_FATAL(it != NULL); + CU_ASSERT(it->type == SPDK_JSON_VAL_NUMBER); + + it = spdk_json_next(it); + SPDK_CU_ASSERT_FATAL(it != NULL); + CU_ASSERT(it->type == SPDK_JSON_VAL_OBJECT_BEGIN); + + /* Array end */ + it = spdk_json_next(it); + CU_ASSERT(it == NULL); + + free(values); +} + +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("json", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "strequal", test_strequal) == NULL || + CU_add_test(suite, "num_to_uint16", test_num_to_uint16) == NULL || + CU_add_test(suite, "num_to_int32", test_num_to_int32) == NULL || + CU_add_test(suite, "num_to_uint64", test_num_to_uint64) == NULL || + CU_add_test(suite, "decode_object", test_decode_object) == NULL || + CU_add_test(suite, "decode_array", test_decode_array) == NULL || + CU_add_test(suite, "decode_bool", test_decode_bool) == NULL || + CU_add_test(suite, "decode_uint16", test_decode_uint16) == NULL || + CU_add_test(suite, "decode_int32", test_decode_int32) == NULL || + CU_add_test(suite, "decode_uint32", test_decode_uint32) == NULL || + CU_add_test(suite, "decode_uint64", test_decode_uint64) == NULL || + CU_add_test(suite, "decode_string", test_decode_string) == NULL || + CU_add_test(suite, "find_object", test_find) == NULL || + CU_add_test(suite, "iterating", test_iterating) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/json/json_write.c/.gitignore b/src/spdk/test/unit/lib/json/json_write.c/.gitignore new file mode 100644 index 00000000..dd576b23 --- /dev/null +++ b/src/spdk/test/unit/lib/json/json_write.c/.gitignore @@ -0,0 +1 @@ +json_write_ut diff --git a/src/spdk/test/unit/lib/json/json_write.c/Makefile b/src/spdk/test/unit/lib/json/json_write.c/Makefile new file mode 100644 index 00000000..9fe1fa91 --- /dev/null +++ b/src/spdk/test/unit/lib/json/json_write.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 = json_write_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/json/json_write.c/json_write_ut.c b/src/spdk/test/unit/lib/json/json_write.c/json_write_ut.c new file mode 100644 index 00000000..70c62fe1 --- /dev/null +++ b/src/spdk/test/unit/lib/json/json_write.c/json_write_ut.c @@ -0,0 +1,745 @@ +/*- + * 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 "json/json_write.c" +#include "json/json_parse.c" + +#include "spdk/util.h" + +static uint8_t g_buf[1000]; +static uint8_t *g_write_pos; + +static int +write_cb(void *cb_ctx, const void *data, size_t size) +{ + size_t buf_free = g_buf + sizeof(g_buf) - g_write_pos; + + if (size > buf_free) { + return -1; + } + + memcpy(g_write_pos, data, size); + g_write_pos += size; + + return 0; +} + +#define BEGIN() \ + memset(g_buf, 0, sizeof(g_buf)); \ + g_write_pos = g_buf; \ + w = spdk_json_write_begin(write_cb, NULL, 0); \ + SPDK_CU_ASSERT_FATAL(w != NULL) + +#define END(json) \ + CU_ASSERT(spdk_json_write_end(w) == 0); \ + CU_ASSERT(g_write_pos - g_buf == sizeof(json) - 1); \ + CU_ASSERT(memcmp(json, g_buf, sizeof(json) - 1) == 0) + +#define END_NOCMP() \ + CU_ASSERT(spdk_json_write_end(w) == 0) + +#define END_FAIL() \ + CU_ASSERT(spdk_json_write_end(w) < 0) + +#define VAL_STRING(str) \ + CU_ASSERT(spdk_json_write_string_raw(w, str, sizeof(str) - 1) == 0) + +#define VAL_STRING_FAIL(str) \ + CU_ASSERT(spdk_json_write_string_raw(w, str, sizeof(str) - 1) < 0) + +#define STR_PASS(in, out) \ + BEGIN(); VAL_STRING(in); END("\"" out "\"") + +#define STR_FAIL(in) \ + BEGIN(); VAL_STRING_FAIL(in); END_FAIL() + +#define VAL_STRING_UTF16LE(str) \ + CU_ASSERT(spdk_json_write_string_utf16le_raw(w, (const uint16_t *)str, sizeof(str) / sizeof(uint16_t) - 1) == 0) + +#define VAL_STRING_UTF16LE_FAIL(str) \ + CU_ASSERT(spdk_json_write_string_utf16le_raw(w, (const uint16_t *)str, sizeof(str) / sizeof(uint16_t) - 1) < 0) + +#define STR_UTF16LE_PASS(in, out) \ + BEGIN(); VAL_STRING_UTF16LE(in); END("\"" out "\"") + +#define STR_UTF16LE_FAIL(in) \ + BEGIN(); VAL_STRING_UTF16LE_FAIL(in); END_FAIL() + +#define VAL_NAME(name) \ + CU_ASSERT(spdk_json_write_name_raw(w, name, sizeof(name) - 1) == 0) + +#define VAL_NULL() CU_ASSERT(spdk_json_write_null(w) == 0) +#define VAL_TRUE() CU_ASSERT(spdk_json_write_bool(w, true) == 0) +#define VAL_FALSE() CU_ASSERT(spdk_json_write_bool(w, false) == 0) + +#define VAL_INT32(i) CU_ASSERT(spdk_json_write_int32(w, i) == 0); +#define VAL_UINT32(u) CU_ASSERT(spdk_json_write_uint32(w, u) == 0); + +#define VAL_INT64(i) CU_ASSERT(spdk_json_write_int64(w, i) == 0); +#define VAL_UINT64(u) CU_ASSERT(spdk_json_write_uint64(w, u) == 0); + +#define VAL_ARRAY_BEGIN() CU_ASSERT(spdk_json_write_array_begin(w) == 0) +#define VAL_ARRAY_END() CU_ASSERT(spdk_json_write_array_end(w) == 0) + +#define VAL_OBJECT_BEGIN() CU_ASSERT(spdk_json_write_object_begin(w) == 0) +#define VAL_OBJECT_END() CU_ASSERT(spdk_json_write_object_end(w) == 0) + +#define VAL(v) CU_ASSERT(spdk_json_write_val(w, v) == 0) + +static void +test_write_literal(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_NULL(); + END("null"); + + BEGIN(); + VAL_TRUE(); + END("true"); + + BEGIN(); + VAL_FALSE(); + END("false"); +} + +static void +test_write_string_simple(void) +{ + struct spdk_json_write_ctx *w; + + STR_PASS("hello world", "hello world"); + STR_PASS(" ", " "); + STR_PASS("~", "~"); +} + +static void +test_write_string_escapes(void) +{ + struct spdk_json_write_ctx *w; + + /* Two-character escapes */ + STR_PASS("\b", "\\b"); + STR_PASS("\f", "\\f"); + STR_PASS("\n", "\\n"); + STR_PASS("\r", "\\r"); + STR_PASS("\t", "\\t"); + STR_PASS("\"", "\\\""); + STR_PASS("\\", "\\\\"); + + /* JSON defines an escape for forward slash, but it is optional */ + STR_PASS("/", "/"); + + STR_PASS("hello\nworld", "hello\\nworld"); + + STR_PASS("\x00", "\\u0000"); + STR_PASS("\x01", "\\u0001"); + STR_PASS("\x02", "\\u0002"); + + STR_PASS("\xC3\xB6", "\\u00F6"); + STR_PASS("\xE2\x88\x9A", "\\u221A"); + STR_PASS("\xEA\xAA\xAA", "\\uAAAA"); + + /* Surrogate pairs */ + STR_PASS("\xF0\x9D\x84\x9E", "\\uD834\\uDD1E"); + STR_PASS("\xF0\xA0\x9C\x8E", "\\uD841\\uDF0E"); + + /* Examples from RFC 3629 */ + STR_PASS("\x41\xE2\x89\xA2\xCE\x91\x2E", "A\\u2262\\u0391."); + STR_PASS("\xED\x95\x9C\xEA\xB5\xAD\xEC\x96\xB4", "\\uD55C\\uAD6D\\uC5B4"); + STR_PASS("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E", "\\u65E5\\u672C\\u8A9E"); + STR_PASS("\xEF\xBB\xBF\xF0\xA3\x8E\xB4", "\\uFEFF\\uD84C\\uDFB4"); + + /* UTF-8 edge cases */ + STR_PASS("\x7F", "\\u007F"); + STR_FAIL("\x80"); + STR_FAIL("\xC1"); + STR_FAIL("\xC2"); + STR_PASS("\xC2\x80", "\\u0080"); + STR_PASS("\xC2\xBF", "\\u00BF"); + STR_PASS("\xDF\x80", "\\u07C0"); + STR_PASS("\xDF\xBF", "\\u07FF"); + STR_FAIL("\xDF"); + STR_FAIL("\xE0\x80"); + STR_FAIL("\xE0\x1F"); + STR_FAIL("\xE0\x1F\x80"); + STR_FAIL("\xE0"); + STR_FAIL("\xE0\xA0"); + STR_PASS("\xE0\xA0\x80", "\\u0800"); + STR_PASS("\xE0\xA0\xBF", "\\u083F"); + STR_FAIL("\xE0\xA0\xC0"); + STR_PASS("\xE0\xBF\x80", "\\u0FC0"); + STR_PASS("\xE0\xBF\xBF", "\\u0FFF"); + STR_FAIL("\xE0\xC0\x80"); + STR_FAIL("\xE1"); + STR_FAIL("\xE1\x80"); + STR_FAIL("\xE1\x7F\x80"); + STR_FAIL("\xE1\x80\x7F"); + STR_PASS("\xE1\x80\x80", "\\u1000"); + STR_PASS("\xE1\x80\xBF", "\\u103F"); + STR_PASS("\xE1\xBF\x80", "\\u1FC0"); + STR_PASS("\xE1\xBF\xBF", "\\u1FFF"); + STR_FAIL("\xE1\xC0\x80"); + STR_FAIL("\xE1\x80\xC0"); + STR_PASS("\xEF\x80\x80", "\\uF000"); + STR_PASS("\xEF\xBF\xBF", "\\uFFFF"); + STR_FAIL("\xF0"); + STR_FAIL("\xF0\x90"); + STR_FAIL("\xF0\x90\x80"); + STR_FAIL("\xF0\x80\x80\x80"); + STR_FAIL("\xF0\x8F\x80\x80"); + STR_PASS("\xF0\x90\x80\x80", "\\uD800\\uDC00"); + STR_PASS("\xF0\x90\x80\xBF", "\\uD800\\uDC3F"); + STR_PASS("\xF0\x90\xBF\x80", "\\uD803\\uDFC0"); + STR_PASS("\xF0\xBF\x80\x80", "\\uD8BC\\uDC00"); + STR_FAIL("\xF0\xC0\x80\x80"); + STR_FAIL("\xF1"); + STR_FAIL("\xF1\x80"); + STR_FAIL("\xF1\x80\x80"); + STR_FAIL("\xF1\x80\x80\x7F"); + STR_PASS("\xF1\x80\x80\x80", "\\uD8C0\\uDC00"); + STR_PASS("\xF1\x80\x80\xBF", "\\uD8C0\\uDC3F"); + STR_PASS("\xF1\x80\xBF\x80", "\\uD8C3\\uDFC0"); + STR_PASS("\xF1\xBF\x80\x80", "\\uD9BC\\uDC00"); + STR_PASS("\xF3\x80\x80\x80", "\\uDAC0\\uDC00"); + STR_FAIL("\xF3\xC0\x80\x80"); + STR_FAIL("\xF3\x80\xC0\x80"); + STR_FAIL("\xF3\x80\x80\xC0"); + STR_FAIL("\xF4"); + STR_FAIL("\xF4\x80"); + STR_FAIL("\xF4\x80\x80"); + STR_PASS("\xF4\x80\x80\x80", "\\uDBC0\\uDC00"); + STR_PASS("\xF4\x8F\x80\x80", "\\uDBFC\\uDC00"); + STR_PASS("\xF4\x8F\xBF\xBF", "\\uDBFF\\uDFFF"); + STR_FAIL("\xF4\x90\x80\x80"); + STR_FAIL("\xF5"); + STR_FAIL("\xF5\x80"); + STR_FAIL("\xF5\x80\x80"); + STR_FAIL("\xF5\x80\x80\x80"); + STR_FAIL("\xF5\x80\x80\x80\x80"); + + /* Overlong encodings */ + STR_FAIL("\xC0\x80"); + + /* Surrogate pairs */ + STR_FAIL("\xED\xA0\x80"); /* U+D800 First high surrogate */ + STR_FAIL("\xED\xAF\xBF"); /* U+DBFF Last high surrogate */ + STR_FAIL("\xED\xB0\x80"); /* U+DC00 First low surrogate */ + STR_FAIL("\xED\xBF\xBF"); /* U+DFFF Last low surrogate */ + STR_FAIL("\xED\xA1\x8C\xED\xBE\xB4"); /* U+233B4 (invalid surrogate pair encoding) */ +} + +static void +test_write_string_utf16le(void) +{ + struct spdk_json_write_ctx *w; + + /* All characters in BMP */ + STR_UTF16LE_PASS(((uint8_t[]) { + 'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0, 0x15, 0xFE, 0, 0 + }), "Hello\\uFE15"); + + /* Surrogate pair */ + STR_UTF16LE_PASS(((uint8_t[]) { + 'H', 0, 'i', 0, 0x34, 0xD8, 0x1E, 0xDD, '!', 0, 0, 0 + }), "Hi\\uD834\\uDD1E!"); + + /* Valid high surrogate, but no low surrogate */ + STR_UTF16LE_FAIL(((uint8_t[]) { + 0x00, 0xD8, 0, 0 /* U+D800 */ + })); + + /* Invalid leading low surrogate */ + STR_UTF16LE_FAIL(((uint8_t[]) { + 0x00, 0xDC, 0x00, 0xDC, 0, 0 /* U+DC00 U+DC00 */ + })); + + /* Valid high surrogate followed by another high surrogate (invalid) */ + STR_UTF16LE_FAIL(((uint8_t[]) { + 0x00, 0xD8, 0x00, 0xD8, 0, 0 /* U+D800 U+D800 */ + })); +} + +static void +test_write_number_int32(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_INT32(0); + END("0"); + + BEGIN(); + VAL_INT32(1); + END("1"); + + BEGIN(); + VAL_INT32(123); + END("123"); + + BEGIN(); + VAL_INT32(-123); + END("-123"); + + BEGIN(); + VAL_INT32(2147483647); + END("2147483647"); + + BEGIN(); + VAL_INT32(-2147483648); + END("-2147483648"); +} + +static void +test_write_number_uint32(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_UINT32(0); + END("0"); + + BEGIN(); + VAL_UINT32(1); + END("1"); + + BEGIN(); + VAL_UINT32(123); + END("123"); + + BEGIN(); + VAL_UINT32(2147483647); + END("2147483647"); + + BEGIN(); + VAL_UINT32(4294967295); + END("4294967295"); +} + +static void +test_write_number_int64(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_INT64(0); + END("0"); + + BEGIN(); + VAL_INT64(1); + END("1"); + + BEGIN(); + VAL_INT64(123); + END("123"); + + BEGIN(); + VAL_INT64(-123); + END("-123"); + + BEGIN(); + VAL_INT64(INT64_MAX); + END("9223372036854775807"); + + BEGIN(); + VAL_INT64(INT64_MIN); + END("-9223372036854775808"); +} + +static void +test_write_number_uint64(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_UINT64(0); + END("0"); + + BEGIN(); + VAL_UINT64(1); + END("1"); + + BEGIN(); + VAL_UINT64(123); + END("123"); + + BEGIN(); + VAL_UINT64(INT64_MAX); + END("9223372036854775807"); + + BEGIN(); + VAL_UINT64(UINT64_MAX); + END("18446744073709551615"); +} + +static void +test_write_array(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_END(); + END("[]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_ARRAY_END(); + END("[0]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_INT32(1); + VAL_ARRAY_END(); + END("[0,1]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_INT32(1); + VAL_INT32(2); + VAL_ARRAY_END(); + END("[0,1,2]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_STRING("a"); + VAL_ARRAY_END(); + END("[\"a\"]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_STRING("a"); + VAL_STRING("b"); + VAL_ARRAY_END(); + END("[\"a\",\"b\"]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_STRING("a"); + VAL_STRING("b"); + VAL_STRING("c"); + VAL_ARRAY_END(); + END("[\"a\",\"b\",\"c\"]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_TRUE(); + VAL_ARRAY_END(); + END("[true]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_TRUE(); + VAL_FALSE(); + VAL_ARRAY_END(); + END("[true,false]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_TRUE(); + VAL_FALSE(); + VAL_TRUE(); + VAL_ARRAY_END(); + END("[true,false,true]"); +} + +static void +test_write_object(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_OBJECT_END(); + END("{}"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_INT32(0); + VAL_OBJECT_END(); + END("{\"a\":0}"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_INT32(0); + VAL_NAME("b"); + VAL_INT32(1); + VAL_OBJECT_END(); + END("{\"a\":0,\"b\":1}"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_INT32(0); + VAL_NAME("b"); + VAL_INT32(1); + VAL_NAME("c"); + VAL_INT32(2); + VAL_OBJECT_END(); + END("{\"a\":0,\"b\":1,\"c\":2}"); +} + +static void +test_write_nesting(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_END(); + VAL_ARRAY_END(); + END("[[]]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_END(); + VAL_ARRAY_END(); + VAL_ARRAY_END(); + END("[[[]]]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_END(); + VAL_ARRAY_END(); + END("[0,[]]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_END(); + VAL_INT32(0); + VAL_ARRAY_END(); + END("[[],0]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_ARRAY_BEGIN(); + VAL_INT32(1); + VAL_ARRAY_END(); + VAL_INT32(2); + VAL_ARRAY_END(); + END("[0,[1],2]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_INT32(1); + VAL_ARRAY_BEGIN(); + VAL_INT32(2); + VAL_INT32(3); + VAL_ARRAY_END(); + VAL_INT32(4); + VAL_INT32(5); + VAL_ARRAY_END(); + END("[0,1,[2,3],4,5]"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_OBJECT_BEGIN(); + VAL_OBJECT_END(); + VAL_OBJECT_END(); + END("{\"a\":{}}"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_OBJECT_BEGIN(); + VAL_NAME("b"); + VAL_INT32(0); + VAL_OBJECT_END(); + VAL_OBJECT_END(); + END("{\"a\":{\"b\":0}}"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + END("{\"a\":[0]}"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_INT32(0); + VAL_OBJECT_END(); + VAL_ARRAY_END(); + END("[{\"a\":0}]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_OBJECT_BEGIN(); + VAL_NAME("b"); + VAL_ARRAY_BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("c"); + VAL_INT32(1); + VAL_OBJECT_END(); + VAL_INT32(2); + VAL_ARRAY_END(); + VAL_NAME("d"); + VAL_INT32(3); + VAL_OBJECT_END(); + VAL_NAME("e"); + VAL_INT32(4); + VAL_OBJECT_END(); + VAL_INT32(5); + VAL_ARRAY_END(); + END("[{\"a\":{\"b\":[{\"c\":1},2],\"d\":3},\"e\":4},5]"); + + /* Examples from RFC 7159 */ + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("Image"); + VAL_OBJECT_BEGIN(); + VAL_NAME("Width"); + VAL_INT32(800); + VAL_NAME("Height"); + VAL_INT32(600); + VAL_NAME("Title"); + VAL_STRING("View from 15th Floor"); + VAL_NAME("Thumbnail"); + VAL_OBJECT_BEGIN(); + VAL_NAME("Url"); + VAL_STRING("http://www.example.com/image/481989943"); + VAL_NAME("Height"); + VAL_INT32(125); + VAL_NAME("Width"); + VAL_INT32(100); + VAL_OBJECT_END(); + VAL_NAME("Animated"); + VAL_FALSE(); + VAL_NAME("IDs"); + VAL_ARRAY_BEGIN(); + VAL_INT32(116); + VAL_INT32(943); + VAL_INT32(234); + VAL_INT32(38793); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + VAL_OBJECT_END(); + END( + "{\"Image\":" + "{" + "\"Width\":800," + "\"Height\":600," + "\"Title\":\"View from 15th Floor\"," + "\"Thumbnail\":{" + "\"Url\":\"http://www.example.com/image/481989943\"," + "\"Height\":125," + "\"Width\":100" + "}," + "\"Animated\":false," + "\"IDs\":[116,943,234,38793]" + "}" + "}"); +} + +/* Round-trip parse and write test */ +static void +test_write_val(void) +{ + struct spdk_json_write_ctx *w; + struct spdk_json_val values[100]; + char src[] = "{\"a\":[1,2,3],\"b\":{\"c\":\"d\"},\"e\":true,\"f\":false,\"g\":null}"; + + CU_ASSERT(spdk_json_parse(src, strlen(src), values, SPDK_COUNTOF(values), NULL, + SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE) == 19); + + BEGIN(); + VAL(values); + END("{\"a\":[1,2,3],\"b\":{\"c\":\"d\"},\"e\":true,\"f\":false,\"g\":null}"); +} + +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("json", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "write_literal", test_write_literal) == NULL || + CU_add_test(suite, "write_string_simple", test_write_string_simple) == NULL || + CU_add_test(suite, "write_string_escapes", test_write_string_escapes) == NULL || + CU_add_test(suite, "write_string_utf16le", test_write_string_utf16le) == NULL || + CU_add_test(suite, "write_number_int32", test_write_number_int32) == NULL || + CU_add_test(suite, "write_number_uint32", test_write_number_uint32) == NULL || + CU_add_test(suite, "write_number_int64", test_write_number_int64) == NULL || + CU_add_test(suite, "write_number_uint64", test_write_number_uint64) == NULL || + CU_add_test(suite, "write_array", test_write_array) == NULL || + CU_add_test(suite, "write_object", test_write_object) == NULL || + CU_add_test(suite, "write_nesting", test_write_nesting) == NULL || + CU_add_test(suite, "write_val", test_write_val) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/json_mock.c b/src/spdk/test/unit/lib/json_mock.c new file mode 100644 index 00000000..b9cee171 --- /dev/null +++ b/src/spdk/test/unit/lib/json_mock.c @@ -0,0 +1,81 @@ +/*- + * 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/json.h" +#include "spdk_internal/mock.h" + +DEFINE_STUB(spdk_json_write_begin, struct spdk_json_write_ctx *, (spdk_json_write_cb write_cb, + void *cb_ctx, uint32_t flags), NULL); + +DEFINE_STUB(spdk_json_write_end, int, (struct spdk_json_write_ctx *w), 0); +DEFINE_STUB(spdk_json_write_null, int, (struct spdk_json_write_ctx *w), 0); +DEFINE_STUB(spdk_json_write_bool, int, (struct spdk_json_write_ctx *w, bool val), 0); +DEFINE_STUB(spdk_json_write_int32, int, (struct spdk_json_write_ctx *w, int32_t val), 0); +DEFINE_STUB(spdk_json_write_uint32, int, (struct spdk_json_write_ctx *w, uint32_t val), 0); +DEFINE_STUB(spdk_json_write_int64, int, (struct spdk_json_write_ctx *w, int64_t val), 0); +DEFINE_STUB(spdk_json_write_uint64, int, (struct spdk_json_write_ctx *w, uint64_t val), 0); +DEFINE_STUB(spdk_json_write_string, int, (struct spdk_json_write_ctx *w, const char *val), 0); +DEFINE_STUB(spdk_json_write_string_raw, int, (struct spdk_json_write_ctx *w, const char *val, + size_t len), 0); + +DEFINE_STUB(spdk_json_write_array_begin, int, (struct spdk_json_write_ctx *w), 0); +DEFINE_STUB(spdk_json_write_array_end, int, (struct spdk_json_write_ctx *w), 0); +DEFINE_STUB(spdk_json_write_object_begin, int, (struct spdk_json_write_ctx *w), 0); +DEFINE_STUB(spdk_json_write_object_end, int, (struct spdk_json_write_ctx *w), 0); +DEFINE_STUB(spdk_json_write_name, int, (struct spdk_json_write_ctx *w, const char *name), 0); +DEFINE_STUB(spdk_json_write_name_raw, int, (struct spdk_json_write_ctx *w, const char *name, + size_t len), 0); + +/* Utility functions */ +DEFINE_STUB(spdk_json_write_named_null, int, (struct spdk_json_write_ctx *w, const char *name), 0); +DEFINE_STUB(spdk_json_write_named_bool, int, (struct spdk_json_write_ctx *w, const char *name, + bool val), 0); +DEFINE_STUB(spdk_json_write_named_int32, int, (struct spdk_json_write_ctx *w, const char *name, + int32_t val), 0); +DEFINE_STUB(spdk_json_write_named_uint32, int, (struct spdk_json_write_ctx *w, const char *name, + uint32_t val), 0); +DEFINE_STUB(spdk_json_write_named_uint64, int, (struct spdk_json_write_ctx *w, const char *name, + uint64_t val), 0); +DEFINE_STUB(spdk_json_write_named_int64, int, (struct spdk_json_write_ctx *w, const char *name, + int64_t val), 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_json_write_named_string_fmt, int, (struct spdk_json_write_ctx *w, const char *name, + const char *fmt, ...), 0); +DEFINE_STUB(spdk_json_write_named_string_fmt_v, int, (struct spdk_json_write_ctx *w, + const char *name, const char *fmt, va_list args), 0); + +DEFINE_STUB(spdk_json_write_named_array_begin, int, (struct spdk_json_write_ctx *w, + const char *name), 0); +DEFINE_STUB(spdk_json_write_named_object_begin, int, (struct spdk_json_write_ctx *w, + const char *name), 0); diff --git a/src/spdk/test/unit/lib/jsonrpc/Makefile b/src/spdk/test/unit/lib/jsonrpc/Makefile new file mode 100644 index 00000000..0fc0a2e9 --- /dev/null +++ b/src/spdk/test/unit/lib/jsonrpc/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = jsonrpc_server.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/.gitignore b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/.gitignore new file mode 100644 index 00000000..8852a96d --- /dev/null +++ b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/.gitignore @@ -0,0 +1 @@ +jsonrpc_server_ut diff --git a/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/Makefile b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/Makefile new file mode 100644 index 00000000..6c02115f --- /dev/null +++ b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/Makefile @@ -0,0 +1,39 @@ +# +# 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 = jsonrpc_server_ut.c +SPDK_LIB_LIST = json + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/jsonrpc_server_ut.c b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/jsonrpc_server_ut.c new file mode 100644 index 00000000..3c62e41f --- /dev/null +++ b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/jsonrpc_server_ut.c @@ -0,0 +1,423 @@ +/*- + * 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 "jsonrpc/jsonrpc_server.c" + +#define MAX_PARAMS 100 +#define MAX_REQS 100 + +struct req { + int error; + bool got_method; + bool got_id; + bool got_params; + struct spdk_jsonrpc_request *request; + struct spdk_json_val method; + struct spdk_json_val id; + struct spdk_json_val params[MAX_PARAMS]; +}; + +static uint8_t g_buf[1000]; +static struct req g_reqs[MAX_REQS]; +static struct req *g_cur_req; +static struct spdk_json_val *g_params; +static size_t g_num_reqs; + +#define PARSE_PASS(in, trailing) \ + memcpy(g_buf, in, sizeof(in) - 1); \ + g_num_reqs = 0; \ + g_cur_req = NULL; \ + CU_ASSERT(spdk_jsonrpc_parse_request(conn, g_buf, sizeof(in) - 1) == sizeof(in) - sizeof(trailing)); \ + if (g_cur_req && g_cur_req->request) { \ + free(g_cur_req->request->send_buf); \ + g_cur_req->request->send_buf = NULL; \ + } + +#define PARSE_FAIL(in) \ + memcpy(g_buf, in, sizeof(in) - 1); \ + g_num_reqs = 0; \ + g_cur_req = 0; \ + CU_ASSERT(spdk_jsonrpc_parse_request(conn, g_buf, sizeof(in) - 1) < 0); \ + if (g_cur_req && g_cur_req->request) { \ + free(g_cur_req->request->send_buf); \ + g_cur_req->request->send_buf = NULL; \ + } + +#define REQ_BEGIN(expected_error) \ + if (g_cur_req == NULL) { \ + g_cur_req = g_reqs; \ + } else { \ + g_cur_req++; \ + } \ + CU_ASSERT(g_cur_req - g_reqs <= (ptrdiff_t)g_num_reqs); \ + CU_ASSERT(g_cur_req->error == expected_error) + +#define REQ_BEGIN_VALID() REQ_BEGIN(0) +#define REQ_BEGIN_INVALID(expected_error) REQ_BEGIN(expected_error) + +#define REQ_METHOD(name) \ + CU_ASSERT(g_cur_req->got_method); \ + CU_ASSERT(spdk_json_strequal(&g_cur_req->method, name) == true) + +#define REQ_METHOD_MISSING() \ + CU_ASSERT(g_cur_req->got_method == false) + +#define REQ_ID_NUM(num) \ + CU_ASSERT(g_cur_req->got_id); \ + CU_ASSERT(g_cur_req->id.type == SPDK_JSON_VAL_NUMBER); \ + CU_ASSERT(memcmp(g_cur_req->id.start, num, sizeof(num) - 1) == 0) + +#define REQ_ID_STRING(str) \ + CU_ASSERT(g_cur_req->got_id); \ + CU_ASSERT(g_cur_req->id.type == SPDK_JSON_VAL_STRING); \ + CU_ASSERT(memcmp(g_cur_req->id.start, str, sizeof(str) - 1) == 0) + +#define REQ_ID_NULL() \ + CU_ASSERT(g_cur_req->got_id); \ + CU_ASSERT(g_cur_req->id.type == SPDK_JSON_VAL_NULL) + +#define REQ_ID_MISSING() \ + CU_ASSERT(g_cur_req->got_id == false) + +#define REQ_PARAMS_MISSING() \ + CU_ASSERT(g_cur_req->got_params == false) + +#define REQ_PARAMS_BEGIN() \ + CU_ASSERT(g_cur_req->got_params); \ + g_params = g_cur_req->params + +#define PARAM_ARRAY_BEGIN() \ + CU_ASSERT(g_params->type == SPDK_JSON_VAL_ARRAY_BEGIN); \ + g_params++ + +#define PARAM_ARRAY_END() \ + CU_ASSERT(g_params->type == SPDK_JSON_VAL_ARRAY_END); \ + g_params++ + +#define PARAM_OBJECT_BEGIN() \ + CU_ASSERT(g_params->type == SPDK_JSON_VAL_OBJECT_BEGIN); \ + g_params++ + +#define PARAM_OBJECT_END() \ + CU_ASSERT(g_params->type == SPDK_JSON_VAL_OBJECT_END); \ + g_params++ + +#define PARAM_NUM(num) \ + CU_ASSERT(g_params->type == SPDK_JSON_VAL_NUMBER); \ + CU_ASSERT(g_params->len == sizeof(num) - 1); \ + CU_ASSERT(memcmp(g_params->start, num, g_params->len) == 0); \ + g_params++ + +#define PARAM_NAME(str) \ + CU_ASSERT(g_params->type == SPDK_JSON_VAL_NAME); \ + CU_ASSERT(g_params->len == sizeof(str) - 1); \ + CU_ASSERT(memcmp(g_params->start, str, g_params->len) == 0); \ + g_params++ + +#define PARAM_STRING(str) \ + CU_ASSERT(g_params->type == SPDK_JSON_VAL_STRING); \ + CU_ASSERT(g_params->len == sizeof(str) - 1); \ + CU_ASSERT(memcmp(g_params->start, str, g_params->len) == 0); \ + g_params++ + +#define FREE_REQUEST() \ + if (g_reqs->request) { \ + free(g_reqs->request->send_buf); \ + } \ + free(g_reqs->request); \ + g_reqs->request = NULL + +static void +ut_handle(struct spdk_jsonrpc_request *request, int error, const struct spdk_json_val *method, + const struct spdk_json_val *params) +{ + const struct spdk_json_val *id = &request->id; + struct req *r; + + SPDK_CU_ASSERT_FATAL(g_num_reqs != MAX_REQS); + r = &g_reqs[g_num_reqs++]; + + r->request = request; + r->error = error; + + if (method) { + r->got_method = true; + r->method = *method; + } else { + r->got_method = false; + } + + if (params) { + r->got_params = true; + SPDK_CU_ASSERT_FATAL(spdk_json_val_len(params) < MAX_PARAMS); + memcpy(r->params, params, spdk_json_val_len(params) * sizeof(struct spdk_json_val)); + } else { + r->got_params = false; + } + + if (id && id->type != SPDK_JSON_VAL_INVALID) { + r->got_id = true; + r->id = *id; + } else { + r->got_id = false; + } +} + +void +spdk_jsonrpc_server_handle_error(struct spdk_jsonrpc_request *request, int error) +{ + /* + * Map missing id to Null - this mirrors the behavior in the real + * spdk_jsonrpc_server_handle_error() function. + */ + if (request->id.type == SPDK_JSON_VAL_INVALID) { + request->id.type = SPDK_JSON_VAL_NULL; + } + + ut_handle(request, error, NULL, NULL); +} + +void +spdk_jsonrpc_server_handle_request(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *method, const struct spdk_json_val *params) +{ + ut_handle(request, 0, method, params); +} + +void +spdk_jsonrpc_server_send_response(struct spdk_jsonrpc_request *request) +{ + /* TODO */ +} + +static void +test_parse_request(void) +{ + struct spdk_jsonrpc_server *server; + struct spdk_jsonrpc_server_conn *conn; + + server = calloc(1, sizeof(*server)); + SPDK_CU_ASSERT_FATAL(server != NULL); + + conn = calloc(1, sizeof(*conn)); + SPDK_CU_ASSERT_FATAL(conn != NULL); + + conn->server = server; + + /* rpc call with positional parameters */ + PARSE_PASS("{\"jsonrpc\":\"2.0\",\"method\":\"subtract\",\"params\":[42,23],\"id\":1}", ""); + REQ_BEGIN_VALID(); + REQ_METHOD("subtract"); + REQ_ID_NUM("1"); + REQ_PARAMS_BEGIN(); + PARAM_ARRAY_BEGIN(); + PARAM_NUM("42"); + PARAM_NUM("23"); + PARAM_ARRAY_END(); + FREE_REQUEST(); + + /* rpc call with named parameters */ + PARSE_PASS("{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": {\"subtrahend\": 23, \"minuend\": 42}, \"id\": 3}", + ""); + REQ_BEGIN_VALID(); + REQ_METHOD("subtract"); + REQ_ID_NUM("3"); + REQ_PARAMS_BEGIN(); + PARAM_OBJECT_BEGIN(); + PARAM_NAME("subtrahend"); + PARAM_NUM("23"); + PARAM_NAME("minuend"); + PARAM_NUM("42"); + PARAM_OBJECT_END(); + FREE_REQUEST(); + + /* notification */ + PARSE_PASS("{\"jsonrpc\": \"2.0\", \"method\": \"update\", \"params\": [1,2,3,4,5]}", ""); + REQ_BEGIN_VALID(); + REQ_METHOD("update"); + REQ_ID_MISSING(); + REQ_PARAMS_BEGIN(); + PARAM_ARRAY_BEGIN(); + PARAM_NUM("1"); + PARAM_NUM("2"); + PARAM_NUM("3"); + PARAM_NUM("4"); + PARAM_NUM("5"); + PARAM_ARRAY_END(); + FREE_REQUEST(); + + /* invalid JSON */ + PARSE_FAIL("{\"jsonrpc\": \"2.0\", \"method\": \"foobar, \"params\": \"bar\", \"baz]"); + REQ_BEGIN_INVALID(SPDK_JSONRPC_ERROR_PARSE_ERROR); + REQ_METHOD_MISSING(); + REQ_ID_NULL(); + REQ_PARAMS_MISSING(); + FREE_REQUEST(); + + /* invalid request (method must be a string; params must be array or object) */ + PARSE_PASS("{\"jsonrpc\": \"2.0\", \"method\": 1, \"params\": \"bar\"}", ""); + REQ_BEGIN_INVALID(SPDK_JSONRPC_ERROR_INVALID_REQUEST); + REQ_METHOD_MISSING(); + REQ_ID_NULL(); + REQ_PARAMS_MISSING(); + FREE_REQUEST(); + + /* batch, invalid JSON */ + PARSE_FAIL( + "[" + "{\"jsonrpc\": \"2.0\", \"method\": \"sum\", \"params\": [1,2,4], \"id\": \"1\"}," + "{\"jsonrpc\": \"2.0\", \"method\"" + "]"); + REQ_BEGIN_INVALID(SPDK_JSONRPC_ERROR_PARSE_ERROR); + REQ_METHOD_MISSING(); + REQ_ID_NULL(); + REQ_PARAMS_MISSING(); + FREE_REQUEST(); + + /* empty array */ + PARSE_PASS("[]", ""); + REQ_BEGIN_INVALID(SPDK_JSONRPC_ERROR_INVALID_REQUEST); + REQ_METHOD_MISSING(); + REQ_ID_NULL(); + REQ_PARAMS_MISSING(); + FREE_REQUEST(); + + /* batch - not supported */ + PARSE_PASS( + "[" + "{\"jsonrpc\": \"2.0\", \"method\": \"sum\", \"params\": [1,2,4], \"id\": \"1\"}," + "{\"jsonrpc\": \"2.0\", \"method\": \"notify_hello\", \"params\": [7]}," + "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42,23], \"id\": \"2\"}," + "{\"foo\": \"boo\"}," + "{\"jsonrpc\": \"2.0\", \"method\": \"foo.get\", \"params\": {\"name\": \"myself\"}, \"id\": \"5\"}," + "{\"jsonrpc\": \"2.0\", \"method\": \"get_data\", \"id\": \"9\"}" + "]", ""); + + REQ_BEGIN_INVALID(SPDK_JSONRPC_ERROR_INVALID_REQUEST); + REQ_METHOD_MISSING(); + REQ_ID_NULL(); + REQ_PARAMS_MISSING(); + FREE_REQUEST(); + + free(conn); + free(server); +} + +static void +test_parse_request_streaming(void) +{ + struct spdk_jsonrpc_server *server; + struct spdk_jsonrpc_server_conn *conn; + size_t len, i; + + server = calloc(1, sizeof(*server)); + SPDK_CU_ASSERT_FATAL(server != NULL); + + conn = calloc(1, sizeof(*conn)); + SPDK_CU_ASSERT_FATAL(conn != NULL); + + conn->server = server; + + /* + * Two valid requests end to end in the same buffer. + * Parse should return the first one and point to the beginning of the second one. + */ + PARSE_PASS( + "{\"jsonrpc\":\"2.0\",\"method\":\"a\",\"params\":[1],\"id\":1}" + "{\"jsonrpc\":\"2.0\",\"method\":\"b\",\"params\":[2],\"id\":2}", + "{\"jsonrpc\":\"2.0\",\"method\":\"b\",\"params\":[2],\"id\":2}"); + REQ_BEGIN_VALID(); + REQ_METHOD("a"); + REQ_ID_NUM("1"); + REQ_PARAMS_BEGIN(); + PARAM_ARRAY_BEGIN(); + PARAM_NUM("1"); + PARAM_ARRAY_END(); + FREE_REQUEST(); + + /* Partial (but not invalid) requests - parse should not consume anything. */ + snprintf(g_buf, sizeof(g_buf), "%s", + "{\"jsonrpc\":\"2.0\",\"method\":\"b\",\"params\":[2],\"id\":2}"); + len = strlen(g_buf); + + /* Try every partial length up to the full request length */ + for (i = 0; i < len; i++) { + int rc = spdk_jsonrpc_parse_request(conn, g_buf, i); + /* Partial request - no data consumed */ + CU_ASSERT(rc == 0); + FREE_REQUEST(); + } + + /* Verify that full request can be parsed successfully */ + CU_ASSERT(spdk_jsonrpc_parse_request(conn, g_buf, len) == (ssize_t)len); + FREE_REQUEST(); + + free(conn); + free(server); +} + +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("jsonrpc", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "parse_request", test_parse_request) == NULL || + CU_add_test(suite, "parse_request_streaming", test_parse_request_streaming) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/log/Makefile b/src/spdk/test/unit/lib/log/Makefile new file mode 100644 index 00000000..79411a45 --- /dev/null +++ b/src/spdk/test/unit/lib/log/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = log.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/log/log.c/.gitignore b/src/spdk/test/unit/lib/log/log.c/.gitignore new file mode 100644 index 00000000..60261c07 --- /dev/null +++ b/src/spdk/test/unit/lib/log/log.c/.gitignore @@ -0,0 +1 @@ +log_ut diff --git a/src/spdk/test/unit/lib/log/log.c/Makefile b/src/spdk/test/unit/lib/log/log.c/Makefile new file mode 100644 index 00000000..deedd9fb --- /dev/null +++ b/src/spdk/test/unit/lib/log/log.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = log_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/log/log.c/log_ut.c b/src/spdk/test/unit/lib/log/log.c/log_ut.c new file mode 100644 index 00000000..17650f71 --- /dev/null +++ b/src/spdk/test/unit/lib/log/log.c/log_ut.c @@ -0,0 +1,113 @@ +/*- + * 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/log.h" + +#include "log/log.c" +#include "log/log_flags.c" + +static void +log_test(void) +{ + spdk_log_set_level(SPDK_LOG_ERROR); + CU_ASSERT_EQUAL(spdk_log_get_level(), SPDK_LOG_ERROR); + spdk_log_set_level(SPDK_LOG_WARN); + CU_ASSERT_EQUAL(spdk_log_get_level(), SPDK_LOG_WARN); + spdk_log_set_level(SPDK_LOG_NOTICE); + CU_ASSERT_EQUAL(spdk_log_get_level(), SPDK_LOG_NOTICE); + spdk_log_set_level(SPDK_LOG_INFO); + CU_ASSERT_EQUAL(spdk_log_get_level(), SPDK_LOG_INFO); + spdk_log_set_level(SPDK_LOG_DEBUG); + CU_ASSERT_EQUAL(spdk_log_get_level(), SPDK_LOG_DEBUG); + + spdk_log_set_print_level(SPDK_LOG_ERROR); + CU_ASSERT_EQUAL(spdk_log_get_print_level(), SPDK_LOG_ERROR); + spdk_log_set_print_level(SPDK_LOG_WARN); + CU_ASSERT_EQUAL(spdk_log_get_print_level(), SPDK_LOG_WARN); + spdk_log_set_print_level(SPDK_LOG_NOTICE); + CU_ASSERT_EQUAL(spdk_log_get_print_level(), SPDK_LOG_NOTICE); + spdk_log_set_print_level(SPDK_LOG_INFO); + CU_ASSERT_EQUAL(spdk_log_get_print_level(), SPDK_LOG_INFO); + spdk_log_set_print_level(SPDK_LOG_DEBUG); + CU_ASSERT_EQUAL(spdk_log_get_print_level(), SPDK_LOG_DEBUG); + +#ifdef DEBUG + CU_ASSERT(spdk_log_get_trace_flag("log") == false); + + spdk_log_set_trace_flag("log"); + CU_ASSERT(spdk_log_get_trace_flag("log") == true); + + spdk_log_clear_trace_flag("log"); + CU_ASSERT(spdk_log_get_trace_flag("log") == false); +#endif + + spdk_log_open(); + spdk_log_set_trace_flag("log"); + SPDK_WARNLOG("log warning unit test\n"); + SPDK_DEBUGLOG(SPDK_LOG_LOG, "log trace test\n"); + SPDK_TRACEDUMP(SPDK_LOG_LOG, "log trace dump test:", "trace dump", 10); + spdk_trace_dump(stderr, "spdk dump test:", "spdk dump", 9); + + spdk_log_close(); +} + +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("log", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "log_ut", log_test) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/lvol/Makefile b/src/spdk/test/unit/lib/lvol/Makefile new file mode 100644 index 00000000..c9276de4 --- /dev/null +++ b/src/spdk/test/unit/lib/lvol/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = lvol.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/lvol/lvol.c/.gitignore b/src/spdk/test/unit/lib/lvol/lvol.c/.gitignore new file mode 100644 index 00000000..57e92bfe --- /dev/null +++ b/src/spdk/test/unit/lib/lvol/lvol.c/.gitignore @@ -0,0 +1 @@ +lvol_ut diff --git a/src/spdk/test/unit/lib/lvol/lvol.c/Makefile b/src/spdk/test/unit/lib/lvol/lvol.c/Makefile new file mode 100644 index 00000000..917f4ef6 --- /dev/null +++ b/src/spdk/test/unit/lib/lvol/lvol.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = lvol_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/lvol/lvol.c/lvol_ut.c b/src/spdk/test/unit/lib/lvol/lvol.c/lvol_ut.c new file mode 100644 index 00000000..0aebbe1a --- /dev/null +++ b/src/spdk/test/unit/lib/lvol/lvol.c/lvol_ut.c @@ -0,0 +1,2127 @@ +/*- + * 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_cunit.h" +#include "spdk/blob.h" +#include "spdk/thread.h" +#include "spdk/util.h" + +#include "common/lib/test_env.c" + +#include "lvol/lvol.c" + +#define DEV_BUFFER_SIZE (64 * 1024 * 1024) +#define DEV_BUFFER_BLOCKLEN (4096) +#define DEV_BUFFER_BLOCKCNT (DEV_BUFFER_SIZE / DEV_BUFFER_BLOCKLEN) +#define BS_CLUSTER_SIZE (1024 * 1024) +#define BS_FREE_CLUSTERS (DEV_BUFFER_SIZE / BS_CLUSTER_SIZE) +#define BS_PAGE_SIZE (4096) + +#define SPDK_BLOB_OPTS_CLUSTER_SZ (1024 * 1024) +#define SPDK_BLOB_OPTS_NUM_MD_PAGES UINT32_MAX +#define SPDK_BLOB_OPTS_MAX_MD_OPS 32 +#define SPDK_BLOB_OPTS_MAX_CHANNEL_OPS 512 + +#define SPDK_BLOB_THIN_PROV (1ULL << 0) + +const char *uuid = "828d9766-ae50-11e7-bd8d-001e67edf350"; + +struct spdk_blob { + spdk_blob_id id; + uint32_t ref; + struct spdk_blob_store *bs; + int close_status; + int open_status; + int load_status; + TAILQ_ENTRY(spdk_blob) link; + char uuid[SPDK_UUID_STRING_LEN]; + char name[SPDK_LVS_NAME_MAX]; + bool thin_provisioned; +}; + +int g_lvolerrno; +int g_lvserrno; +int g_close_super_status; +int g_resize_rc; +int g_inflate_rc; +bool g_lvs_rename_blob_open_error = false; +struct spdk_lvol_store *g_lvol_store; +struct spdk_lvol *g_lvol; +spdk_blob_id g_blobid = 1; +struct spdk_io_channel *g_io_channel; + +struct spdk_blob_store { + struct spdk_bs_opts bs_opts; + spdk_blob_id super_blobid; + TAILQ_HEAD(, spdk_blob) blobs; + int get_super_status; +}; + +struct lvol_ut_bs_dev { + struct spdk_bs_dev bs_dev; + int init_status; + int load_status; + struct spdk_blob_store *bs; +}; + +void spdk_bs_inflate_blob(struct spdk_blob_store *bs, struct spdk_io_channel *channel, + spdk_blob_id blobid, spdk_blob_op_complete cb_fn, void *cb_arg) +{ + cb_fn(cb_arg, g_inflate_rc); +} + +void spdk_bs_blob_decouple_parent(struct spdk_blob_store *bs, struct spdk_io_channel *channel, + spdk_blob_id blobid, spdk_blob_op_complete cb_fn, void *cb_arg) +{ + cb_fn(cb_arg, g_inflate_rc); +} + +void +spdk_bs_iter_next(struct spdk_blob_store *bs, struct spdk_blob *b, + spdk_blob_op_with_handle_complete cb_fn, void *cb_arg) +{ + struct spdk_blob *next; + int _errno = 0; + + next = TAILQ_NEXT(b, link); + if (next == NULL) { + _errno = -ENOENT; + } else if (next->load_status != 0) { + _errno = next->load_status; + } + + cb_fn(cb_arg, next, _errno); +} + +void +spdk_bs_iter_first(struct spdk_blob_store *bs, + spdk_blob_op_with_handle_complete cb_fn, void *cb_arg) +{ + struct spdk_blob *first; + int _errno = 0; + + first = TAILQ_FIRST(&bs->blobs); + if (first == NULL) { + _errno = -ENOENT; + } else if (first->load_status != 0) { + _errno = first->load_status; + } + + cb_fn(cb_arg, first, _errno); +} + +uint64_t spdk_blob_get_num_clusters(struct spdk_blob *blob) +{ + return 0; +} + +void +spdk_bs_get_super(struct spdk_blob_store *bs, + spdk_blob_op_with_id_complete cb_fn, void *cb_arg) +{ + if (bs->get_super_status != 0) { + cb_fn(cb_arg, 0, bs->get_super_status); + } else { + cb_fn(cb_arg, bs->super_blobid, 0); + } +} + +void +spdk_bs_set_super(struct spdk_blob_store *bs, spdk_blob_id blobid, + spdk_bs_op_complete cb_fn, void *cb_arg) +{ + bs->super_blobid = blobid; + cb_fn(cb_arg, 0); +} + +void +spdk_bs_load(struct spdk_bs_dev *dev, struct spdk_bs_opts *opts, + spdk_bs_op_with_handle_complete cb_fn, void *cb_arg) +{ + struct lvol_ut_bs_dev *ut_dev = SPDK_CONTAINEROF(dev, struct lvol_ut_bs_dev, bs_dev); + struct spdk_blob_store *bs = NULL; + + if (ut_dev->load_status == 0) { + bs = ut_dev->bs; + } + + cb_fn(cb_arg, bs, ut_dev->load_status); +} + +struct spdk_io_channel *spdk_bs_alloc_io_channel(struct spdk_blob_store *bs) +{ + if (g_io_channel == NULL) { + g_io_channel = calloc(1, sizeof(struct spdk_io_channel)); + SPDK_CU_ASSERT_FATAL(g_io_channel != NULL); + } + g_io_channel->ref++; + return g_io_channel; +} + +void spdk_bs_free_io_channel(struct spdk_io_channel *channel) +{ + g_io_channel->ref--; + if (g_io_channel->ref == 0) { + free(g_io_channel); + g_io_channel = NULL; + } + return; +} + +int +spdk_blob_set_xattr(struct spdk_blob *blob, const char *name, const void *value, + uint16_t value_len) +{ + if (!strcmp(name, "uuid")) { + CU_ASSERT(value_len == SPDK_UUID_STRING_LEN); + memcpy(blob->uuid, value, SPDK_UUID_STRING_LEN); + } else if (!strcmp(name, "name")) { + CU_ASSERT(value_len <= SPDK_LVS_NAME_MAX); + memcpy(blob->name, value, value_len); + } + + return 0; +} + +int +spdk_blob_get_xattr_value(struct spdk_blob *blob, const char *name, + const void **value, size_t *value_len) +{ + if (!strcmp(name, "uuid") && strnlen(blob->uuid, SPDK_UUID_STRING_LEN) != 0) { + CU_ASSERT(strnlen(blob->uuid, SPDK_UUID_STRING_LEN) == (SPDK_UUID_STRING_LEN - 1)); + *value = blob->uuid; + *value_len = SPDK_UUID_STRING_LEN; + return 0; + } else if (!strcmp(name, "name") && strnlen(blob->name, SPDK_LVS_NAME_MAX) != 0) { + *value = blob->name; + *value_len = strnlen(blob->name, SPDK_LVS_NAME_MAX) + 1; + return 0; + } + + return -ENOENT; +} + +int +spdk_blob_get_clones(struct spdk_blob_store *bs, spdk_blob_id blobid, spdk_blob_id *ids, + size_t *count) +{ + return 0; +} + +uint64_t +spdk_bs_get_page_size(struct spdk_blob_store *bs) +{ + return BS_PAGE_SIZE; +} + +int +spdk_bdev_notify_blockcnt_change(struct spdk_bdev *bdev, uint64_t size) +{ + bdev->blockcnt = size; + return 0; +} + +static void +init_dev(struct lvol_ut_bs_dev *dev) +{ + memset(dev, 0, sizeof(*dev)); + dev->bs_dev.blockcnt = DEV_BUFFER_BLOCKCNT; + dev->bs_dev.blocklen = DEV_BUFFER_BLOCKLEN; +} + +static void +free_dev(struct lvol_ut_bs_dev *dev) +{ + struct spdk_blob_store *bs = dev->bs; + struct spdk_blob *blob, *tmp; + + if (bs == NULL) { + return; + } + + TAILQ_FOREACH_SAFE(blob, &bs->blobs, link, tmp) { + TAILQ_REMOVE(&bs->blobs, blob, link); + free(blob); + } + + free(bs); + dev->bs = NULL; +} + +void +spdk_bs_init(struct spdk_bs_dev *dev, struct spdk_bs_opts *o, + spdk_bs_op_with_handle_complete cb_fn, void *cb_arg) +{ + struct lvol_ut_bs_dev *ut_dev = SPDK_CONTAINEROF(dev, struct lvol_ut_bs_dev, bs_dev); + struct spdk_blob_store *bs; + + bs = calloc(1, sizeof(*bs)); + SPDK_CU_ASSERT_FATAL(bs != NULL); + + TAILQ_INIT(&bs->blobs); + + ut_dev->bs = bs; + + memcpy(&bs->bs_opts, o, sizeof(struct spdk_bs_opts)); + + cb_fn(cb_arg, bs, 0); +} + +void +spdk_bs_unload(struct spdk_blob_store *bs, spdk_bs_op_complete cb_fn, void *cb_arg) +{ + cb_fn(cb_arg, 0); +} + +void +spdk_bs_destroy(struct spdk_blob_store *bs, spdk_bs_op_complete cb_fn, + void *cb_arg) +{ + free(bs); + + cb_fn(cb_arg, 0); +} + +void +spdk_bs_delete_blob(struct spdk_blob_store *bs, spdk_blob_id blobid, + spdk_blob_op_complete cb_fn, void *cb_arg) +{ + struct spdk_blob *blob; + + TAILQ_FOREACH(blob, &bs->blobs, link) { + if (blob->id == blobid) { + TAILQ_REMOVE(&bs->blobs, blob, link); + free(blob); + break; + } + } + + cb_fn(cb_arg, 0); +} + +spdk_blob_id +spdk_blob_get_id(struct spdk_blob *blob) +{ + return blob->id; +} + +void +spdk_bs_opts_init(struct spdk_bs_opts *opts) +{ + opts->cluster_sz = SPDK_BLOB_OPTS_CLUSTER_SZ; + opts->num_md_pages = SPDK_BLOB_OPTS_NUM_MD_PAGES; + opts->max_md_ops = SPDK_BLOB_OPTS_MAX_MD_OPS; + opts->max_channel_ops = SPDK_BLOB_OPTS_MAX_CHANNEL_OPS; + memset(&opts->bstype, 0, sizeof(opts->bstype)); +} + +uint64_t +spdk_bs_get_cluster_size(struct spdk_blob_store *bs) +{ + return BS_CLUSTER_SIZE; +} + +void spdk_blob_close(struct spdk_blob *b, spdk_blob_op_complete cb_fn, void *cb_arg) +{ + b->ref--; + + cb_fn(cb_arg, b->close_status); +} + +void +spdk_blob_resize(struct spdk_blob *blob, uint64_t sz, spdk_blob_op_complete cb_fn, void *cb_arg) +{ + if (g_resize_rc != 0) { + return cb_fn(cb_arg, g_resize_rc); + } else if (sz > DEV_BUFFER_SIZE / BS_CLUSTER_SIZE) { + return cb_fn(cb_arg, -ENOMEM); + } + cb_fn(cb_arg, 0); +} + +void +spdk_blob_sync_md(struct spdk_blob *blob, spdk_blob_op_complete cb_fn, void *cb_arg) +{ + cb_fn(cb_arg, 0); +} + +void +spdk_bs_open_blob(struct spdk_blob_store *bs, spdk_blob_id blobid, + spdk_blob_op_with_handle_complete cb_fn, void *cb_arg) +{ + struct spdk_blob *blob; + + if (!g_lvs_rename_blob_open_error) { + TAILQ_FOREACH(blob, &bs->blobs, link) { + if (blob->id == blobid) { + blob->ref++; + cb_fn(cb_arg, blob, blob->open_status); + return; + } + } + } + + cb_fn(cb_arg, NULL, -ENOENT); +} + +uint64_t +spdk_bs_free_cluster_count(struct spdk_blob_store *bs) +{ + return BS_FREE_CLUSTERS; +} + +void +spdk_blob_opts_init(struct spdk_blob_opts *opts) +{ + opts->num_clusters = 0; + opts->thin_provision = false; + opts->xattrs.count = 0; + opts->xattrs.names = NULL; + opts->xattrs.ctx = NULL; + opts->xattrs.get_value = NULL; +} + +void +spdk_bs_create_blob(struct spdk_blob_store *bs, + spdk_blob_op_with_id_complete cb_fn, void *cb_arg) +{ + spdk_bs_create_blob_ext(bs, NULL, cb_fn, cb_arg); +} + +void +spdk_bs_create_blob_ext(struct spdk_blob_store *bs, const struct spdk_blob_opts *opts, + spdk_blob_op_with_id_complete cb_fn, void *cb_arg) +{ + struct spdk_blob *b; + + if (opts && opts->num_clusters > DEV_BUFFER_SIZE / BS_CLUSTER_SIZE) { + cb_fn(cb_arg, 0, -1); + return; + } + + b = calloc(1, sizeof(*b)); + SPDK_CU_ASSERT_FATAL(b != NULL); + + b->id = g_blobid++; + if (opts != NULL && opts->thin_provision) { + b->thin_provisioned = true; + } + b->bs = bs; + + TAILQ_INSERT_TAIL(&bs->blobs, b, link); + cb_fn(cb_arg, b->id, 0); +} + +void +spdk_bs_create_snapshot(struct spdk_blob_store *bs, spdk_blob_id blobid, + const struct spdk_blob_xattr_opts *snapshot_xattrs, + spdk_blob_op_with_id_complete cb_fn, void *cb_arg) +{ + spdk_bs_create_blob_ext(bs, NULL, cb_fn, cb_arg); +} + +void +spdk_bs_create_clone(struct spdk_blob_store *bs, spdk_blob_id blobid, + const struct spdk_blob_xattr_opts *clone_xattrs, + spdk_blob_op_with_id_complete cb_fn, void *cb_arg) +{ + spdk_bs_create_blob_ext(bs, NULL, cb_fn, cb_arg); +} + +static void +_lvol_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx) +{ + fn(ctx); +} + +static void +lvol_store_op_with_handle_complete(void *cb_arg, struct spdk_lvol_store *lvol_store, int lvserrno) +{ + g_lvol_store = lvol_store; + g_lvserrno = lvserrno; +} + +static void +lvol_op_complete(void *cb_arg, int lvolerrno) +{ + g_lvolerrno = lvolerrno; +} + +static void +lvol_op_with_handle_complete(void *cb_arg, struct spdk_lvol *lvol, int lvserrno) +{ + g_lvol = lvol; + g_lvserrno = lvserrno; +} + +static void +lvol_store_op_complete(void *cb_arg, int lvserrno) +{ + g_lvserrno = lvserrno; +} + +static void +close_cb(void *cb_arg, int lvolerrno) +{ + g_lvserrno = lvolerrno; +} + +static void +destroy_cb(void *cb_arg, int lvolerrno) +{ + g_lvserrno = lvolerrno; +} + +static void +lvs_init_unload_success(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(!TAILQ_EMPTY(&g_lvol_stores)); + + spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + /* Lvol store has an open lvol, this unload should fail. */ + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == -EBUSY); + CU_ASSERT(g_lvserrno == -EBUSY); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(!TAILQ_EMPTY(&g_lvol_stores)); + + /* Lvol has to be closed (or destroyed) before unloading lvol store. */ + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvs_init_destroy_success(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + /* Lvol store contains one lvol, this destroy should fail. */ + g_lvserrno = -1; + rc = spdk_lvs_destroy(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == -EBUSY); + CU_ASSERT(g_lvserrno == -EBUSY); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + + spdk_lvol_destroy(g_lvol, destroy_cb, NULL); + + g_lvserrno = -1; + rc = spdk_lvs_destroy(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + spdk_free_thread(); +} + +static void +lvs_init_opts_success(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + g_lvserrno = -1; + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + opts.cluster_sz = 8192; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(dev.bs->bs_opts.cluster_sz == opts.cluster_sz); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvs_unload_lvs_is_null_fail(void) +{ + int rc = 0; + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + g_lvserrno = -1; + rc = spdk_lvs_unload(NULL, lvol_store_op_complete, NULL); + CU_ASSERT(rc == -ENODEV); + CU_ASSERT(g_lvserrno == -1); + + spdk_free_thread(); +} + +static void +lvs_names(void) +{ + struct lvol_ut_bs_dev dev_x, dev_y, dev_x2; + struct spdk_lvs_opts opts_none, opts_x, opts_y, opts_full; + struct spdk_lvol_store *lvs_x, *lvs_y, *lvs_x2; + int rc = 0; + + init_dev(&dev_x); + init_dev(&dev_y); + init_dev(&dev_x2); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts_none); + spdk_lvs_opts_init(&opts_x); + opts_x.name[0] = 'x'; + spdk_lvs_opts_init(&opts_y); + opts_y.name[0] = 'y'; + spdk_lvs_opts_init(&opts_full); + memset(opts_full.name, 'a', sizeof(opts_full.name)); + + /* Test that opts with no name fails spdk_lvs_init(). */ + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + rc = spdk_lvs_init(&dev_x.bs_dev, &opts_none, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc != 0); + CU_ASSERT(g_lvol_store == NULL); + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + + /* Test that opts with no null terminator for name fails spdk_lvs_init(). */ + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + rc = spdk_lvs_init(&dev_x.bs_dev, &opts_full, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc != 0); + CU_ASSERT(g_lvol_store == NULL); + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + + /* Test that we can create an lvolstore with name 'x'. */ + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + g_lvol_store = NULL; + rc = spdk_lvs_init(&dev_x.bs_dev, &opts_x, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(!TAILQ_EMPTY(&g_lvol_stores)); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + lvs_x = g_lvol_store; + + /* Test that we can create an lvolstore with name 'y'. */ + g_lvol_store = NULL; + rc = spdk_lvs_init(&dev_y.bs_dev, &opts_y, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + lvs_y = g_lvol_store; + + /* Test that we cannot create another lvolstore with name 'x'. */ + rc = spdk_lvs_init(&dev_x2.bs_dev, &opts_x, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == -EEXIST); + + /* Now destroy lvolstore 'x' and then confirm we can create a new lvolstore with name 'x'. */ + g_lvserrno = -1; + rc = spdk_lvs_destroy(lvs_x, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + rc = spdk_lvs_init(&dev_x.bs_dev, &opts_x, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + lvs_x = g_lvol_store; + + /* + * Unload lvolstore 'x'. Then we should be able to create another lvolstore with name 'x'. + */ + g_lvserrno = -1; + rc = spdk_lvs_unload(lvs_x, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + rc = spdk_lvs_init(&dev_x2.bs_dev, &opts_x, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + lvs_x2 = g_lvol_store; + + /* Confirm that we cannot load the first lvolstore 'x'. */ + g_lvserrno = 0; + spdk_lvs_load(&dev_x.bs_dev, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno != 0); + + /* Destroy the second lvolstore 'x'. Then we should be able to load the first lvolstore 'x'. */ + g_lvserrno = -1; + rc = spdk_lvs_destroy(lvs_x2, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvserrno = -1; + spdk_lvs_load(&dev_x.bs_dev, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + lvs_x = g_lvol_store; + + g_lvserrno = -1; + rc = spdk_lvs_destroy(lvs_x, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + + g_lvserrno = -1; + rc = spdk_lvs_destroy(lvs_y, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + + spdk_free_thread(); +} + +static void +lvol_create_destroy_success(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + spdk_lvol_destroy(g_lvol, destroy_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_create_fail(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvol_store = NULL; + g_lvserrno = 0; + rc = spdk_lvs_init(NULL, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc != 0); + CU_ASSERT(g_lvol_store == NULL); + + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + g_lvol = NULL; + rc = spdk_lvol_create(NULL, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc != 0); + CU_ASSERT(g_lvol == NULL); + + g_lvol = NULL; + rc = spdk_lvol_create(g_lvol_store, "lvol", DEV_BUFFER_SIZE + 1, false, + lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno != 0); + CU_ASSERT(g_lvol == NULL); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_destroy_fail(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + spdk_lvol_destroy(g_lvol, destroy_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_close_fail(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_close_success(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_resize(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_resize_rc = 0; + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + /* Resize to same size */ + spdk_lvol_resize(g_lvol, 10, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + + /* Resize to smaller size */ + spdk_lvol_resize(g_lvol, 5, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + + /* Resize to bigger size */ + spdk_lvol_resize(g_lvol, 15, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + + /* Resize to size = 0 */ + spdk_lvol_resize(g_lvol, 0, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + + /* Resize to bigger size than available */ + g_lvserrno = 0; + spdk_lvol_resize(g_lvol, 0xFFFFFFFF, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno != 0); + + /* Fail resize */ + g_resize_rc = -1; + g_lvserrno = 0; + spdk_lvol_resize(g_lvol, 10, lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno != 0); + g_resize_rc = 0; + + g_resize_rc = 0; + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + spdk_lvol_destroy(g_lvol, destroy_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +null_cb(void *ctx, struct spdk_blob_store *bs, int bserrno) +{ + SPDK_CU_ASSERT_FATAL(bs != NULL); +} + +static void +lvs_load(void) +{ + int rc = -1; + struct lvol_ut_bs_dev dev; + struct spdk_lvs_with_handle_req *req; + struct spdk_bs_opts bs_opts = {}; + struct spdk_blob *super_blob; + + req = calloc(1, sizeof(*req)); + SPDK_CU_ASSERT_FATAL(req != NULL); + + init_dev(&dev); + spdk_bs_opts_init(&bs_opts); + snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "LVOLSTORE"); + spdk_bs_init(&dev.bs_dev, &bs_opts, null_cb, NULL); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + /* Fail on bs load */ + dev.load_status = -1; + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno != 0); + CU_ASSERT(g_lvol_store == NULL); + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + + /* Fail on getting super blob */ + dev.load_status = 0; + dev.bs->get_super_status = -1; + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno == -ENODEV); + CU_ASSERT(g_lvol_store == NULL); + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + + /* Fail on opening super blob */ + g_lvserrno = 0; + super_blob = calloc(1, sizeof(*super_blob)); + super_blob->id = 0x100; + super_blob->open_status = -1; + TAILQ_INSERT_TAIL(&dev.bs->blobs, super_blob, link); + dev.bs->super_blobid = 0x100; + dev.bs->get_super_status = 0; + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno == -ENODEV); + CU_ASSERT(g_lvol_store == NULL); + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + + /* Fail on getting uuid */ + g_lvserrno = 0; + super_blob->open_status = 0; + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno == -EINVAL); + CU_ASSERT(g_lvol_store == NULL); + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + + /* Fail on getting name */ + g_lvserrno = 0; + spdk_blob_set_xattr(super_blob, "uuid", uuid, SPDK_UUID_STRING_LEN); + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno == -EINVAL); + CU_ASSERT(g_lvol_store == NULL); + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + + /* Fail on closing super blob */ + g_lvserrno = 0; + spdk_blob_set_xattr(super_blob, "name", "lvs", strnlen("lvs", SPDK_LVS_NAME_MAX) + 1); + super_blob->close_status = -1; + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno == -ENODEV); + CU_ASSERT(g_lvol_store == NULL); + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + + /* Load successfully */ + g_lvserrno = 0; + super_blob->close_status = 0; + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store != NULL); + CU_ASSERT(!TAILQ_EMPTY(&g_lvol_stores)); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores)); + + free(req); + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvols_load(void) +{ + int rc = -1; + struct lvol_ut_bs_dev dev; + struct spdk_lvs_with_handle_req *req; + struct spdk_bs_opts bs_opts; + struct spdk_blob *super_blob, *blob1, *blob2, *blob3; + + req = calloc(1, sizeof(*req)); + SPDK_CU_ASSERT_FATAL(req != NULL); + + init_dev(&dev); + spdk_bs_opts_init(&bs_opts); + snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "LVOLSTORE"); + spdk_bs_init(&dev.bs_dev, &bs_opts, null_cb, NULL); + super_blob = calloc(1, sizeof(*super_blob)); + SPDK_CU_ASSERT_FATAL(super_blob != NULL); + super_blob->id = 0x100; + spdk_blob_set_xattr(super_blob, "uuid", uuid, SPDK_UUID_STRING_LEN); + spdk_blob_set_xattr(super_blob, "name", "lvs", strnlen("lvs", SPDK_LVS_NAME_MAX) + 1); + TAILQ_INSERT_TAIL(&dev.bs->blobs, super_blob, link); + dev.bs->super_blobid = 0x100; + + /* + * Create 3 blobs, write different char values to the last char in the UUID + * to make sure they are unique. + */ + blob1 = calloc(1, sizeof(*blob1)); + SPDK_CU_ASSERT_FATAL(blob1 != NULL); + blob1->id = 0x1; + spdk_blob_set_xattr(blob1, "uuid", uuid, SPDK_UUID_STRING_LEN); + spdk_blob_set_xattr(blob1, "name", "lvol1", strnlen("lvol1", SPDK_LVOL_NAME_MAX) + 1); + blob1->uuid[SPDK_UUID_STRING_LEN - 2] = '1'; + + blob2 = calloc(1, sizeof(*blob2)); + SPDK_CU_ASSERT_FATAL(blob2 != NULL); + blob2->id = 0x2; + spdk_blob_set_xattr(blob2, "uuid", uuid, SPDK_UUID_STRING_LEN); + spdk_blob_set_xattr(blob2, "name", "lvol2", strnlen("lvol2", SPDK_LVOL_NAME_MAX) + 1); + blob2->uuid[SPDK_UUID_STRING_LEN - 2] = '2'; + + blob3 = calloc(1, sizeof(*blob3)); + SPDK_CU_ASSERT_FATAL(blob3 != NULL); + blob3->id = 0x2; + spdk_blob_set_xattr(blob3, "uuid", uuid, SPDK_UUID_STRING_LEN); + spdk_blob_set_xattr(blob3, "name", "lvol3", strnlen("lvol3", SPDK_LVOL_NAME_MAX) + 1); + blob3->uuid[SPDK_UUID_STRING_LEN - 2] = '3'; + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + /* Load lvs with 0 blobs */ + g_lvserrno = 0; + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT(g_lvol_store != NULL); + CU_ASSERT(g_lvserrno == 0); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + + TAILQ_INSERT_TAIL(&dev.bs->blobs, blob1, link); + TAILQ_INSERT_TAIL(&dev.bs->blobs, blob2, link); + TAILQ_INSERT_TAIL(&dev.bs->blobs, blob3, link); + + /* Load lvs again with 3 blobs, but fail on 1st one */ + g_lvol_store = NULL; + g_lvserrno = 0; + blob1->load_status = -1; + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno != 0); + CU_ASSERT(g_lvol_store == NULL); + + /* Load lvs again with 3 blobs, but fail on 3rd one */ + g_lvol_store = NULL; + g_lvserrno = 0; + blob1->load_status = 0; + blob2->load_status = 0; + blob3->load_status = -1; + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno != 0); + CU_ASSERT(g_lvol_store == NULL); + + /* Load lvs again with 3 blobs, with success */ + g_lvol_store = NULL; + g_lvserrno = 0; + blob1->load_status = 0; + blob2->load_status = 0; + blob3->load_status = 0; + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + CU_ASSERT(!TAILQ_EMPTY(&g_lvol_store->lvols)); + + g_lvserrno = -1; + /* rc = */ spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + /* + * Disable these two asserts for now. lvolstore should allow unload as long + * as the lvols were not opened - but this is coming a future patch. + */ + /* CU_ASSERT(rc == 0); */ + /* CU_ASSERT(g_lvserrno == 0); */ + + free(req); + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_open(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_with_handle_req *req; + struct spdk_bs_opts bs_opts; + struct spdk_blob *super_blob, *blob1, *blob2, *blob3; + struct spdk_lvol *lvol, *tmp; + + req = calloc(1, sizeof(*req)); + SPDK_CU_ASSERT_FATAL(req != NULL); + + init_dev(&dev); + spdk_bs_opts_init(&bs_opts); + snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "LVOLSTORE"); + spdk_bs_init(&dev.bs_dev, &bs_opts, null_cb, NULL); + super_blob = calloc(1, sizeof(*super_blob)); + SPDK_CU_ASSERT_FATAL(super_blob != NULL); + super_blob->id = 0x100; + spdk_blob_set_xattr(super_blob, "uuid", uuid, SPDK_UUID_STRING_LEN); + spdk_blob_set_xattr(super_blob, "name", "lvs", strnlen("lvs", SPDK_LVS_NAME_MAX) + 1); + TAILQ_INSERT_TAIL(&dev.bs->blobs, super_blob, link); + dev.bs->super_blobid = 0x100; + + /* + * Create 3 blobs, write different char values to the last char in the UUID + * to make sure they are unique. + */ + blob1 = calloc(1, sizeof(*blob1)); + SPDK_CU_ASSERT_FATAL(blob1 != NULL); + blob1->id = 0x1; + spdk_blob_set_xattr(blob1, "uuid", uuid, SPDK_UUID_STRING_LEN); + spdk_blob_set_xattr(blob1, "name", "lvol1", strnlen("lvol1", SPDK_LVOL_NAME_MAX) + 1); + blob1->uuid[SPDK_UUID_STRING_LEN - 2] = '1'; + + blob2 = calloc(1, sizeof(*blob2)); + SPDK_CU_ASSERT_FATAL(blob2 != NULL); + blob2->id = 0x2; + spdk_blob_set_xattr(blob2, "uuid", uuid, SPDK_UUID_STRING_LEN); + spdk_blob_set_xattr(blob2, "name", "lvol2", strnlen("lvol2", SPDK_LVOL_NAME_MAX) + 1); + blob2->uuid[SPDK_UUID_STRING_LEN - 2] = '2'; + + blob3 = calloc(1, sizeof(*blob3)); + SPDK_CU_ASSERT_FATAL(blob3 != NULL); + blob3->id = 0x2; + spdk_blob_set_xattr(blob3, "uuid", uuid, SPDK_UUID_STRING_LEN); + spdk_blob_set_xattr(blob3, "name", "lvol3", strnlen("lvol3", SPDK_LVOL_NAME_MAX) + 1); + blob3->uuid[SPDK_UUID_STRING_LEN - 2] = '3'; + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + TAILQ_INSERT_TAIL(&dev.bs->blobs, blob1, link); + TAILQ_INSERT_TAIL(&dev.bs->blobs, blob2, link); + TAILQ_INSERT_TAIL(&dev.bs->blobs, blob3, link); + + /* Load lvs with 3 blobs */ + g_lvol_store = NULL; + g_lvserrno = 0; + spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_lvol_stores)); + + blob1->open_status = -1; + blob2->open_status = -1; + blob3->open_status = -1; + + /* Fail opening all lvols */ + TAILQ_FOREACH_SAFE(lvol, &g_lvol_store->lvols, link, tmp) { + spdk_lvol_open(lvol, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno != 0); + } + + blob1->open_status = 0; + blob2->open_status = 0; + blob3->open_status = 0; + + /* Open all lvols */ + TAILQ_FOREACH_SAFE(lvol, &g_lvol_store->lvols, link, tmp) { + spdk_lvol_open(lvol, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + } + + /* Close all lvols */ + TAILQ_FOREACH_SAFE(lvol, &g_lvol_store->lvols, link, tmp) { + spdk_lvol_close(lvol, lvol_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + } + + g_lvserrno = -1; + spdk_lvs_destroy(g_lvol_store, lvol_store_op_complete, NULL); + + free(req); + free(blob1); + free(blob2); + free(blob3); + + spdk_free_thread(); +} + +static void +lvol_snapshot(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvol *lvol; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, true, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + lvol = g_lvol; + + spdk_lvol_create_snapshot(lvol, "snap", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT_STRING_EQUAL(g_lvol->name, "snap"); + + /* Lvol has to be closed (or destroyed) before unloading lvol store. */ + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + g_lvserrno = -1; + + spdk_lvol_close(lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + g_lvserrno = -1; + + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_snapshot_fail(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvol *lvol, *snap; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, true, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + lvol = g_lvol; + + spdk_lvol_create_snapshot(NULL, "snap", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno < 0); + SPDK_CU_ASSERT_FATAL(g_lvol == NULL); + + spdk_lvol_create_snapshot(lvol, "", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno < 0); + SPDK_CU_ASSERT_FATAL(g_lvol == NULL); + + spdk_lvol_create_snapshot(lvol, NULL, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno < 0); + SPDK_CU_ASSERT_FATAL(g_lvol == NULL); + + spdk_lvol_create_snapshot(lvol, "snap", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT_STRING_EQUAL(g_lvol->name, "snap"); + + snap = g_lvol; + + spdk_lvol_create_snapshot(lvol, "snap", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno < 0); + + spdk_lvol_close(lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + g_lvserrno = -1; + + spdk_lvol_close(snap, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + g_lvserrno = -1; + + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_clone(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvol *lvol; + struct spdk_lvol *snap; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, true, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + lvol = g_lvol; + + spdk_lvol_create_snapshot(lvol, "snap", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT_STRING_EQUAL(g_lvol->name, "snap"); + + snap = g_lvol; + + spdk_lvol_create_clone(snap, "clone", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT_STRING_EQUAL(g_lvol->name, "clone"); + + /* Lvol has to be closed (or destroyed) before unloading lvol store. */ + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + g_lvserrno = -1; + + spdk_lvol_close(snap, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + g_lvserrno = -1; + + spdk_lvol_close(lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + g_lvserrno = -1; + + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_clone_fail(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvol *lvol; + struct spdk_lvol *snap; + struct spdk_lvol *clone; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, true, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + lvol = g_lvol; + + spdk_lvol_create_snapshot(lvol, "snap", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT_STRING_EQUAL(g_lvol->name, "snap"); + + snap = g_lvol; + + spdk_lvol_create_clone(NULL, "clone", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno < 0); + + spdk_lvol_create_clone(snap, "", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno < 0); + + spdk_lvol_create_clone(snap, NULL, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno < 0); + + spdk_lvol_create_clone(snap, "clone", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT_STRING_EQUAL(g_lvol->name, "clone"); + + clone = g_lvol; + + spdk_lvol_create_clone(snap, "clone", lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno < 0); + + /* Lvol has to be closed (or destroyed) before unloading lvol store. */ + spdk_lvol_close(clone, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + g_lvserrno = -1; + + spdk_lvol_close(snap, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + g_lvserrno = -1; + + spdk_lvol_close(lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + g_lvserrno = -1; + + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_names(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + struct spdk_lvol_store *lvs; + struct spdk_lvol *lvol, *lvol2; + char fullname[SPDK_LVOL_NAME_MAX]; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + g_lvol_store = NULL; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + lvs = g_lvol_store; + + rc = spdk_lvol_create(lvs, NULL, 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == -EINVAL); + + rc = spdk_lvol_create(lvs, "", 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == -EINVAL); + + memset(fullname, 'x', sizeof(fullname)); + rc = spdk_lvol_create(lvs, fullname, 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == -EINVAL); + + g_lvserrno = -1; + rc = spdk_lvol_create(lvs, "lvol", 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + lvol = g_lvol; + + rc = spdk_lvol_create(lvs, "lvol", 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == -EEXIST); + + g_lvserrno = -1; + rc = spdk_lvol_create(lvs, "lvol2", 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + lvol2 = g_lvol; + + spdk_lvol_close(lvol, close_cb, NULL); + spdk_lvol_destroy(lvol, lvol_op_complete, NULL); + + g_lvserrno = -1; + g_lvol = NULL; + rc = spdk_lvol_create(lvs, "lvol", 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + lvol = g_lvol; + + spdk_lvol_close(lvol, close_cb, NULL); + spdk_lvol_destroy(lvol, destroy_cb, NULL); + + spdk_lvol_close(lvol2, close_cb, NULL); + spdk_lvol_destroy(lvol2, destroy_cb, NULL); + + /* Simulate creating two lvols with same name simultaneously. */ + lvol = calloc(1, sizeof(*lvol)); + SPDK_CU_ASSERT_FATAL(lvol != NULL); + snprintf(lvol->name, sizeof(lvol->name), "tmp_name"); + TAILQ_INSERT_TAIL(&lvs->pending_lvols, lvol, link); + rc = spdk_lvol_create(lvs, "tmp_name", 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == -EEXIST); + + /* Remove name from temporary list and try again. */ + TAILQ_REMOVE(&lvs->pending_lvols, lvol, link); + free(lvol); + + rc = spdk_lvol_create(lvs, "tmp_name", 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + lvol = g_lvol; + + spdk_lvol_close(lvol, close_cb, NULL); + spdk_lvol_destroy(lvol, destroy_cb, NULL); + + g_lvserrno = -1; + rc = spdk_lvs_destroy(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + spdk_free_thread(); +} + +static void +lvol_rename(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + struct spdk_lvol_store *lvs; + struct spdk_lvol *lvol, *lvol2; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + g_lvol_store = NULL; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + lvs = g_lvol_store; + + /* Trying to create new lvol */ + g_lvserrno = -1; + rc = spdk_lvol_create(lvs, "lvol", 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + lvol = g_lvol; + + /* Trying to create second lvol with existing lvol name */ + g_lvserrno = -1; + g_lvol = NULL; + rc = spdk_lvol_create(lvs, "lvol", 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == -EEXIST); + CU_ASSERT(g_lvserrno == -1); + SPDK_CU_ASSERT_FATAL(g_lvol == NULL); + + /* Trying to create second lvol with non existing name */ + g_lvserrno = -1; + rc = spdk_lvol_create(lvs, "lvol2", 1, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + lvol2 = g_lvol; + + /* Trying to rename lvol with not existing name */ + spdk_lvol_rename(lvol, "lvol_new", lvol_op_complete, NULL); + CU_ASSERT(g_lvolerrno == 0); + CU_ASSERT_STRING_EQUAL(lvol->name, "lvol_new"); + + /* Trying to rename lvol with other lvol name */ + spdk_lvol_rename(lvol2, "lvol_new", lvol_op_complete, NULL); + CU_ASSERT(g_lvolerrno == -EEXIST); + CU_ASSERT_STRING_NOT_EQUAL(lvol2->name, "lvol_new"); + + spdk_lvol_close(lvol, close_cb, NULL); + spdk_lvol_destroy(lvol, lvol_op_complete, NULL); + + spdk_lvol_close(lvol2, close_cb, NULL); + spdk_lvol_destroy(lvol2, lvol_op_complete, NULL); + + g_lvserrno = -1; + rc = spdk_lvs_destroy(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + spdk_free_thread(); +} + +static void +lvs_rename(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + struct spdk_lvol_store *lvs, *lvs2; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + g_lvserrno = -1; + g_lvol_store = NULL; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + lvs = g_lvol_store; + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "unimportant_lvs_name"); + g_lvserrno = -1; + g_lvol_store = NULL; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + lvs2 = g_lvol_store; + + /* Trying to rename lvs with new name */ + spdk_lvs_rename(lvs, "new_lvs_name", lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name"); + + /* Trying to rename lvs with name lvs already has */ + spdk_lvs_rename(lvs, "new_lvs_name", lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name"); + + /* Trying to rename lvs with name already existing */ + spdk_lvs_rename(lvs2, "new_lvs_name", lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == -EEXIST); + CU_ASSERT_STRING_EQUAL(lvs2->name, "unimportant_lvs_name"); + + /* Trying to rename lvs with another rename process started with the same name */ + /* Simulate renaming process in progress */ + snprintf(lvs2->new_name, sizeof(lvs2->new_name), "another_new_lvs_name"); + CU_ASSERT_STRING_EQUAL(lvs2->new_name, "another_new_lvs_name"); + /* Start second process */ + spdk_lvs_rename(lvs, "another_new_lvs_name", lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno == -EEXIST); + CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name"); + /* reverting lvs2 new name to proper value */ + snprintf(lvs2->new_name, sizeof(lvs2->new_name), "unimportant_lvs_name"); + CU_ASSERT_STRING_EQUAL(lvs2->new_name, "unimportant_lvs_name"); + + /* Simulate error while lvs rename */ + g_lvs_rename_blob_open_error = true; + spdk_lvs_rename(lvs, "complete_new_lvs_name", lvol_store_op_complete, NULL); + CU_ASSERT(g_lvserrno != 0); + CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name"); + CU_ASSERT_STRING_EQUAL(lvs->new_name, "new_lvs_name"); + g_lvs_rename_blob_open_error = false; + + g_lvserrno = -1; + rc = spdk_lvs_destroy(lvs, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + g_lvserrno = -1; + rc = spdk_lvs_destroy(lvs2, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + spdk_free_thread(); +} +static void lvol_refcnt(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + struct spdk_lvol *lvol; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + + spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT(g_lvol->ref_count == 1); + + lvol = g_lvol; + spdk_lvol_open(g_lvol, lvol_op_with_handle_complete, NULL); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + CU_ASSERT(lvol->ref_count == 2); + + /* Trying to destroy lvol while its open should fail */ + spdk_lvol_destroy(lvol, lvol_op_complete, NULL); + CU_ASSERT(g_lvolerrno != 0); + + spdk_lvol_close(lvol, lvol_op_complete, NULL); + CU_ASSERT(lvol->ref_count == 1); + CU_ASSERT(g_lvolerrno == 0); + + spdk_lvol_close(lvol, lvol_op_complete, NULL); + CU_ASSERT(lvol->ref_count == 0); + CU_ASSERT(g_lvolerrno == 0); + + /* Try to close already closed lvol */ + spdk_lvol_close(lvol, lvol_op_complete, NULL); + CU_ASSERT(lvol->ref_count == 0); + CU_ASSERT(g_lvolerrno != 0); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_create_thin_provisioned(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + CU_ASSERT(g_lvol->blob->thin_provisioned == false); + + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + spdk_lvol_destroy(g_lvol, destroy_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + + spdk_lvol_create(g_lvol_store, "lvol", 10, true, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + CU_ASSERT(g_lvol->blob->thin_provisioned == true); + + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + spdk_lvol_destroy(g_lvol, destroy_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + spdk_free_thread(); +} + +static void +lvol_inflate(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + g_inflate_rc = -1; + spdk_lvol_inflate(g_lvol, lvol_op_complete, NULL); + CU_ASSERT(g_lvolerrno != 0); + + g_inflate_rc = 0; + spdk_lvol_inflate(g_lvol, lvol_op_complete, NULL); + CU_ASSERT(g_lvolerrno == 0); + + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + spdk_lvol_destroy(g_lvol, destroy_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + /* Make sure that all references to the io_channel was closed after + * inflate call + */ + CU_ASSERT(g_io_channel == NULL); + + spdk_free_thread(); +} + +static void +lvol_decouple_parent(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + int rc = 0; + + init_dev(&dev); + + spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL); + + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + + spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol != NULL); + + g_inflate_rc = -1; + spdk_lvol_decouple_parent(g_lvol, lvol_op_complete, NULL); + CU_ASSERT(g_lvolerrno != 0); + + g_inflate_rc = 0; + spdk_lvol_decouple_parent(g_lvol, lvol_op_complete, NULL); + CU_ASSERT(g_lvolerrno == 0); + + spdk_lvol_close(g_lvol, close_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + spdk_lvol_destroy(g_lvol, destroy_cb, NULL); + CU_ASSERT(g_lvserrno == 0); + + g_lvserrno = -1; + rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + g_lvol_store = NULL; + + free_dev(&dev); + + /* Make sure that all references to the io_channel was closed after + * inflate call + */ + CU_ASSERT(g_io_channel == NULL); + + spdk_free_thread(); +} + +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("lvol", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "lvs_init_unload_success", lvs_init_unload_success) == NULL || + CU_add_test(suite, "lvs_init_destroy_success", lvs_init_destroy_success) == NULL || + CU_add_test(suite, "lvs_init_opts_success", lvs_init_opts_success) == NULL || + CU_add_test(suite, "lvs_unload_lvs_is_null_fail", lvs_unload_lvs_is_null_fail) == NULL || + CU_add_test(suite, "lvs_names", lvs_names) == NULL || + CU_add_test(suite, "lvol_create_destroy_success", lvol_create_destroy_success) == NULL || + CU_add_test(suite, "lvol_create_fail", lvol_create_fail) == NULL || + CU_add_test(suite, "lvol_destroy_fail", lvol_destroy_fail) == NULL || + CU_add_test(suite, "lvol_close_fail", lvol_close_fail) == NULL || + CU_add_test(suite, "lvol_close_success", lvol_close_success) == NULL || + CU_add_test(suite, "lvol_resize", lvol_resize) == NULL || + CU_add_test(suite, "lvs_load", lvs_load) == NULL || + CU_add_test(suite, "lvols_load", lvols_load) == NULL || + CU_add_test(suite, "lvol_open", lvol_open) == NULL || + CU_add_test(suite, "lvol_load", lvs_load) == NULL || + CU_add_test(suite, "lvs_load", lvols_load) == NULL || + CU_add_test(suite, "lvol_open", lvol_open) == NULL || + CU_add_test(suite, "lvol_snapshot", lvol_snapshot) == NULL || + CU_add_test(suite, "lvol_snapshot_fail", lvol_snapshot_fail) == NULL || + CU_add_test(suite, "lvol_clone", lvol_clone) == NULL || + CU_add_test(suite, "lvol_clone_fail", lvol_clone_fail) == NULL || + CU_add_test(suite, "lvol_refcnt", lvol_refcnt) == NULL || + CU_add_test(suite, "lvol_names", lvol_names) == NULL || + CU_add_test(suite, "lvol_create_thin_provisioned", lvol_create_thin_provisioned) == NULL || + CU_add_test(suite, "lvol_rename", lvol_rename) == NULL || + CU_add_test(suite, "lvs_rename", lvs_rename) == NULL || + CU_add_test(suite, "lvol_inflate", lvol_inflate) == NULL || + CU_add_test(suite, "lvol_decouple_parent", lvol_decouple_parent) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvme/Makefile b/src/spdk/test/unit/lib/nvme/Makefile new file mode 100644 index 00000000..fb17a2d0 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/Makefile @@ -0,0 +1,47 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = nvme.c nvme_ctrlr.c nvme_ctrlr_cmd.c nvme_ctrlr_ocssd_cmd.c nvme_ns.c nvme_ns_cmd.c nvme_ns_ocssd_cmd.c nvme_pcie.c nvme_qpair.c \ + nvme_quirks.c \ + +DIRS-$(CONFIG_RDMA) += nvme_rdma.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme.c/.gitignore new file mode 100644 index 00000000..90c0c167 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme.c/.gitignore @@ -0,0 +1 @@ +nvme_ut diff --git a/src/spdk/test/unit/lib/nvme/nvme.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme.c/Makefile new file mode 100644 index 00000000..4202cf54 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme.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 = nvme_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme.c/nvme_ut.c b/src/spdk/test/unit/lib/nvme/nvme.c/nvme_ut.c new file mode 100644 index 00000000..6925a2cf --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme.c/nvme_ut.c @@ -0,0 +1,1135 @@ +/*- + * 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_cunit.h" + +#include "spdk/env.h" + +#include "nvme/nvme.c" + +#include "spdk_internal/mock.h" + +#include "common/lib/test_env.c" + +DEFINE_STUB_V(nvme_ctrlr_fail, + (struct spdk_nvme_ctrlr *ctrlr, bool hot_remove)) + +DEFINE_STUB_V(nvme_ctrlr_proc_get_ref, (struct spdk_nvme_ctrlr *ctrlr)) + +DEFINE_STUB_V(nvme_ctrlr_proc_put_ref, (struct spdk_nvme_ctrlr *ctrlr)) + +DEFINE_STUB(spdk_pci_nvme_enumerate, int, + (spdk_pci_enum_cb enum_cb, void *enum_ctx), -1) + +DEFINE_STUB(spdk_pci_device_get_id, struct spdk_pci_id, + (struct spdk_pci_device *pci_dev), + MOCK_STRUCT_INIT(.vendor_id = 0xffff, .device_id = 0xffff, + .subvendor_id = 0xffff, .subdevice_id = 0xffff)) + +DEFINE_STUB(spdk_nvme_transport_available, bool, + (enum spdk_nvme_transport_type trtype), true) + +DEFINE_STUB(nvme_ctrlr_add_process, int, + (struct spdk_nvme_ctrlr *ctrlr, void *devhandle), 0) + +DEFINE_STUB(nvme_ctrlr_process_init, int, + (struct spdk_nvme_ctrlr *ctrlr), 0) + +DEFINE_STUB(spdk_pci_device_get_addr, struct spdk_pci_addr, + (struct spdk_pci_device *pci_dev), {0}) + +DEFINE_STUB(nvme_ctrlr_get_ref_count, int, + (struct spdk_nvme_ctrlr *ctrlr), 0) + +DEFINE_STUB(dummy_probe_cb, bool, + (void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr_opts *opts), false) + +DEFINE_STUB(nvme_transport_ctrlr_construct, struct spdk_nvme_ctrlr *, + (const struct spdk_nvme_transport_id *trid, + const struct spdk_nvme_ctrlr_opts *opts, + void *devhandle), NULL) + +DEFINE_STUB(spdk_nvme_qpair_process_completions, int32_t, + (struct spdk_nvme_qpair *qpair, + uint32_t max_completions), 0); + +static bool ut_destruct_called = false; +void +nvme_ctrlr_destruct(struct spdk_nvme_ctrlr *ctrlr) +{ + ut_destruct_called = true; +} + +void +spdk_nvme_ctrlr_get_default_ctrlr_opts(struct spdk_nvme_ctrlr_opts *opts, size_t opts_size) +{ + memset(opts, 0, sizeof(*opts)); +} + +static void +memset_trid(struct spdk_nvme_transport_id *trid1, struct spdk_nvme_transport_id *trid2) +{ + memset(trid1, 0, sizeof(struct spdk_nvme_transport_id)); + memset(trid2, 0, sizeof(struct spdk_nvme_transport_id)); +} + +static bool ut_check_trtype = false; +int +nvme_transport_ctrlr_scan(const struct spdk_nvme_transport_id *trid, + void *cb_ctx, + spdk_nvme_probe_cb probe_cb, + spdk_nvme_remove_cb remove_cb, + bool direct_connect) +{ + struct spdk_nvme_ctrlr *ctrlr = NULL; + + if (ut_check_trtype == true) { + CU_ASSERT(trid->trtype == SPDK_NVME_TRANSPORT_PCIE); + } + + if (direct_connect == true && probe_cb) { + nvme_robust_mutex_unlock(&g_spdk_nvme_driver->lock); + ctrlr = spdk_nvme_get_ctrlr_by_trid(trid); + nvme_robust_mutex_lock(&g_spdk_nvme_driver->lock); + probe_cb(cb_ctx, trid, &ctrlr->opts); + } + return 0; +} + +static bool ut_attach_cb_called = false; +static void +dummy_attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ + ut_attach_cb_called = true; +} + +static void +test_spdk_nvme_probe(void) +{ + int rc = 0; + const struct spdk_nvme_transport_id *trid = NULL; + void *cb_ctx = NULL; + spdk_nvme_probe_cb probe_cb = NULL; + spdk_nvme_attach_cb attach_cb = dummy_attach_cb; + spdk_nvme_remove_cb remove_cb = NULL; + struct spdk_nvme_ctrlr ctrlr; + pthread_mutexattr_t attr; + struct nvme_driver dummy; + g_spdk_nvme_driver = &dummy; + + /* driver init fails */ + MOCK_SET(spdk_process_is_primary, false); + MOCK_SET(spdk_memzone_lookup, NULL); + rc = spdk_nvme_probe(trid, cb_ctx, probe_cb, attach_cb, remove_cb); + CU_ASSERT(rc == -1); + + /* + * For secondary processes, the attach_cb should automatically get + * called for any controllers already initialized by the primary + * process. + */ + MOCK_SET(spdk_nvme_transport_available, false); + MOCK_SET(spdk_process_is_primary, true); + dummy.initialized = true; + g_spdk_nvme_driver = &dummy; + rc = spdk_nvme_probe(trid, cb_ctx, probe_cb, attach_cb, remove_cb); + CU_ASSERT(rc == -1); + + /* driver init passes, transport available, secondary call attach_cb */ + MOCK_SET(spdk_nvme_transport_available, true); + MOCK_SET(spdk_process_is_primary, false); + MOCK_SET(spdk_memzone_lookup, g_spdk_nvme_driver); + dummy.initialized = true; + memset(&ctrlr, 0, sizeof(struct spdk_nvme_ctrlr)); + CU_ASSERT(pthread_mutexattr_init(&attr) == 0); + CU_ASSERT(pthread_mutex_init(&dummy.lock, &attr) == 0); + TAILQ_INIT(&dummy.shared_attached_ctrlrs); + TAILQ_INSERT_TAIL(&dummy.shared_attached_ctrlrs, &ctrlr, tailq); + ut_attach_cb_called = false; + /* setup nvme_transport_ctrlr_scan() stub to also check the trype */ + ut_check_trtype = true; + rc = spdk_nvme_probe(trid, cb_ctx, probe_cb, attach_cb, remove_cb); + CU_ASSERT(rc == 0); + CU_ASSERT(ut_attach_cb_called == true); + + /* driver init passes, transport available, we are primary */ + MOCK_SET(spdk_process_is_primary, true); + TAILQ_INIT(&g_nvme_init_ctrlrs); + rc = spdk_nvme_probe(trid, cb_ctx, probe_cb, attach_cb, remove_cb); + CU_ASSERT(rc == 0); + + g_spdk_nvme_driver = NULL; + /* reset to pre-test values */ + MOCK_CLEAR(spdk_memzone_lookup); + ut_check_trtype = false; + + pthread_mutex_destroy(&dummy.lock); + pthread_mutexattr_destroy(&attr); +} + +static void +test_spdk_nvme_connect(void) +{ + struct spdk_nvme_ctrlr *ret_ctrlr = NULL; + struct spdk_nvme_transport_id trid = {}; + struct spdk_nvme_ctrlr_opts opts = {}; + struct spdk_nvme_ctrlr ctrlr; + pthread_mutexattr_t attr; + struct nvme_driver dummy; + + /* initialize the variable to prepare the test */ + dummy.initialized = true; + TAILQ_INIT(&dummy.shared_attached_ctrlrs); + g_spdk_nvme_driver = &dummy; + CU_ASSERT(pthread_mutexattr_init(&attr) == 0); + CU_ASSERT(pthread_mutex_init(&g_spdk_nvme_driver->lock, &attr) == 0); + + /* set NULL trid pointer to test immediate return */ + ret_ctrlr = spdk_nvme_connect(NULL, NULL, 0); + CU_ASSERT(ret_ctrlr == NULL); + + /* driver init passes, transport available, secondary process connects ctrlr */ + MOCK_SET(spdk_process_is_primary, false); + MOCK_SET(spdk_memzone_lookup, g_spdk_nvme_driver); + MOCK_SET(spdk_nvme_transport_available, true); + memset(&trid, 0, sizeof(trid)); + trid.trtype = SPDK_NVME_TRANSPORT_PCIE; + ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0); + CU_ASSERT(ret_ctrlr == NULL); + + /* driver init passes, setup one ctrlr on the attached_list */ + memset(&ctrlr, 0, sizeof(struct spdk_nvme_ctrlr)); + snprintf(ctrlr.trid.traddr, sizeof(ctrlr.trid.traddr), "0000:01:00.0"); + ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_PCIE; + TAILQ_INSERT_TAIL(&g_spdk_nvme_driver->shared_attached_ctrlrs, &ctrlr, tailq); + /* get the ctrlr from the attached list */ + snprintf(trid.traddr, sizeof(trid.traddr), "0000:01:00.0"); + ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0); + CU_ASSERT(ret_ctrlr == &ctrlr); + /* get the ctrlr from the attached list with default ctrlr opts */ + ctrlr.opts.num_io_queues = DEFAULT_MAX_IO_QUEUES; + ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0); + CU_ASSERT(ret_ctrlr == &ctrlr); + CU_ASSERT_EQUAL(ret_ctrlr->opts.num_io_queues, DEFAULT_MAX_IO_QUEUES); + /* get the ctrlr from the attached list with default ctrlr opts and consistent opts_size */ + opts.num_io_queues = 1; + ret_ctrlr = spdk_nvme_connect(&trid, &opts, sizeof(opts)); + CU_ASSERT(ret_ctrlr == &ctrlr); + CU_ASSERT_EQUAL(ret_ctrlr->opts.num_io_queues, 1); + /* remove the attached ctrlr on the attached_list */ + CU_ASSERT(spdk_nvme_detach(&ctrlr) == 0); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_nvme_driver->shared_attached_ctrlrs)); + + /* driver init passes, transport available, primary process connects ctrlr */ + MOCK_SET(spdk_process_is_primary, true); + /* setup one ctrlr on the attached_list */ + memset(&ctrlr, 0, sizeof(struct spdk_nvme_ctrlr)); + snprintf(ctrlr.trid.traddr, sizeof(ctrlr.trid.traddr), "0000:02:00.0"); + ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_PCIE; + TAILQ_INSERT_TAIL(&g_spdk_nvme_driver->shared_attached_ctrlrs, &ctrlr, tailq); + /* get the ctrlr from the attached list */ + snprintf(trid.traddr, sizeof(trid.traddr), "0000:02:00.0"); + ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0); + CU_ASSERT(ret_ctrlr == &ctrlr); + /* get the ctrlr from the attached list with default ctrlr opts */ + ctrlr.opts.num_io_queues = DEFAULT_MAX_IO_QUEUES; + ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0); + CU_ASSERT(ret_ctrlr == &ctrlr); + CU_ASSERT_EQUAL(ret_ctrlr->opts.num_io_queues, DEFAULT_MAX_IO_QUEUES); + /* get the ctrlr from the attached list with default ctrlr opts and consistent opts_size */ + opts.num_io_queues = 2; + ret_ctrlr = spdk_nvme_connect(&trid, &opts, sizeof(opts)); + CU_ASSERT(ret_ctrlr == &ctrlr); + CU_ASSERT_EQUAL(ret_ctrlr->opts.num_io_queues, 2); + /* remove the attached ctrlr on the attached_list */ + CU_ASSERT(spdk_nvme_detach(ret_ctrlr) == 0); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_nvme_driver->shared_attached_ctrlrs)); + + /* test driver init failure return */ + MOCK_SET(spdk_process_is_primary, false); + MOCK_SET(spdk_memzone_lookup, NULL); + ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0); + CU_ASSERT(ret_ctrlr == NULL); +} + +static void +test_nvme_init_controllers(void) +{ + int rc = 0; + struct nvme_driver test_driver; + void *cb_ctx = NULL; + spdk_nvme_attach_cb attach_cb = dummy_attach_cb; + struct spdk_nvme_ctrlr ctrlr; + pthread_mutexattr_t attr; + + g_spdk_nvme_driver = &test_driver; + memset(&ctrlr, 0, sizeof(struct spdk_nvme_ctrlr)); + ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_PCIE; + CU_ASSERT(pthread_mutexattr_init(&attr) == 0); + CU_ASSERT(pthread_mutex_init(&test_driver.lock, &attr) == 0); + TAILQ_INIT(&g_nvme_init_ctrlrs); + TAILQ_INSERT_TAIL(&g_nvme_init_ctrlrs, &ctrlr, tailq); + TAILQ_INIT(&test_driver.shared_attached_ctrlrs); + + /* + * Try to initialize, but nvme_ctrlr_process_init will fail. + * Verify correct behavior when it does. + */ + MOCK_SET(nvme_ctrlr_process_init, 1); + g_spdk_nvme_driver->initialized = false; + ut_destruct_called = false; + rc = nvme_init_controllers(cb_ctx, attach_cb); + CU_ASSERT(rc == -1); + CU_ASSERT(g_spdk_nvme_driver->initialized == true); + CU_ASSERT(TAILQ_EMPTY(&g_nvme_init_ctrlrs)); + CU_ASSERT(ut_destruct_called == true); + + /* + * Controller init OK, need to move the controller state machine + * forward by setting the ctrl state so that it can be moved + * the shared_attached_ctrlrs list. + */ + TAILQ_INSERT_TAIL(&g_nvme_init_ctrlrs, &ctrlr, tailq); + ctrlr.state = NVME_CTRLR_STATE_READY; + MOCK_SET(nvme_ctrlr_process_init, 0); + rc = nvme_init_controllers(cb_ctx, attach_cb); + CU_ASSERT(rc == 0); + CU_ASSERT(ut_attach_cb_called == true); + CU_ASSERT(TAILQ_EMPTY(&g_nvme_init_ctrlrs)); + CU_ASSERT(TAILQ_EMPTY(&g_nvme_attached_ctrlrs)); + CU_ASSERT(TAILQ_FIRST(&g_spdk_nvme_driver->shared_attached_ctrlrs) == &ctrlr); + TAILQ_REMOVE(&g_spdk_nvme_driver->shared_attached_ctrlrs, &ctrlr, tailq); + + /* + * Non-PCIe controllers should be added to the per-process list, not the shared list. + */ + memset(&ctrlr, 0, sizeof(struct spdk_nvme_ctrlr)); + ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_RDMA; + TAILQ_INSERT_TAIL(&g_nvme_init_ctrlrs, &ctrlr, tailq); + ctrlr.state = NVME_CTRLR_STATE_READY; + MOCK_SET(nvme_ctrlr_process_init, 0); + rc = nvme_init_controllers(cb_ctx, attach_cb); + CU_ASSERT(rc == 0); + CU_ASSERT(ut_attach_cb_called == true); + CU_ASSERT(TAILQ_EMPTY(&g_nvme_init_ctrlrs)); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_nvme_driver->shared_attached_ctrlrs)); + CU_ASSERT(TAILQ_FIRST(&g_nvme_attached_ctrlrs) == &ctrlr); + TAILQ_REMOVE(&g_nvme_attached_ctrlrs, &ctrlr, tailq); + + g_spdk_nvme_driver = NULL; + pthread_mutexattr_destroy(&attr); + pthread_mutex_destroy(&test_driver.lock); +} + +static void +test_nvme_driver_init(void) +{ + int rc; + struct nvme_driver dummy; + g_spdk_nvme_driver = &dummy; + + /* adjust this so testing doesn't take so long */ + g_nvme_driver_timeout_ms = 100; + + /* process is primary and mem already reserved */ + MOCK_SET(spdk_process_is_primary, true); + dummy.initialized = true; + rc = nvme_driver_init(); + CU_ASSERT(rc == 0); + + /* + * Process is primary and mem not yet reserved but the call + * to spdk_memzone_reserve() returns NULL. + */ + g_spdk_nvme_driver = NULL; + MOCK_SET(spdk_process_is_primary, true); + MOCK_SET(spdk_memzone_reserve, NULL); + rc = nvme_driver_init(); + CU_ASSERT(rc == -1); + + /* process is not primary, no mem already reserved */ + MOCK_SET(spdk_process_is_primary, false); + MOCK_SET(spdk_memzone_lookup, NULL); + g_spdk_nvme_driver = NULL; + rc = nvme_driver_init(); + CU_ASSERT(rc == -1); + + /* process is not primary, mem is already reserved & init'd */ + MOCK_SET(spdk_process_is_primary, false); + MOCK_SET(spdk_memzone_lookup, (void *)&dummy); + dummy.initialized = true; + rc = nvme_driver_init(); + CU_ASSERT(rc == 0); + + /* process is not primary, mem is reserved but not initialized */ + /* and times out */ + MOCK_SET(spdk_process_is_primary, false); + MOCK_SET(spdk_memzone_reserve, (void *)&dummy); + dummy.initialized = false; + rc = nvme_driver_init(); + CU_ASSERT(rc == -1); + + /* process is primary, got mem but mutex won't init */ + MOCK_SET(spdk_process_is_primary, true); + MOCK_SET(spdk_memzone_reserve, (void *)&dummy); + MOCK_SET(pthread_mutexattr_init, -1); + g_spdk_nvme_driver = NULL; + dummy.initialized = true; + rc = nvme_driver_init(); + /* for FreeBSD we can't can't effectively mock this path */ +#ifndef __FreeBSD__ + CU_ASSERT(rc != 0); +#else + CU_ASSERT(rc == 0); +#endif + + /* process is primary, got mem, mutex OK */ + MOCK_SET(spdk_process_is_primary, true); + MOCK_CLEAR(pthread_mutexattr_init); + g_spdk_nvme_driver = NULL; + rc = nvme_driver_init(); + CU_ASSERT(g_spdk_nvme_driver->initialized == false); + CU_ASSERT(TAILQ_EMPTY(&g_nvme_init_ctrlrs)); + CU_ASSERT(TAILQ_EMPTY(&g_spdk_nvme_driver->shared_attached_ctrlrs)); + CU_ASSERT(rc == 0); + + g_spdk_nvme_driver = NULL; + MOCK_CLEAR(spdk_memzone_reserve); + MOCK_CLEAR(spdk_memzone_lookup); +} + +static void +test_spdk_nvme_detach(void) +{ + int rc = 1; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_ctrlr *ret_ctrlr; + struct nvme_driver test_driver; + + memset(&ctrlr, 0, sizeof(ctrlr)); + ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_PCIE; + + g_spdk_nvme_driver = &test_driver; + TAILQ_INIT(&test_driver.shared_attached_ctrlrs); + TAILQ_INSERT_TAIL(&test_driver.shared_attached_ctrlrs, &ctrlr, tailq); + CU_ASSERT(pthread_mutex_init(&test_driver.lock, NULL) == 0); + + /* + * Controllers are ref counted so mock the function that returns + * the ref count so that detach will actually call the destruct + * function which we've mocked simply to verify that it gets + * called (we aren't testing what the real destruct function does + * here.) + */ + MOCK_SET(nvme_ctrlr_get_ref_count, 0); + rc = spdk_nvme_detach(&ctrlr); + ret_ctrlr = TAILQ_FIRST(&test_driver.shared_attached_ctrlrs); + CU_ASSERT(ret_ctrlr == NULL); + CU_ASSERT(ut_destruct_called == true); + CU_ASSERT(rc == 0); + + /* + * Mock the ref count to 1 so we confirm that the destruct + * function is not called and that attached ctrl list is + * not empty. + */ + MOCK_SET(nvme_ctrlr_get_ref_count, 1); + TAILQ_INSERT_TAIL(&test_driver.shared_attached_ctrlrs, &ctrlr, tailq); + ut_destruct_called = false; + rc = spdk_nvme_detach(&ctrlr); + ret_ctrlr = TAILQ_FIRST(&test_driver.shared_attached_ctrlrs); + CU_ASSERT(ret_ctrlr != NULL); + CU_ASSERT(ut_destruct_called == false); + CU_ASSERT(rc == 0); + + /* + * Non-PCIe controllers should be on the per-process attached_ctrlrs list, not the + * shared_attached_ctrlrs list. Test an RDMA controller and ensure it is removed + * from the correct list. + */ + memset(&ctrlr, 0, sizeof(ctrlr)); + ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_RDMA; + TAILQ_INIT(&g_nvme_attached_ctrlrs); + TAILQ_INSERT_TAIL(&g_nvme_attached_ctrlrs, &ctrlr, tailq); + MOCK_SET(nvme_ctrlr_get_ref_count, 0); + rc = spdk_nvme_detach(&ctrlr); + CU_ASSERT(TAILQ_EMPTY(&g_nvme_attached_ctrlrs)); + CU_ASSERT(ut_destruct_called == true); + CU_ASSERT(rc == 0); + + g_spdk_nvme_driver = NULL; + pthread_mutex_destroy(&test_driver.lock); +} + +static void +test_nvme_completion_poll_cb(void) +{ + struct nvme_completion_poll_status status; + struct spdk_nvme_cpl cpl; + + memset(&status, 0x0, sizeof(status)); + memset(&cpl, 0xff, sizeof(cpl)); + + nvme_completion_poll_cb(&status, &cpl); + CU_ASSERT(status.done == true); + CU_ASSERT(memcmp(&cpl, &status.cpl, + sizeof(struct spdk_nvme_cpl)) == 0); +} + +/* stub callback used by test_nvme_user_copy_cmd_complete() */ +static struct spdk_nvme_cpl ut_spdk_nvme_cpl = {0}; +static void +dummy_cb(void *user_cb_arg, struct spdk_nvme_cpl *cpl) +{ + ut_spdk_nvme_cpl = *cpl; +} + +static void +test_nvme_user_copy_cmd_complete(void) +{ + struct nvme_request req; + int test_data = 0xdeadbeef; + int buff_size = sizeof(int); + void *buff; + static struct spdk_nvme_cpl cpl; + + memset(&req, 0, sizeof(req)); + memset(&cpl, 0x5a, sizeof(cpl)); + + /* test without a user buffer provided */ + req.user_cb_fn = (void *)dummy_cb; + nvme_user_copy_cmd_complete(&req, &cpl); + CU_ASSERT(memcmp(&ut_spdk_nvme_cpl, &cpl, sizeof(cpl)) == 0); + + /* test with a user buffer provided */ + req.user_buffer = malloc(buff_size); + SPDK_CU_ASSERT_FATAL(req.user_buffer != NULL); + memset(req.user_buffer, 0, buff_size); + req.payload_size = buff_size; + buff = spdk_dma_zmalloc(buff_size, 0x100, NULL); + SPDK_CU_ASSERT_FATAL(buff != NULL); + req.payload = NVME_PAYLOAD_CONTIG(buff, NULL); + memcpy(buff, &test_data, buff_size); + req.cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE; + req.pid = getpid(); + + /* zero out the test value set in the callback */ + memset(&ut_spdk_nvme_cpl, 0, sizeof(ut_spdk_nvme_cpl)); + + nvme_user_copy_cmd_complete(&req, &cpl); + CU_ASSERT(memcmp(req.user_buffer, &test_data, buff_size) == 0); + CU_ASSERT(memcmp(&ut_spdk_nvme_cpl, &cpl, sizeof(cpl)) == 0); + + /* + * Now test the same path as above but this time choose an opc + * that results in a different data transfer type. + */ + memset(&ut_spdk_nvme_cpl, 0, sizeof(ut_spdk_nvme_cpl)); + memset(req.user_buffer, 0, buff_size); + buff = spdk_dma_zmalloc(buff_size, 0x100, NULL); + SPDK_CU_ASSERT_FATAL(buff != NULL); + req.payload = NVME_PAYLOAD_CONTIG(buff, NULL); + memcpy(buff, &test_data, buff_size); + req.cmd.opc = SPDK_NVME_OPC_SET_FEATURES; + nvme_user_copy_cmd_complete(&req, &cpl); + CU_ASSERT(memcmp(req.user_buffer, &test_data, buff_size) != 0); + CU_ASSERT(memcmp(&ut_spdk_nvme_cpl, &cpl, sizeof(cpl)) == 0); + + /* clean up */ + free(req.user_buffer); +} + +static void +test_nvme_allocate_request_null(void) +{ + struct spdk_nvme_qpair qpair; + spdk_nvme_cmd_cb cb_fn = (spdk_nvme_cmd_cb)0x1234; + void *cb_arg = (void *)0x5678; + struct nvme_request *req = NULL; + struct nvme_request dummy_req; + + STAILQ_INIT(&qpair.free_req); + STAILQ_INIT(&qpair.queued_req); + + /* + * Put a dummy on the queue so we can make a request + * and confirm that what comes back is what we expect. + */ + STAILQ_INSERT_HEAD(&qpair.free_req, &dummy_req, stailq); + + req = nvme_allocate_request_null(&qpair, cb_fn, cb_arg); + + /* + * Compare the req with the parmaters that we passed in + * as well as what the function is supposed to update. + */ + SPDK_CU_ASSERT_FATAL(req != NULL); + CU_ASSERT(req->cb_fn == cb_fn); + CU_ASSERT(req->cb_arg == cb_arg); + CU_ASSERT(req->pid == getpid()); + CU_ASSERT(nvme_payload_type(&req->payload) == NVME_PAYLOAD_TYPE_CONTIG); + CU_ASSERT(req->payload.md == NULL); + CU_ASSERT(req->payload.contig_or_cb_arg == NULL); +} + +static void +test_nvme_allocate_request(void) +{ + struct spdk_nvme_qpair qpair; + struct nvme_payload payload; + uint32_t payload_struct_size = sizeof(payload); + spdk_nvme_cmd_cb cb_fn = (spdk_nvme_cmd_cb)0x1234; + void *cb_arg = (void *)0x6789; + struct nvme_request *req = NULL; + struct nvme_request dummy_req; + + /* Fill the whole payload struct with a known pattern */ + memset(&payload, 0x5a, payload_struct_size); + STAILQ_INIT(&qpair.free_req); + STAILQ_INIT(&qpair.queued_req); + + /* Test trying to allocate a request when no requests are available */ + req = nvme_allocate_request(&qpair, &payload, payload_struct_size, + cb_fn, cb_arg); + CU_ASSERT(req == NULL); + + /* put a dummy on the queue, and then allocate one */ + STAILQ_INSERT_HEAD(&qpair.free_req, &dummy_req, stailq); + req = nvme_allocate_request(&qpair, &payload, payload_struct_size, + cb_fn, cb_arg); + + /* all the req elements should now match the passed in paramters */ + SPDK_CU_ASSERT_FATAL(req != NULL); + CU_ASSERT(req->cb_fn == cb_fn); + CU_ASSERT(req->cb_arg == cb_arg); + CU_ASSERT(memcmp(&req->payload, &payload, payload_struct_size) == 0); + CU_ASSERT(req->payload_size == payload_struct_size); + CU_ASSERT(req->qpair == &qpair); + CU_ASSERT(req->pid == getpid()); +} + +static void +test_nvme_free_request(void) +{ + struct nvme_request match_req; + struct spdk_nvme_qpair qpair; + struct nvme_request *req; + + /* put a req on the Q, take it off and compare */ + memset(&match_req.cmd, 0x5a, sizeof(struct spdk_nvme_cmd)); + match_req.qpair = &qpair; + /* the code under tests asserts this condition */ + match_req.num_children = 0; + STAILQ_INIT(&qpair.free_req); + + nvme_free_request(&match_req); + req = STAILQ_FIRST(&match_req.qpair->free_req); + CU_ASSERT(req == &match_req); +} + +static void +test_nvme_allocate_request_user_copy(void) +{ + struct spdk_nvme_qpair qpair; + spdk_nvme_cmd_cb cb_fn = (spdk_nvme_cmd_cb)0x12345; + void *cb_arg = (void *)0x12345; + bool host_to_controller = true; + struct nvme_request *req; + struct nvme_request dummy_req; + int test_data = 0xdeadbeef; + void *buffer = NULL; + uint32_t payload_size = sizeof(int); + + STAILQ_INIT(&qpair.free_req); + STAILQ_INIT(&qpair.queued_req); + + /* no buffer or valid payload size, early NULL return */ + req = nvme_allocate_request_user_copy(&qpair, buffer, payload_size, cb_fn, + cb_arg, host_to_controller); + CU_ASSERT(req == NULL); + + /* good buffer and valid payload size */ + buffer = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(buffer != NULL); + memcpy(buffer, &test_data, payload_size); + + /* put a dummy on the queue */ + STAILQ_INSERT_HEAD(&qpair.free_req, &dummy_req, stailq); + + MOCK_CLEAR(spdk_malloc) + MOCK_CLEAR(spdk_zmalloc) + req = nvme_allocate_request_user_copy(&qpair, buffer, payload_size, cb_fn, + cb_arg, host_to_controller); + SPDK_CU_ASSERT_FATAL(req != NULL); + CU_ASSERT(req->user_cb_fn == cb_fn); + CU_ASSERT(req->user_cb_arg == cb_arg); + CU_ASSERT(req->user_buffer == buffer); + CU_ASSERT(req->cb_arg == req); + CU_ASSERT(memcmp(req->payload.contig_or_cb_arg, buffer, payload_size) == 0); + spdk_dma_free(req->payload.contig_or_cb_arg); + + /* same thing but additional path coverage, no copy */ + host_to_controller = false; + STAILQ_INSERT_HEAD(&qpair.free_req, &dummy_req, stailq); + + req = nvme_allocate_request_user_copy(&qpair, buffer, payload_size, cb_fn, + cb_arg, host_to_controller); + SPDK_CU_ASSERT_FATAL(req != NULL); + CU_ASSERT(req->user_cb_fn == cb_fn); + CU_ASSERT(req->user_cb_arg == cb_arg); + CU_ASSERT(req->user_buffer == buffer); + CU_ASSERT(req->cb_arg == req); + CU_ASSERT(memcmp(req->payload.contig_or_cb_arg, buffer, payload_size) != 0); + spdk_dma_free(req->payload.contig_or_cb_arg); + + /* good buffer and valid payload size but make spdk_dma_zmalloc fail */ + /* set the mock pointer to NULL for spdk_dma_zmalloc */ + MOCK_SET(spdk_dma_zmalloc, NULL); + req = nvme_allocate_request_user_copy(&qpair, buffer, payload_size, cb_fn, + cb_arg, host_to_controller); + CU_ASSERT(req == NULL); + free(buffer); + MOCK_CLEAR(spdk_dma_zmalloc); +} + +static void +test_nvme_ctrlr_probe(void) +{ + int rc = 0; + struct spdk_nvme_ctrlr ctrlr = {}; + const struct spdk_nvme_transport_id trid = {}; + void *devhandle = NULL; + void *cb_ctx = NULL; + struct spdk_nvme_ctrlr *dummy = NULL; + + /* test when probe_cb returns false */ + MOCK_SET(dummy_probe_cb, false); + rc = nvme_ctrlr_probe(&trid, devhandle, dummy_probe_cb, cb_ctx); + CU_ASSERT(rc == 1); + + /* probe_cb returns true but we can't construct a ctrl */ + MOCK_SET(dummy_probe_cb, true); + MOCK_SET(nvme_transport_ctrlr_construct, NULL); + rc = nvme_ctrlr_probe(&trid, devhandle, dummy_probe_cb, cb_ctx); + CU_ASSERT(rc == -1); + + /* happy path */ + g_spdk_nvme_driver = malloc(sizeof(struct nvme_driver)); + SPDK_CU_ASSERT_FATAL(g_spdk_nvme_driver != NULL); + MOCK_SET(dummy_probe_cb, true); + MOCK_SET(nvme_transport_ctrlr_construct, &ctrlr); + TAILQ_INIT(&g_nvme_init_ctrlrs); + rc = nvme_ctrlr_probe(&trid, devhandle, dummy_probe_cb, cb_ctx); + CU_ASSERT(rc == 0); + dummy = TAILQ_FIRST(&g_nvme_init_ctrlrs); + CU_ASSERT(dummy == ut_nvme_transport_ctrlr_construct); + TAILQ_REMOVE(&g_nvme_init_ctrlrs, dummy, tailq); + MOCK_CLEAR_P(nvme_transport_ctrlr_construct); + + free(g_spdk_nvme_driver); +} + +static void +test_nvme_robust_mutex_init_shared(void) +{ + pthread_mutex_t mtx; + int rc = 0; + + /* test where both pthread calls succeed */ + MOCK_SET(pthread_mutexattr_init, 0); + MOCK_SET(pthread_mutex_init, 0); + rc = nvme_robust_mutex_init_shared(&mtx); + CU_ASSERT(rc == 0); + + /* test where we can't init attr's but init mutex works */ + MOCK_SET(pthread_mutexattr_init, -1); + MOCK_SET(pthread_mutex_init, 0); + rc = nvme_robust_mutex_init_shared(&mtx); + /* for FreeBSD the only possible return value is 0 */ +#ifndef __FreeBSD__ + CU_ASSERT(rc != 0); +#else + CU_ASSERT(rc == 0); +#endif + + /* test where we can init attr's but the mutex init fails */ + MOCK_SET(pthread_mutexattr_init, 0); + MOCK_SET(pthread_mutex_init, -1); + rc = nvme_robust_mutex_init_shared(&mtx); + /* for FreeBSD the only possible return value is 0 */ +#ifndef __FreeBSD__ + CU_ASSERT(rc != 0); +#else + CU_ASSERT(rc == 0); +#endif +} + +static void +test_opc_data_transfer(void) +{ + enum spdk_nvme_data_transfer xfer; + + xfer = spdk_nvme_opc_get_data_transfer(SPDK_NVME_OPC_FLUSH); + CU_ASSERT(xfer == SPDK_NVME_DATA_NONE); + + xfer = spdk_nvme_opc_get_data_transfer(SPDK_NVME_OPC_WRITE); + CU_ASSERT(xfer == SPDK_NVME_DATA_HOST_TO_CONTROLLER); + + xfer = spdk_nvme_opc_get_data_transfer(SPDK_NVME_OPC_READ); + CU_ASSERT(xfer == SPDK_NVME_DATA_CONTROLLER_TO_HOST); + + xfer = spdk_nvme_opc_get_data_transfer(SPDK_NVME_OPC_GET_LOG_PAGE); + CU_ASSERT(xfer == SPDK_NVME_DATA_CONTROLLER_TO_HOST); +} + +static void +test_trid_parse_and_compare(void) +{ + struct spdk_nvme_transport_id trid1, trid2; + int ret; + + /* set trid1 trid2 value to id parse */ + ret = spdk_nvme_transport_id_parse(NULL, "trtype:PCIe traddr:0000:04:00.0"); + CU_ASSERT(ret == -EINVAL); + memset(&trid1, 0, sizeof(trid1)); + ret = spdk_nvme_transport_id_parse(&trid1, NULL); + CU_ASSERT(ret == -EINVAL); + ret = spdk_nvme_transport_id_parse(NULL, NULL); + CU_ASSERT(ret == -EINVAL); + memset(&trid1, 0, sizeof(trid1)); + ret = spdk_nvme_transport_id_parse(&trid1, "trtype-PCIe traddr-0000-04-00.0"); + CU_ASSERT(ret == -EINVAL); + memset(&trid1, 0, sizeof(trid1)); + ret = spdk_nvme_transport_id_parse(&trid1, "trtype-PCIe traddr-0000-04-00.0-:"); + CU_ASSERT(ret == -EINVAL); + memset(&trid1, 0, sizeof(trid1)); + ret = spdk_nvme_transport_id_parse(&trid1, " \t\n:"); + CU_ASSERT(ret == -EINVAL); + memset(&trid1, 0, sizeof(trid1)); + CU_ASSERT(spdk_nvme_transport_id_parse(&trid1, + "trtype:rdma\n" + "adrfam:ipv4\n" + "traddr:192.168.100.8\n" + "trsvcid:4420\n" + "subnqn:nqn.2014-08.org.nvmexpress.discovery") == 0); + CU_ASSERT(trid1.trtype == SPDK_NVME_TRANSPORT_RDMA); + CU_ASSERT(trid1.adrfam == SPDK_NVMF_ADRFAM_IPV4); + CU_ASSERT(strcmp(trid1.traddr, "192.168.100.8") == 0); + CU_ASSERT(strcmp(trid1.trsvcid, "4420") == 0); + CU_ASSERT(strcmp(trid1.subnqn, "nqn.2014-08.org.nvmexpress.discovery") == 0); + + memset(&trid2, 0, sizeof(trid2)); + CU_ASSERT(spdk_nvme_transport_id_parse(&trid2, "trtype:PCIe traddr:0000:04:00.0") == 0); + CU_ASSERT(trid2.trtype == SPDK_NVME_TRANSPORT_PCIE); + CU_ASSERT(strcmp(trid2.traddr, "0000:04:00.0") == 0); + + CU_ASSERT(spdk_nvme_transport_id_compare(&trid1, &trid2) != 0); + + /* set trid1 trid2 and test id_compare */ + memset_trid(&trid1, &trid2); + trid1.adrfam = SPDK_NVMF_ADRFAM_IPV6; + trid2.adrfam = SPDK_NVMF_ADRFAM_IPV4; + ret = spdk_nvme_transport_id_compare(&trid1, &trid2); + CU_ASSERT(ret > 0); + + memset_trid(&trid1, &trid2); + snprintf(trid1.traddr, sizeof(trid1.traddr), "192.168.100.8"); + snprintf(trid2.traddr, sizeof(trid2.traddr), "192.168.100.9"); + ret = spdk_nvme_transport_id_compare(&trid1, &trid2); + CU_ASSERT(ret < 0); + + memset_trid(&trid1, &trid2); + snprintf(trid1.trsvcid, sizeof(trid1.trsvcid), "4420"); + snprintf(trid2.trsvcid, sizeof(trid2.trsvcid), "4421"); + ret = spdk_nvme_transport_id_compare(&trid1, &trid2); + CU_ASSERT(ret < 0); + + memset_trid(&trid1, &trid2); + snprintf(trid1.subnqn, sizeof(trid1.subnqn), "subnqn:nqn.2016-08.org.nvmexpress.discovery"); + snprintf(trid2.subnqn, sizeof(trid2.subnqn), "subnqn:nqn.2017-08.org.nvmexpress.discovery"); + ret = spdk_nvme_transport_id_compare(&trid1, &trid2); + CU_ASSERT(ret < 0); + + memset_trid(&trid1, &trid2); + snprintf(trid1.subnqn, sizeof(trid1.subnqn), "subnqn:nqn.2016-08.org.nvmexpress.discovery"); + snprintf(trid2.subnqn, sizeof(trid2.subnqn), "subnqn:nqn.2016-08.org.nvmexpress.discovery"); + ret = spdk_nvme_transport_id_compare(&trid1, &trid2); + CU_ASSERT(ret == 0); + + memset_trid(&trid1, &trid2); + snprintf(trid1.subnqn, sizeof(trid1.subnqn), "subnqn:nqn.2016-08.org.nvmexpress.discovery"); + snprintf(trid2.subnqn, sizeof(trid2.subnqn), "subnqn:nqn.2016-08.org.Nvmexpress.discovery"); + ret = spdk_nvme_transport_id_compare(&trid1, &trid2); + CU_ASSERT(ret > 0); + + memset_trid(&trid1, &trid2); + ret = spdk_nvme_transport_id_compare(&trid1, &trid2); + CU_ASSERT(ret == 0); + + /* Compare PCI addresses via spdk_pci_addr_compare (rather than as strings) */ + memset_trid(&trid1, &trid2); + CU_ASSERT(spdk_nvme_transport_id_parse(&trid1, "trtype:PCIe traddr:0000:04:00.0") == 0); + CU_ASSERT(spdk_nvme_transport_id_parse(&trid2, "trtype:PCIe traddr:04:00.0") == 0); + CU_ASSERT(spdk_nvme_transport_id_compare(&trid1, &trid2) == 0); + + memset_trid(&trid1, &trid2); + CU_ASSERT(spdk_nvme_transport_id_parse(&trid1, "trtype:PCIe traddr:0000:05:00.0") == 0); + CU_ASSERT(spdk_nvme_transport_id_parse(&trid2, "trtype:PCIe traddr:04:00.0") == 0); + CU_ASSERT(spdk_nvme_transport_id_compare(&trid1, &trid2) > 0); + + memset_trid(&trid1, &trid2); + CU_ASSERT(spdk_nvme_transport_id_parse(&trid1, "trtype:PCIe traddr:0000:04:00.0") == 0); + CU_ASSERT(spdk_nvme_transport_id_parse(&trid2, "trtype:PCIe traddr:05:00.0") == 0); + CU_ASSERT(spdk_nvme_transport_id_compare(&trid1, &trid2) < 0); + + memset_trid(&trid1, &trid2); + CU_ASSERT(spdk_nvme_transport_id_parse(&trid1, "trtype=PCIe traddr=0000:04:00.0") == 0); + CU_ASSERT(spdk_nvme_transport_id_parse(&trid2, "trtype=PCIe traddr=05:00.0") == 0); + CU_ASSERT(spdk_nvme_transport_id_compare(&trid1, &trid2) < 0); +} + +static void +test_spdk_nvme_transport_id_parse_trtype(void) +{ + + enum spdk_nvme_transport_type *trtype; + enum spdk_nvme_transport_type sct; + char *str; + + trtype = NULL; + str = "unit_test"; + + /* test function returned value when trtype is NULL but str not NULL */ + CU_ASSERT(spdk_nvme_transport_id_parse_trtype(trtype, str) == (-EINVAL)); + + /* test function returned value when str is NULL but trtype not NULL */ + trtype = &sct; + str = NULL; + CU_ASSERT(spdk_nvme_transport_id_parse_trtype(trtype, str) == (-EINVAL)); + + /* test function returned value when str and strtype not NULL, but str value + * not "PCIe" or "RDMA" */ + str = "unit_test"; + CU_ASSERT(spdk_nvme_transport_id_parse_trtype(trtype, str) == (-ENOENT)); + + /* test trtype value when use function "strcasecmp" to compare str and "PCIe",not case-sensitive */ + str = "PCIe"; + spdk_nvme_transport_id_parse_trtype(trtype, str); + CU_ASSERT((*trtype) == SPDK_NVME_TRANSPORT_PCIE); + + str = "pciE"; + spdk_nvme_transport_id_parse_trtype(trtype, str); + CU_ASSERT((*trtype) == SPDK_NVME_TRANSPORT_PCIE); + + /* test trtype value when use function "strcasecmp" to compare str and "RDMA",not case-sensitive */ + str = "RDMA"; + spdk_nvme_transport_id_parse_trtype(trtype, str); + CU_ASSERT((*trtype) == SPDK_NVME_TRANSPORT_RDMA); + + str = "rdma"; + spdk_nvme_transport_id_parse_trtype(trtype, str); + CU_ASSERT((*trtype) == SPDK_NVME_TRANSPORT_RDMA); + +} + +static void +test_spdk_nvme_transport_id_parse_adrfam(void) +{ + + enum spdk_nvmf_adrfam *adrfam; + enum spdk_nvmf_adrfam sct; + char *str; + + adrfam = NULL; + str = "unit_test"; + + /* test function returned value when adrfam is NULL but str not NULL */ + CU_ASSERT(spdk_nvme_transport_id_parse_adrfam(adrfam, str) == (-EINVAL)); + + /* test function returned value when str is NULL but adrfam not NULL */ + adrfam = &sct; + str = NULL; + CU_ASSERT(spdk_nvme_transport_id_parse_adrfam(adrfam, str) == (-EINVAL)); + + /* test function returned value when str and adrfam not NULL, but str value + * not "IPv4" or "IPv6" or "IB" or "FC" */ + str = "unit_test"; + CU_ASSERT(spdk_nvme_transport_id_parse_adrfam(adrfam, str) == (-ENOENT)); + + /* test adrfam value when use function "strcasecmp" to compare str and "IPv4",not case-sensitive */ + str = "IPv4"; + spdk_nvme_transport_id_parse_adrfam(adrfam, str); + CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IPV4); + + str = "ipV4"; + spdk_nvme_transport_id_parse_adrfam(adrfam, str); + CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IPV4); + + /* test adrfam value when use function "strcasecmp" to compare str and "IPv6",not case-sensitive */ + str = "IPv6"; + spdk_nvme_transport_id_parse_adrfam(adrfam, str); + CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IPV6); + + str = "ipV6"; + spdk_nvme_transport_id_parse_adrfam(adrfam, str); + CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IPV6); + + /* test adrfam value when use function "strcasecmp" to compare str and "IB",not case-sensitive */ + str = "IB"; + spdk_nvme_transport_id_parse_adrfam(adrfam, str); + CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IB); + + str = "ib"; + spdk_nvme_transport_id_parse_adrfam(adrfam, str); + CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IB); + + /* test adrfam value when use function "strcasecmp" to compare str and "FC",not case-sensitive */ + str = "FC"; + spdk_nvme_transport_id_parse_adrfam(adrfam, str); + CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_FC); + + str = "fc"; + spdk_nvme_transport_id_parse_adrfam(adrfam, str); + CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_FC); + +} + +static void +test_trid_trtype_str(void) +{ + const char *s; + + s = spdk_nvme_transport_id_trtype_str(-5); + CU_ASSERT(s == NULL); + + s = spdk_nvme_transport_id_trtype_str(SPDK_NVME_TRANSPORT_PCIE); + SPDK_CU_ASSERT_FATAL(s != NULL); + CU_ASSERT(strcmp(s, "PCIe") == 0); + + s = spdk_nvme_transport_id_trtype_str(SPDK_NVME_TRANSPORT_RDMA); + SPDK_CU_ASSERT_FATAL(s != NULL); + CU_ASSERT(strcmp(s, "RDMA") == 0); +} + +static void +test_trid_adrfam_str(void) +{ + const char *s; + + s = spdk_nvme_transport_id_adrfam_str(-5); + CU_ASSERT(s == NULL); + + s = spdk_nvme_transport_id_adrfam_str(SPDK_NVMF_ADRFAM_IPV4); + SPDK_CU_ASSERT_FATAL(s != NULL); + CU_ASSERT(strcmp(s, "IPv4") == 0); + + s = spdk_nvme_transport_id_adrfam_str(SPDK_NVMF_ADRFAM_IPV6); + SPDK_CU_ASSERT_FATAL(s != NULL); + CU_ASSERT(strcmp(s, "IPv6") == 0); + + s = spdk_nvme_transport_id_adrfam_str(SPDK_NVMF_ADRFAM_IB); + SPDK_CU_ASSERT_FATAL(s != NULL); + CU_ASSERT(strcmp(s, "IB") == 0); + + s = spdk_nvme_transport_id_adrfam_str(SPDK_NVMF_ADRFAM_FC); + SPDK_CU_ASSERT_FATAL(s != NULL); + CU_ASSERT(strcmp(s, "FC") == 0); +} + +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("nvme", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_opc_data_transfer", + test_opc_data_transfer) == NULL || + CU_add_test(suite, "test_spdk_nvme_transport_id_parse_trtype", + test_spdk_nvme_transport_id_parse_trtype) == NULL || + CU_add_test(suite, "test_spdk_nvme_transport_id_parse_adrfam", + test_spdk_nvme_transport_id_parse_adrfam) == NULL || + CU_add_test(suite, "test_trid_parse_and_compare", + test_trid_parse_and_compare) == NULL || + CU_add_test(suite, "test_trid_trtype_str", + test_trid_trtype_str) == NULL || + CU_add_test(suite, "test_trid_adrfam_str", + test_trid_adrfam_str) == NULL || + CU_add_test(suite, "test_nvme_ctrlr_probe", + test_nvme_ctrlr_probe) == NULL || + CU_add_test(suite, "test_spdk_nvme_probe", + test_spdk_nvme_probe) == NULL || + CU_add_test(suite, "test_spdk_nvme_connect", + test_spdk_nvme_connect) == NULL || + CU_add_test(suite, "test_nvme_init_controllers", + test_nvme_init_controllers) == NULL || + CU_add_test(suite, "test_nvme_driver_init", + test_nvme_driver_init) == NULL || + CU_add_test(suite, "test_spdk_nvme_detach", + test_spdk_nvme_detach) == NULL || + CU_add_test(suite, "test_nvme_completion_poll_cb", + test_nvme_completion_poll_cb) == NULL || + CU_add_test(suite, "test_nvme_user_copy_cmd_complete", + test_nvme_user_copy_cmd_complete) == NULL || + CU_add_test(suite, "test_nvme_allocate_request_null", + test_nvme_allocate_request_null) == NULL || + CU_add_test(suite, "test_nvme_allocate_request", + test_nvme_allocate_request) == NULL || + CU_add_test(suite, "test_nvme_free_request", + test_nvme_free_request) == NULL || + CU_add_test(suite, "test_nvme_allocate_request_user_copy", + test_nvme_allocate_request_user_copy) == NULL || + CU_add_test(suite, "test_nvme_robust_mutex_init_shared", + test_nvme_robust_mutex_init_shared) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/.gitignore new file mode 100644 index 00000000..97a75bee --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/.gitignore @@ -0,0 +1 @@ +nvme_ctrlr_ut diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/Makefile new file mode 100644 index 00000000..3ce33dc4 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.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 = nvme_ctrlr_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/nvme_ctrlr_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/nvme_ctrlr_ut.c new file mode 100644 index 00000000..db7469ff --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/nvme_ctrlr_ut.c @@ -0,0 +1,1795 @@ +/*- + * 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_internal/log.h" + +#include "common/lib/test_env.c" + +struct spdk_trace_flag SPDK_LOG_NVME = { + .name = "nvme", + .enabled = false, +}; + +#include "nvme/nvme_ctrlr.c" +#include "nvme/nvme_quirks.c" + +pid_t g_spdk_nvme_pid; + +struct nvme_driver _g_nvme_driver = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +struct nvme_driver *g_spdk_nvme_driver = &_g_nvme_driver; + +struct spdk_nvme_registers g_ut_nvme_regs = {}; + +__thread int nvme_thread_ioq_index = -1; + +uint32_t set_size = 1; + +int set_status_cpl = -1; + +DEFINE_STUB(nvme_ctrlr_cmd_set_host_id, int, + (struct spdk_nvme_ctrlr *ctrlr, void *host_id, uint32_t host_id_size, + spdk_nvme_cmd_cb cb_fn, void *cb_arg), 0); +DEFINE_STUB(nvme_ctrlr_identify_ns, int, (struct spdk_nvme_ns *ns), 0); +DEFINE_STUB(nvme_ctrlr_identify_id_desc, int, (struct spdk_nvme_ns *ns), 0); +DEFINE_STUB_V(nvme_ns_set_identify_data, (struct spdk_nvme_ns *ns)); + +struct spdk_nvme_ctrlr *nvme_transport_ctrlr_construct(const struct spdk_nvme_transport_id *trid, + const struct spdk_nvme_ctrlr_opts *opts, + void *devhandle) +{ + return NULL; +} + +int +nvme_transport_ctrlr_destruct(struct spdk_nvme_ctrlr *ctrlr) +{ + nvme_ctrlr_destruct_finish(ctrlr); + + return 0; +} + +int +nvme_transport_ctrlr_enable(struct spdk_nvme_ctrlr *ctrlr) +{ + return 0; +} + +int +nvme_transport_ctrlr_set_reg_4(struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, uint32_t value) +{ + SPDK_CU_ASSERT_FATAL(offset <= sizeof(struct spdk_nvme_registers) - 4); + *(uint32_t *)((uintptr_t)&g_ut_nvme_regs + offset) = value; + return 0; +} + +int +nvme_transport_ctrlr_set_reg_8(struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, uint64_t value) +{ + SPDK_CU_ASSERT_FATAL(offset <= sizeof(struct spdk_nvme_registers) - 8); + *(uint64_t *)((uintptr_t)&g_ut_nvme_regs + offset) = value; + return 0; +} + +int +nvme_transport_ctrlr_get_reg_4(struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, uint32_t *value) +{ + SPDK_CU_ASSERT_FATAL(offset <= sizeof(struct spdk_nvme_registers) - 4); + *value = *(uint32_t *)((uintptr_t)&g_ut_nvme_regs + offset); + return 0; +} + +int +nvme_transport_ctrlr_get_reg_8(struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, uint64_t *value) +{ + SPDK_CU_ASSERT_FATAL(offset <= sizeof(struct spdk_nvme_registers) - 8); + *value = *(uint64_t *)((uintptr_t)&g_ut_nvme_regs + offset); + return 0; +} + +uint32_t +nvme_transport_ctrlr_get_max_xfer_size(struct spdk_nvme_ctrlr *ctrlr) +{ + return UINT32_MAX; +} + +uint16_t +nvme_transport_ctrlr_get_max_sges(struct spdk_nvme_ctrlr *ctrlr) +{ + return 1; +} + +void * +nvme_transport_ctrlr_alloc_cmb_io_buffer(struct spdk_nvme_ctrlr *ctrlr, size_t size) +{ + return NULL; +} + +int +nvme_transport_ctrlr_free_cmb_io_buffer(struct spdk_nvme_ctrlr *ctrlr, void *buf, size_t size) +{ + return 0; +} + +struct spdk_nvme_qpair * +nvme_transport_ctrlr_create_io_qpair(struct spdk_nvme_ctrlr *ctrlr, uint16_t qid, + const struct spdk_nvme_io_qpair_opts *opts) +{ + struct spdk_nvme_qpair *qpair; + + qpair = calloc(1, sizeof(*qpair)); + SPDK_CU_ASSERT_FATAL(qpair != NULL); + + qpair->ctrlr = ctrlr; + qpair->id = qid; + qpair->qprio = opts->qprio; + + return qpair; +} + +int +nvme_transport_ctrlr_delete_io_qpair(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_qpair *qpair) +{ + free(qpair); + return 0; +} + +int +nvme_transport_ctrlr_reinit_io_qpair(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_qpair *qpair) +{ + return 0; +} + +int +nvme_transport_qpair_reset(struct spdk_nvme_qpair *qpair) +{ + return 0; +} + +int +nvme_driver_init(void) +{ + return 0; +} + +int nvme_qpair_init(struct spdk_nvme_qpair *qpair, uint16_t id, + struct spdk_nvme_ctrlr *ctrlr, + enum spdk_nvme_qprio qprio, + uint32_t num_requests) +{ + qpair->id = id; + qpair->qprio = qprio; + qpair->ctrlr = ctrlr; + + return 0; +} + +static void +fake_cpl_success(spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + struct spdk_nvme_cpl cpl = {}; + + cpl.status.sc = SPDK_NVME_SC_SUCCESS; + cb_fn(cb_arg, &cpl); +} + +int +spdk_nvme_ctrlr_cmd_set_feature(struct spdk_nvme_ctrlr *ctrlr, uint8_t feature, + uint32_t cdw11, uint32_t cdw12, void *payload, uint32_t payload_size, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + CU_ASSERT(0); + return -1; +} + +int +spdk_nvme_ctrlr_cmd_get_feature(struct spdk_nvme_ctrlr *ctrlr, uint8_t feature, + uint32_t cdw11, void *payload, uint32_t payload_size, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + CU_ASSERT(0); + return -1; +} + +int +spdk_nvme_ctrlr_cmd_get_log_page(struct spdk_nvme_ctrlr *ctrlr, uint8_t log_page, + uint32_t nsid, void *payload, uint32_t payload_size, + uint64_t offset, spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + fake_cpl_success(cb_fn, cb_arg); + return 0; +} + +int +nvme_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_ASYNC_EVENT_REQUEST); + + /* + * For the purposes of this unit test, we don't need to bother emulating request submission. + */ + + return 0; +} + +int32_t +spdk_nvme_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions) +{ + return 0; +} + +void +nvme_qpair_disable(struct spdk_nvme_qpair *qpair) +{ +} + +void +nvme_qpair_enable(struct spdk_nvme_qpair *qpair) +{ +} + +void +nvme_completion_poll_cb(void *arg, const struct spdk_nvme_cpl *cpl) +{ + struct nvme_completion_poll_status *status = arg; + + status->cpl = *cpl; + status->done = true; +} + +int +spdk_nvme_wait_for_completion_robust_lock( + struct spdk_nvme_qpair *qpair, + struct nvme_completion_poll_status *status, + pthread_mutex_t *robust_mutex) +{ + status->done = true; + memset(&status->cpl, 0, sizeof(status->cpl)); + status->cpl.status.sc = 0; + if (set_status_cpl == 1) { + status->cpl.status.sc = 1; + } + return spdk_nvme_cpl_is_error(&status->cpl) ? -EIO : 0; +} + +int +spdk_nvme_wait_for_completion(struct spdk_nvme_qpair *qpair, + struct nvme_completion_poll_status *status) +{ + return spdk_nvme_wait_for_completion_robust_lock(qpair, status, NULL); +} + + +int +nvme_ctrlr_cmd_set_async_event_config(struct spdk_nvme_ctrlr *ctrlr, + union spdk_nvme_feat_async_event_configuration config, spdk_nvme_cmd_cb cb_fn, + void *cb_arg) +{ + fake_cpl_success(cb_fn, cb_arg); + return 0; +} + +int +nvme_ctrlr_cmd_identify(struct spdk_nvme_ctrlr *ctrlr, uint8_t cns, uint16_t cntid, uint32_t nsid, + void *payload, size_t payload_size, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + if (cns == SPDK_NVME_IDENTIFY_ACTIVE_NS_LIST) { + uint32_t count = 0; + uint32_t i = 0; + struct spdk_nvme_ns_list *ns_list = (struct spdk_nvme_ns_list *)payload; + + for (i = 1; i <= ctrlr->num_ns; i++) { + if (i <= nsid) { + continue; + } + + ns_list->ns_list[count++] = i; + if (count == SPDK_COUNTOF(ns_list->ns_list)) { + break; + } + } + + } + fake_cpl_success(cb_fn, cb_arg); + return 0; +} + +int +nvme_ctrlr_cmd_set_num_queues(struct spdk_nvme_ctrlr *ctrlr, + uint32_t num_queues, spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + fake_cpl_success(cb_fn, cb_arg); + return 0; +} + +int +nvme_ctrlr_cmd_get_num_queues(struct spdk_nvme_ctrlr *ctrlr, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + fake_cpl_success(cb_fn, cb_arg); + return 0; +} + +int +nvme_ctrlr_cmd_attach_ns(struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid, + struct spdk_nvme_ctrlr_list *payload, spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + return 0; +} + +int +nvme_ctrlr_cmd_detach_ns(struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid, + struct spdk_nvme_ctrlr_list *payload, spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + return 0; +} + +int +nvme_ctrlr_cmd_create_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns_data *payload, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + return 0; +} + +int +nvme_ctrlr_cmd_delete_ns(struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid, spdk_nvme_cmd_cb cb_fn, + void *cb_arg) +{ + return 0; +} + +int +nvme_ctrlr_cmd_format(struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid, struct spdk_nvme_format *format, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + return 0; +} + +int +nvme_ctrlr_cmd_fw_commit(struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_fw_commit *fw_commit, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + CU_ASSERT(fw_commit->ca == SPDK_NVME_FW_COMMIT_REPLACE_IMG); + if (fw_commit->fs == 0) { + return -1; + } + set_status_cpl = 1; + if (ctrlr->is_resetting == true) { + set_status_cpl = 0; + } + return 0; +} + +int +nvme_ctrlr_cmd_fw_image_download(struct spdk_nvme_ctrlr *ctrlr, + uint32_t size, uint32_t offset, void *payload, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + if ((size != 0 && payload == NULL) || (size == 0 && payload != NULL)) { + return -1; + } + CU_ASSERT(offset == 0); + return 0; +} + +void +nvme_ns_destruct(struct spdk_nvme_ns *ns) +{ +} + +int +nvme_ns_construct(struct spdk_nvme_ns *ns, uint32_t id, + struct spdk_nvme_ctrlr *ctrlr) +{ + return 0; +} + +#define DECLARE_AND_CONSTRUCT_CTRLR() \ + struct spdk_nvme_ctrlr ctrlr = {}; \ + struct spdk_nvme_qpair adminq = {}; \ + struct nvme_request req; \ + \ + STAILQ_INIT(&adminq.free_req); \ + STAILQ_INSERT_HEAD(&adminq.free_req, &req, stailq); \ + ctrlr.adminq = &adminq; + +static void +test_nvme_ctrlr_init_en_1_rdy_0(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs)); + + /* + * Initial state: CC.EN = 1, CSTS.RDY = 0 + */ + g_ut_nvme_regs.cc.bits.en = 1; + g_ut_nvme_regs.csts.bits.rdy = 0; + + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_1); + + /* + * Transition to CSTS.RDY = 1. + * init() should set CC.EN = 0. + */ + g_ut_nvme_regs.csts.bits.rdy = 1; + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0); + + /* + * Transition to CSTS.RDY = 0. + */ + g_ut_nvme_regs.csts.bits.rdy = 0; + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + + /* + * Transition to CC.EN = 1 + */ + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + + /* + * Transition to CSTS.RDY = 1. + */ + g_ut_nvme_regs.csts.bits.rdy = 1; + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE); + + /* + * Transition to READY. + */ + while (ctrlr.state != NVME_CTRLR_STATE_READY) { + nvme_ctrlr_process_init(&ctrlr); + } + + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); +} + +static void +test_nvme_ctrlr_init_en_1_rdy_1(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs)); + + /* + * Initial state: CC.EN = 1, CSTS.RDY = 1 + * init() should set CC.EN = 0. + */ + g_ut_nvme_regs.cc.bits.en = 1; + g_ut_nvme_regs.csts.bits.rdy = 1; + + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0); + + /* + * Transition to CSTS.RDY = 0. + */ + g_ut_nvme_regs.csts.bits.rdy = 0; + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + + /* + * Transition to CC.EN = 1 + */ + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + + /* + * Transition to CSTS.RDY = 1. + */ + g_ut_nvme_regs.csts.bits.rdy = 1; + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE); + + /* + * Transition to READY. + */ + while (ctrlr.state != NVME_CTRLR_STATE_READY) { + nvme_ctrlr_process_init(&ctrlr); + } + + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); +} + +static void +test_nvme_ctrlr_init_en_0_rdy_0_ams_rr(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs)); + + /* + * Initial state: CC.EN = 0, CSTS.RDY = 0 + * init() should set CC.EN = 1. + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Default round robin enabled + */ + g_ut_nvme_regs.cap.bits.ams = 0x0; + ctrlr.cap = g_ut_nvme_regs.cap; + + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + /* + * Case 1: default round robin arbitration mechanism selected + */ + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_RR; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_RR); + CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_RR); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 2: weighted round robin arbitration mechanism selected + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_WRR; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 3: vendor specific arbitration mechanism selected + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 4: invalid arbitration mechanism selected + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS + 1; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 5: reset to default round robin arbitration mechanism + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_RR; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_RR); + CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_RR); + + /* + * Transition to CSTS.RDY = 1. + */ + g_ut_nvme_regs.csts.bits.rdy = 1; + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE); + + /* + * Transition to READY. + */ + while (ctrlr.state != NVME_CTRLR_STATE_READY) { + nvme_ctrlr_process_init(&ctrlr); + } + + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); +} + +static void +test_nvme_ctrlr_init_en_0_rdy_0_ams_wrr(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs)); + + /* + * Initial state: CC.EN = 0, CSTS.RDY = 0 + * init() should set CC.EN = 1. + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Weighted round robin enabled + */ + g_ut_nvme_regs.cap.bits.ams = SPDK_NVME_CAP_AMS_WRR; + ctrlr.cap = g_ut_nvme_regs.cap; + + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + /* + * Case 1: default round robin arbitration mechanism selected + */ + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_RR; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_RR); + CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_RR); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 2: weighted round robin arbitration mechanism selected + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_WRR; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_WRR); + CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_WRR); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 3: vendor specific arbitration mechanism selected + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 4: invalid arbitration mechanism selected + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS + 1; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 5: reset to weighted round robin arbitration mechanism + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_WRR; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_WRR); + CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_WRR); + + /* + * Transition to CSTS.RDY = 1. + */ + g_ut_nvme_regs.csts.bits.rdy = 1; + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE); + + /* + * Transition to READY. + */ + while (ctrlr.state != NVME_CTRLR_STATE_READY) { + nvme_ctrlr_process_init(&ctrlr); + } + + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); +} +static void +test_nvme_ctrlr_init_en_0_rdy_0_ams_vs(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs)); + + /* + * Initial state: CC.EN = 0, CSTS.RDY = 0 + * init() should set CC.EN = 1. + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Default round robin enabled + */ + g_ut_nvme_regs.cap.bits.ams = SPDK_NVME_CAP_AMS_VS; + ctrlr.cap = g_ut_nvme_regs.cap; + + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + /* + * Case 1: default round robin arbitration mechanism selected + */ + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_RR; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_RR); + CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_RR); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 2: weighted round robin arbitration mechanism selected + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_WRR; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 3: vendor specific arbitration mechanism selected + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_VS); + CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_VS); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 4: invalid arbitration mechanism selected + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS + 1; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0); + + /* + * Complete and destroy the controller + */ + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); + + /* + * Reset to initial state + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + /* + * Case 5: reset to vendor specific arbitration mechanism + */ + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS; + + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_VS); + CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_VS); + + /* + * Transition to CSTS.RDY = 1. + */ + g_ut_nvme_regs.csts.bits.rdy = 1; + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE); + + /* + * Transition to READY. + */ + while (ctrlr.state != NVME_CTRLR_STATE_READY) { + nvme_ctrlr_process_init(&ctrlr); + } + + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); +} + +static void +test_nvme_ctrlr_init_en_0_rdy_0(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs)); + + /* + * Initial state: CC.EN = 0, CSTS.RDY = 0 + * init() should set CC.EN = 1. + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 0; + + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + + /* + * Transition to CSTS.RDY = 1. + */ + g_ut_nvme_regs.csts.bits.rdy = 1; + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE); + + /* + * Transition to READY. + */ + while (ctrlr.state != NVME_CTRLR_STATE_READY) { + nvme_ctrlr_process_init(&ctrlr); + } + + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); +} + +static void +test_nvme_ctrlr_init_en_0_rdy_1(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs)); + + /* + * Initial state: CC.EN = 0, CSTS.RDY = 1 + */ + g_ut_nvme_regs.cc.bits.en = 0; + g_ut_nvme_regs.csts.bits.rdy = 1; + + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0); + ctrlr.cdata.nn = 1; + ctrlr.page_size = 0x1000; + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT); + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0); + + /* + * Transition to CSTS.RDY = 0. + */ + g_ut_nvme_regs.csts.bits.rdy = 0; + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE); + + /* + * Transition to CC.EN = 1 + */ + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1); + CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1); + + /* + * Transition to CSTS.RDY = 1. + */ + g_ut_nvme_regs.csts.bits.rdy = 1; + CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0); + CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE); + + /* + * Transition to READY. + */ + while (ctrlr.state != NVME_CTRLR_STATE_READY) { + nvme_ctrlr_process_init(&ctrlr); + } + + g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE; + nvme_ctrlr_destruct(&ctrlr); +} + +static void +setup_qpairs(struct spdk_nvme_ctrlr *ctrlr, uint32_t num_io_queues) +{ + uint32_t i; + + CU_ASSERT(pthread_mutex_init(&ctrlr->ctrlr_lock, NULL) == 0); + + SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(ctrlr) == 0); + + ctrlr->page_size = 0x1000; + ctrlr->opts.num_io_queues = num_io_queues; + ctrlr->free_io_qids = spdk_bit_array_create(num_io_queues + 1); + SPDK_CU_ASSERT_FATAL(ctrlr->free_io_qids != NULL); + + spdk_bit_array_clear(ctrlr->free_io_qids, 0); + for (i = 1; i <= num_io_queues; i++) { + spdk_bit_array_set(ctrlr->free_io_qids, i); + } +} + +static void +cleanup_qpairs(struct spdk_nvme_ctrlr *ctrlr) +{ + nvme_ctrlr_destruct(ctrlr); +} + +static void +test_alloc_io_qpair_rr_1(void) +{ + struct spdk_nvme_io_qpair_opts opts; + struct spdk_nvme_ctrlr ctrlr = {}; + struct spdk_nvme_qpair *q0; + + setup_qpairs(&ctrlr, 1); + + /* + * Fake to simulate the controller with default round robin + * arbitration mechanism. + */ + g_ut_nvme_regs.cc.bits.ams = SPDK_NVME_CC_AMS_RR; + + spdk_nvme_ctrlr_get_default_io_qpair_opts(&ctrlr, &opts, sizeof(opts)); + + q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, NULL, 0); + SPDK_CU_ASSERT_FATAL(q0 != NULL); + SPDK_CU_ASSERT_FATAL(q0->qprio == 0); + /* Only 1 I/O qpair was allocated, so this should fail */ + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, NULL, 0) == NULL); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0); + + /* + * Now that the qpair has been returned to the free list, + * we should be able to allocate it again. + */ + q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, NULL, 0); + SPDK_CU_ASSERT_FATAL(q0 != NULL); + SPDK_CU_ASSERT_FATAL(q0->qprio == 0); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0); + + /* Only 0 qprio is acceptable for default round robin arbitration mechanism */ + opts.qprio = 1; + q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q0 == NULL); + + opts.qprio = 2; + q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q0 == NULL); + + opts.qprio = 3; + q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q0 == NULL); + + /* Only 0 ~ 3 qprio is acceptable */ + opts.qprio = 4; + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)) == NULL); + + cleanup_qpairs(&ctrlr); +} + +static void +test_alloc_io_qpair_wrr_1(void) +{ + struct spdk_nvme_io_qpair_opts opts; + struct spdk_nvme_ctrlr ctrlr = {}; + struct spdk_nvme_qpair *q0, *q1; + + setup_qpairs(&ctrlr, 2); + + /* + * Fake to simulate the controller with weighted round robin + * arbitration mechanism. + */ + g_ut_nvme_regs.cc.bits.ams = SPDK_NVME_CC_AMS_WRR; + + spdk_nvme_ctrlr_get_default_io_qpair_opts(&ctrlr, &opts, sizeof(opts)); + + /* + * Allocate 2 qpairs and free them + */ + opts.qprio = 0; + q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q0 != NULL); + SPDK_CU_ASSERT_FATAL(q0->qprio == 0); + + opts.qprio = 1; + q1 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q1 != NULL); + SPDK_CU_ASSERT_FATAL(q1->qprio == 1); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q1) == 0); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0); + + /* + * Allocate 2 qpairs and free them in the reverse order + */ + opts.qprio = 2; + q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q0 != NULL); + SPDK_CU_ASSERT_FATAL(q0->qprio == 2); + + opts.qprio = 3; + q1 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q1 != NULL); + SPDK_CU_ASSERT_FATAL(q1->qprio == 3); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q1) == 0); + + /* Only 0 ~ 3 qprio is acceptable */ + opts.qprio = 4; + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)) == NULL); + + cleanup_qpairs(&ctrlr); +} + +static void +test_alloc_io_qpair_wrr_2(void) +{ + struct spdk_nvme_io_qpair_opts opts; + struct spdk_nvme_ctrlr ctrlr = {}; + struct spdk_nvme_qpair *q0, *q1, *q2, *q3; + + setup_qpairs(&ctrlr, 4); + + /* + * Fake to simulate the controller with weighted round robin + * arbitration mechanism. + */ + g_ut_nvme_regs.cc.bits.ams = SPDK_NVME_CC_AMS_WRR; + + spdk_nvme_ctrlr_get_default_io_qpair_opts(&ctrlr, &opts, sizeof(opts)); + + opts.qprio = 0; + q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q0 != NULL); + SPDK_CU_ASSERT_FATAL(q0->qprio == 0); + + opts.qprio = 1; + q1 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q1 != NULL); + SPDK_CU_ASSERT_FATAL(q1->qprio == 1); + + opts.qprio = 2; + q2 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q2 != NULL); + SPDK_CU_ASSERT_FATAL(q2->qprio == 2); + + opts.qprio = 3; + q3 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q3 != NULL); + SPDK_CU_ASSERT_FATAL(q3->qprio == 3); + + /* Only 4 I/O qpairs was allocated, so this should fail */ + opts.qprio = 0; + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)) == NULL); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q3) == 0); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q2) == 0); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q1) == 0); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0); + + /* + * Now that the qpair has been returned to the free list, + * we should be able to allocate it again. + * + * Allocate 4 I/O qpairs and half of them with same qprio. + */ + opts.qprio = 1; + q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q0 != NULL); + SPDK_CU_ASSERT_FATAL(q0->qprio == 1); + + opts.qprio = 1; + q1 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q1 != NULL); + SPDK_CU_ASSERT_FATAL(q1->qprio == 1); + + opts.qprio = 3; + q2 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q2 != NULL); + SPDK_CU_ASSERT_FATAL(q2->qprio == 3); + + opts.qprio = 3; + q3 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)); + SPDK_CU_ASSERT_FATAL(q3 != NULL); + SPDK_CU_ASSERT_FATAL(q3->qprio == 3); + + /* + * Free all I/O qpairs in reverse order + */ + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q1) == 0); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q2) == 0); + SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q3) == 0); + + cleanup_qpairs(&ctrlr); +} + +static void +test_nvme_ctrlr_fail(void) +{ + struct spdk_nvme_ctrlr ctrlr = {}; + + ctrlr.opts.num_io_queues = 0; + nvme_ctrlr_fail(&ctrlr, false); + + CU_ASSERT(ctrlr.is_failed == true); +} + +static void +test_nvme_ctrlr_construct_intel_support_log_page_list(void) +{ + bool res; + struct spdk_nvme_ctrlr ctrlr = {}; + struct spdk_nvme_intel_log_page_directory payload = {}; + struct spdk_pci_id pci_id = {}; + + /* Get quirks for a device with all 0 vendor/device id */ + ctrlr.quirks = nvme_get_quirks(&pci_id); + CU_ASSERT(ctrlr.quirks == 0); + + nvme_ctrlr_construct_intel_support_log_page_list(&ctrlr, &payload); + res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_TEMPERATURE); + CU_ASSERT(res == false); + + /* Set the vendor to Intel, but provide no device id */ + ctrlr.cdata.vid = pci_id.vendor_id = SPDK_PCI_VID_INTEL; + payload.temperature_statistics_log_len = 1; + ctrlr.quirks = nvme_get_quirks(&pci_id); + memset(ctrlr.log_page_supported, 0, sizeof(ctrlr.log_page_supported)); + + nvme_ctrlr_construct_intel_support_log_page_list(&ctrlr, &payload); + res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_PAGE_DIRECTORY); + CU_ASSERT(res == true); + res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_TEMPERATURE); + CU_ASSERT(res == true); + res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_READ_CMD_LATENCY); + CU_ASSERT(res == false); + res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_SMART); + CU_ASSERT(res == false); + + /* set valid vendor id, device id and sub device id */ + ctrlr.cdata.vid = SPDK_PCI_VID_INTEL; + payload.temperature_statistics_log_len = 0; + pci_id.vendor_id = SPDK_PCI_VID_INTEL; + pci_id.device_id = 0x0953; + pci_id.subvendor_id = SPDK_PCI_VID_INTEL; + pci_id.subdevice_id = 0x3702; + ctrlr.quirks = nvme_get_quirks(&pci_id); + memset(ctrlr.log_page_supported, 0, sizeof(ctrlr.log_page_supported)); + + nvme_ctrlr_construct_intel_support_log_page_list(&ctrlr, &payload); + res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_PAGE_DIRECTORY); + CU_ASSERT(res == true); + res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_TEMPERATURE); + CU_ASSERT(res == false); + res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_READ_CMD_LATENCY); + CU_ASSERT(res == true); + res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_SMART); + CU_ASSERT(res == false); +} + +static void +test_nvme_ctrlr_set_supported_features(void) +{ + bool res; + struct spdk_nvme_ctrlr ctrlr = {}; + + /* set a invalid vendor id */ + ctrlr.cdata.vid = 0xFFFF; + nvme_ctrlr_set_supported_features(&ctrlr); + res = spdk_nvme_ctrlr_is_feature_supported(&ctrlr, SPDK_NVME_FEAT_ARBITRATION); + CU_ASSERT(res == true); + res = spdk_nvme_ctrlr_is_feature_supported(&ctrlr, SPDK_NVME_INTEL_FEAT_MAX_LBA); + CU_ASSERT(res == false); + + ctrlr.cdata.vid = SPDK_PCI_VID_INTEL; + nvme_ctrlr_set_supported_features(&ctrlr); + res = spdk_nvme_ctrlr_is_feature_supported(&ctrlr, SPDK_NVME_FEAT_ARBITRATION); + CU_ASSERT(res == true); + res = spdk_nvme_ctrlr_is_feature_supported(&ctrlr, SPDK_NVME_INTEL_FEAT_MAX_LBA); + CU_ASSERT(res == true); +} + +static void +test_ctrlr_get_default_ctrlr_opts(void) +{ + struct spdk_nvme_ctrlr_opts opts = {}; + + CU_ASSERT(spdk_uuid_parse(&g_spdk_nvme_driver->default_extended_host_id, + "e53e9258-c93b-48b5-be1a-f025af6d232a") == 0); + + memset(&opts, 0, sizeof(opts)); + + /* set a smaller opts_size */ + CU_ASSERT(sizeof(opts) > 8); + spdk_nvme_ctrlr_get_default_ctrlr_opts(&opts, 8); + CU_ASSERT_EQUAL(opts.num_io_queues, DEFAULT_MAX_IO_QUEUES); + CU_ASSERT_TRUE(opts.use_cmb_sqs); + /* check below fields are not initialized by default value */ + CU_ASSERT_EQUAL(opts.arb_mechanism, 0); + CU_ASSERT_EQUAL(opts.keep_alive_timeout_ms, 0); + CU_ASSERT_EQUAL(opts.io_queue_size, 0); + CU_ASSERT_EQUAL(opts.io_queue_requests, 0); + for (int i = 0; i < 8; i++) { + CU_ASSERT(opts.host_id[i] == 0); + } + for (int i = 0; i < 16; i++) { + CU_ASSERT(opts.extended_host_id[i] == 0); + } + CU_ASSERT(strlen(opts.hostnqn) == 0); + CU_ASSERT(strlen(opts.src_addr) == 0); + CU_ASSERT(strlen(opts.src_svcid) == 0); + + /* set a consistent opts_size */ + spdk_nvme_ctrlr_get_default_ctrlr_opts(&opts, sizeof(opts)); + CU_ASSERT_EQUAL(opts.num_io_queues, DEFAULT_MAX_IO_QUEUES); + CU_ASSERT_TRUE(opts.use_cmb_sqs); + CU_ASSERT_EQUAL(opts.arb_mechanism, SPDK_NVME_CC_AMS_RR); + CU_ASSERT_EQUAL(opts.keep_alive_timeout_ms, 10 * 1000); + CU_ASSERT_EQUAL(opts.io_queue_size, DEFAULT_IO_QUEUE_SIZE); + CU_ASSERT_EQUAL(opts.io_queue_requests, DEFAULT_IO_QUEUE_REQUESTS); + for (int i = 0; i < 8; i++) { + CU_ASSERT(opts.host_id[i] == 0); + } + CU_ASSERT_STRING_EQUAL(opts.hostnqn, + "2014-08.org.nvmexpress:uuid:e53e9258-c93b-48b5-be1a-f025af6d232a"); + CU_ASSERT(memcmp(opts.extended_host_id, &g_spdk_nvme_driver->default_extended_host_id, + sizeof(opts.extended_host_id)) == 0); + CU_ASSERT(strlen(opts.src_addr) == 0); + CU_ASSERT(strlen(opts.src_svcid) == 0); +} + +static void +test_ctrlr_get_default_io_qpair_opts(void) +{ + struct spdk_nvme_ctrlr ctrlr = {}; + struct spdk_nvme_io_qpair_opts opts = {}; + + memset(&opts, 0, sizeof(opts)); + + /* set a smaller opts_size */ + ctrlr.opts.io_queue_size = DEFAULT_IO_QUEUE_SIZE; + CU_ASSERT(sizeof(opts) > 8); + spdk_nvme_ctrlr_get_default_io_qpair_opts(&ctrlr, &opts, 8); + CU_ASSERT_EQUAL(opts.qprio, SPDK_NVME_QPRIO_URGENT); + CU_ASSERT_EQUAL(opts.io_queue_size, DEFAULT_IO_QUEUE_SIZE); + /* check below field is not initialized by default value */ + CU_ASSERT_EQUAL(opts.io_queue_requests, 0); + + /* set a consistent opts_size */ + ctrlr.opts.io_queue_size = DEFAULT_IO_QUEUE_SIZE; + ctrlr.opts.io_queue_requests = DEFAULT_IO_QUEUE_REQUESTS; + spdk_nvme_ctrlr_get_default_io_qpair_opts(&ctrlr, &opts, sizeof(opts)); + CU_ASSERT_EQUAL(opts.qprio, SPDK_NVME_QPRIO_URGENT); + CU_ASSERT_EQUAL(opts.io_queue_size, DEFAULT_IO_QUEUE_SIZE); + CU_ASSERT_EQUAL(opts.io_queue_requests, DEFAULT_IO_QUEUE_REQUESTS); +} + +#if 0 /* TODO: move to PCIe-specific unit test */ +static void +test_nvme_ctrlr_alloc_cmb(void) +{ + int rc; + uint64_t offset; + struct spdk_nvme_ctrlr ctrlr = {}; + + ctrlr.cmb_size = 0x1000000; + ctrlr.cmb_current_offset = 0x100; + rc = nvme_ctrlr_alloc_cmb(&ctrlr, 0x200, 0x1000, &offset); + CU_ASSERT(rc == 0); + CU_ASSERT(offset == 0x1000); + CU_ASSERT(ctrlr.cmb_current_offset == 0x1200); + + rc = nvme_ctrlr_alloc_cmb(&ctrlr, 0x800, 0x1000, &offset); + CU_ASSERT(rc == 0); + CU_ASSERT(offset == 0x2000); + CU_ASSERT(ctrlr.cmb_current_offset == 0x2800); + + rc = nvme_ctrlr_alloc_cmb(&ctrlr, 0x800000, 0x100000, &offset); + CU_ASSERT(rc == 0); + CU_ASSERT(offset == 0x100000); + CU_ASSERT(ctrlr.cmb_current_offset == 0x900000); + + rc = nvme_ctrlr_alloc_cmb(&ctrlr, 0x8000000, 0x1000, &offset); + CU_ASSERT(rc == -1); +} +#endif + +static void +test_spdk_nvme_ctrlr_update_firmware(void) +{ + struct spdk_nvme_ctrlr ctrlr = {}; + void *payload = NULL; + int point_payload = 1; + int slot = 0; + int ret = 0; + struct spdk_nvme_status status; + enum spdk_nvme_fw_commit_action commit_action = SPDK_NVME_FW_COMMIT_REPLACE_IMG; + + /* Set invalid size check function return value */ + set_size = 5; + ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status); + CU_ASSERT(ret == -1); + + /* When payload is NULL but set_size < min_page_size */ + set_size = 4; + ctrlr.min_page_size = 5; + ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status); + CU_ASSERT(ret == -1); + + /* When payload not NULL but min_page_size is 0 */ + set_size = 4; + ctrlr.min_page_size = 0; + payload = &point_payload; + ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status); + CU_ASSERT(ret == -1); + + /* Check firmware image download when payload not NULL and min_page_size not 0 , status.cpl value is 1 */ + set_status_cpl = 1; + set_size = 4; + ctrlr.min_page_size = 5; + payload = &point_payload; + ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status); + CU_ASSERT(ret == -ENXIO); + + /* Check firmware image download and set status.cpl value is 0 */ + set_status_cpl = 0; + set_size = 4; + ctrlr.min_page_size = 5; + payload = &point_payload; + ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status); + CU_ASSERT(ret == -1); + + /* Check firmware commit */ + ctrlr.is_resetting = false; + set_status_cpl = 0; + slot = 1; + set_size = 4; + ctrlr.min_page_size = 5; + payload = &point_payload; + ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status); + CU_ASSERT(ret == -ENXIO); + + /* Set size check firmware download and firmware commit */ + ctrlr.is_resetting = true; + set_status_cpl = 0; + slot = 1; + set_size = 4; + ctrlr.min_page_size = 5; + payload = &point_payload; + ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status); + CU_ASSERT(ret == 0); + + set_status_cpl = 0; +} + +int +nvme_ctrlr_cmd_doorbell_buffer_config(struct spdk_nvme_ctrlr *ctrlr, uint64_t prp1, uint64_t prp2, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + fake_cpl_success(cb_fn, cb_arg); + return 0; +} + +static void +test_spdk_nvme_ctrlr_doorbell_buffer_config(void) +{ + struct spdk_nvme_ctrlr ctrlr = {}; + int ret = -1; + + ctrlr.cdata.oacs.doorbell_buffer_config = 1; + ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_PCIE; + ctrlr.page_size = 0x1000; + MOCK_CLEAR(spdk_malloc) + MOCK_CLEAR(spdk_zmalloc) + MOCK_CLEAR(spdk_dma_malloc) + MOCK_CLEAR(spdk_dma_zmalloc) + ret = nvme_ctrlr_set_doorbell_buffer_config(&ctrlr); + CU_ASSERT(ret == 0); + nvme_ctrlr_free_doorbell_buffer(&ctrlr); +} + +static void +test_nvme_ctrlr_test_active_ns(void) +{ + uint32_t nsid, minor; + size_t ns_id_count; + struct spdk_nvme_ctrlr ctrlr = {}; + + ctrlr.page_size = 0x1000; + + for (minor = 0; minor <= 2; minor++) { + ctrlr.cdata.ver.bits.mjr = 1; + ctrlr.cdata.ver.bits.mnr = minor; + ctrlr.cdata.ver.bits.ter = 0; + ctrlr.num_ns = 1531; + nvme_ctrlr_identify_active_ns(&ctrlr); + + for (nsid = 1; nsid <= ctrlr.num_ns; nsid++) { + CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, nsid) == true); + } + ctrlr.num_ns = 1559; + for (; nsid <= ctrlr.num_ns; nsid++) { + CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, nsid) == false); + } + ctrlr.num_ns = 1531; + for (nsid = 0; nsid < ctrlr.num_ns; nsid++) { + ctrlr.active_ns_list[nsid] = 0; + } + CU_ASSERT(spdk_nvme_ctrlr_get_first_active_ns(&ctrlr) == 0); + + ctrlr.active_ns_list[0] = 1; + CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, 1) == true); + CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, 2) == false); + nsid = spdk_nvme_ctrlr_get_first_active_ns(&ctrlr); + CU_ASSERT(nsid == 1); + + ctrlr.active_ns_list[1] = 3; + CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, 1) == true); + CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, 2) == false); + CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, 3) == true); + nsid = spdk_nvme_ctrlr_get_next_active_ns(&ctrlr, nsid); + CU_ASSERT(nsid == 3); + nsid = spdk_nvme_ctrlr_get_next_active_ns(&ctrlr, nsid); + CU_ASSERT(nsid == 0); + + memset(ctrlr.active_ns_list, 0, ctrlr.num_ns); + for (nsid = 0; nsid < ctrlr.num_ns; nsid++) { + ctrlr.active_ns_list[nsid] = nsid + 1; + } + + ns_id_count = 0; + for (nsid = spdk_nvme_ctrlr_get_first_active_ns(&ctrlr); + nsid != 0; nsid = spdk_nvme_ctrlr_get_next_active_ns(&ctrlr, nsid)) { + CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, nsid) == true); + ns_id_count++; + } + CU_ASSERT(ns_id_count == ctrlr.num_ns); + + nvme_ctrlr_destruct(&ctrlr); + } +} + +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("nvme_ctrlr", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test nvme_ctrlr init CC.EN = 1 CSTS.RDY = 0", + test_nvme_ctrlr_init_en_1_rdy_0) == NULL + || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 1 CSTS.RDY = 1", + test_nvme_ctrlr_init_en_1_rdy_1) == NULL + || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 0 CSTS.RDY = 0", + test_nvme_ctrlr_init_en_0_rdy_0) == NULL + || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 0 CSTS.RDY = 1", + test_nvme_ctrlr_init_en_0_rdy_1) == NULL + || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 0 CSTS.RDY = 0 AMS = RR", + test_nvme_ctrlr_init_en_0_rdy_0_ams_rr) == NULL + || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 0 CSTS.RDY = 0 AMS = WRR", + test_nvme_ctrlr_init_en_0_rdy_0_ams_wrr) == NULL + || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 0 CSTS.RDY = 0 AMS = VS", + test_nvme_ctrlr_init_en_0_rdy_0_ams_vs) == NULL + || CU_add_test(suite, "alloc_io_qpair_rr 1", test_alloc_io_qpair_rr_1) == NULL + || CU_add_test(suite, "get_default_ctrlr_opts", test_ctrlr_get_default_ctrlr_opts) == NULL + || CU_add_test(suite, "get_default_io_qpair_opts", test_ctrlr_get_default_io_qpair_opts) == NULL + || CU_add_test(suite, "alloc_io_qpair_wrr 1", test_alloc_io_qpair_wrr_1) == NULL + || CU_add_test(suite, "alloc_io_qpair_wrr 2", test_alloc_io_qpair_wrr_2) == NULL + || CU_add_test(suite, "test nvme ctrlr function update_firmware", + test_spdk_nvme_ctrlr_update_firmware) == NULL + || CU_add_test(suite, "test nvme_ctrlr function nvme_ctrlr_fail", test_nvme_ctrlr_fail) == NULL + || CU_add_test(suite, "test nvme ctrlr function nvme_ctrlr_construct_intel_support_log_page_list", + test_nvme_ctrlr_construct_intel_support_log_page_list) == NULL + || CU_add_test(suite, "test nvme ctrlr function nvme_ctrlr_set_supported_features", + test_nvme_ctrlr_set_supported_features) == NULL + || CU_add_test(suite, "test nvme ctrlr function nvme_ctrlr_set_doorbell_buffer_config", + test_spdk_nvme_ctrlr_doorbell_buffer_config) == NULL +#if 0 /* TODO: move to PCIe-specific unit test */ + || CU_add_test(suite, "test nvme ctrlr function nvme_ctrlr_alloc_cmb", + test_nvme_ctrlr_alloc_cmb) == NULL +#endif + || CU_add_test(suite, "test nvme ctrlr function test_nvme_ctrlr_test_active_ns", + test_nvme_ctrlr_test_active_ns) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/.gitignore new file mode 100644 index 00000000..1568b476 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/.gitignore @@ -0,0 +1 @@ +nvme_ctrlr_cmd_ut diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/Makefile new file mode 100644 index 00000000..5c647dd3 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.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 = nvme_ctrlr_cmd_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/nvme_ctrlr_cmd_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/nvme_ctrlr_cmd_ut.c new file mode 100644 index 00000000..8cbc4476 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/nvme_ctrlr_cmd_ut.c @@ -0,0 +1,645 @@ + +/*- + * 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_cunit.h" + +#include "nvme/nvme_ctrlr_cmd.c" + +#define CTRLR_CDATA_ELPE 5 + +pid_t g_spdk_nvme_pid; + +struct nvme_request g_req; + +uint32_t error_num_entries; +uint32_t health_log_nsid = 1; +uint8_t feature = 1; +uint32_t feature_cdw11 = 1; +uint32_t feature_cdw12 = 1; +uint8_t get_feature = 1; +uint32_t get_feature_cdw11 = 1; +uint32_t fw_img_size = 1024; +uint32_t fw_img_offset = 0; +uint16_t abort_cid = 1; +uint16_t abort_sqid = 1; +uint32_t namespace_management_nsid = 1; +uint32_t format_nvme_nsid = 1; + +uint32_t expected_feature_ns = 2; +uint32_t expected_feature_cdw10 = SPDK_NVME_FEAT_LBA_RANGE_TYPE; +uint32_t expected_feature_cdw11 = 1; +uint32_t expected_feature_cdw12 = 1; + +typedef void (*verify_request_fn_t)(struct nvme_request *req); +verify_request_fn_t verify_fn; + +static void verify_firmware_log_page(struct nvme_request *req) +{ + uint32_t temp_cdw10; + + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE); + CU_ASSERT(req->cmd.nsid == SPDK_NVME_GLOBAL_NS_TAG); + + temp_cdw10 = ((sizeof(struct spdk_nvme_firmware_page) / sizeof(uint32_t) - 1) << 16) | + SPDK_NVME_LOG_FIRMWARE_SLOT; + CU_ASSERT(req->cmd.cdw10 == temp_cdw10); +} + +static void verify_health_log_page(struct nvme_request *req) +{ + uint32_t temp_cdw10; + + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE); + CU_ASSERT(req->cmd.nsid == health_log_nsid); + + temp_cdw10 = ((sizeof(struct spdk_nvme_health_information_page) / sizeof(uint32_t) - 1) << 16) | + SPDK_NVME_LOG_HEALTH_INFORMATION; + CU_ASSERT(req->cmd.cdw10 == temp_cdw10); +} + +static void verify_error_log_page(struct nvme_request *req) +{ + uint32_t temp_cdw10; + + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE); + CU_ASSERT(req->cmd.nsid == SPDK_NVME_GLOBAL_NS_TAG); + + temp_cdw10 = (((sizeof(struct spdk_nvme_error_information_entry) * error_num_entries) / + sizeof(uint32_t) - 1) << 16) | SPDK_NVME_LOG_ERROR; + CU_ASSERT(req->cmd.cdw10 == temp_cdw10); +} + +static void verify_set_feature_cmd(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_SET_FEATURES); + CU_ASSERT(req->cmd.cdw10 == feature); + CU_ASSERT(req->cmd.cdw11 == feature_cdw11); + CU_ASSERT(req->cmd.cdw12 == feature_cdw12); +} + +static void verify_set_feature_ns_cmd(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_SET_FEATURES); + CU_ASSERT(req->cmd.cdw10 == expected_feature_cdw10); + CU_ASSERT(req->cmd.cdw11 == expected_feature_cdw11); + CU_ASSERT(req->cmd.cdw12 == expected_feature_cdw12); + CU_ASSERT(req->cmd.nsid == expected_feature_ns); +} + +static void verify_get_feature_cmd(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_FEATURES); + CU_ASSERT(req->cmd.cdw10 == get_feature); + CU_ASSERT(req->cmd.cdw11 == get_feature_cdw11); +} + +static void verify_get_feature_ns_cmd(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_FEATURES); + CU_ASSERT(req->cmd.cdw10 == expected_feature_cdw10); + CU_ASSERT(req->cmd.cdw11 == expected_feature_cdw11); + CU_ASSERT(req->cmd.nsid == expected_feature_ns); +} + +static void verify_abort_cmd(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_ABORT); + CU_ASSERT(req->cmd.cdw10 == (((uint32_t)abort_cid << 16) | abort_sqid)); +} + +static void verify_io_raw_cmd(struct nvme_request *req) +{ + struct spdk_nvme_cmd command = {}; + + CU_ASSERT(memcmp(&req->cmd, &command, sizeof(req->cmd)) == 0); +} + +static void verify_io_raw_cmd_with_md(struct nvme_request *req) +{ + struct spdk_nvme_cmd command = {}; + + CU_ASSERT(memcmp(&req->cmd, &command, sizeof(req->cmd)) == 0); +} + +static void verify_intel_smart_log_page(struct nvme_request *req) +{ + uint32_t temp_cdw10; + + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE); + CU_ASSERT(req->cmd.nsid == health_log_nsid); + + temp_cdw10 = ((sizeof(struct spdk_nvme_intel_smart_information_page) / + sizeof(uint32_t) - 1) << 16) | + SPDK_NVME_INTEL_LOG_SMART; + CU_ASSERT(req->cmd.cdw10 == temp_cdw10); +} + +static void verify_intel_temperature_log_page(struct nvme_request *req) +{ + uint32_t temp_cdw10; + + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE); + + temp_cdw10 = ((sizeof(struct spdk_nvme_intel_temperature_page) / sizeof(uint32_t) - 1) << 16) | + SPDK_NVME_INTEL_LOG_TEMPERATURE; + CU_ASSERT(req->cmd.cdw10 == temp_cdw10); +} + +static void verify_intel_read_latency_log_page(struct nvme_request *req) +{ + uint32_t temp_cdw10; + + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE); + + temp_cdw10 = ((sizeof(struct spdk_nvme_intel_rw_latency_page) / sizeof(uint32_t) - 1) << 16) | + SPDK_NVME_INTEL_LOG_READ_CMD_LATENCY; + CU_ASSERT(req->cmd.cdw10 == temp_cdw10); +} + +static void verify_intel_write_latency_log_page(struct nvme_request *req) +{ + uint32_t temp_cdw10; + + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE); + + temp_cdw10 = ((sizeof(struct spdk_nvme_intel_rw_latency_page) / sizeof(uint32_t) - 1) << 16) | + SPDK_NVME_INTEL_LOG_WRITE_CMD_LATENCY; + CU_ASSERT(req->cmd.cdw10 == temp_cdw10); +} + +static void verify_intel_get_log_page_directory(struct nvme_request *req) +{ + uint32_t temp_cdw10; + + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE); + + temp_cdw10 = ((sizeof(struct spdk_nvme_intel_log_page_directory) / sizeof(uint32_t) - 1) << 16) | + SPDK_NVME_INTEL_LOG_PAGE_DIRECTORY; + CU_ASSERT(req->cmd.cdw10 == temp_cdw10); +} + +static void verify_intel_marketing_description_log_page(struct nvme_request *req) +{ + uint32_t temp_cdw10; + + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE); + + temp_cdw10 = ((sizeof(struct spdk_nvme_intel_marketing_description_page) / sizeof( + uint32_t) - 1) << 16) | + SPDK_NVME_INTEL_MARKETING_DESCRIPTION; + CU_ASSERT(req->cmd.cdw10 == temp_cdw10); +} + +static void verify_namespace_attach(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_NS_ATTACHMENT); + CU_ASSERT(req->cmd.cdw10 == SPDK_NVME_NS_CTRLR_ATTACH); + CU_ASSERT(req->cmd.nsid == namespace_management_nsid); +} + +static void verify_namespace_detach(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_NS_ATTACHMENT); + CU_ASSERT(req->cmd.cdw10 == SPDK_NVME_NS_CTRLR_DETACH); + CU_ASSERT(req->cmd.nsid == namespace_management_nsid); +} + +static void verify_namespace_create(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_NS_MANAGEMENT); + CU_ASSERT(req->cmd.cdw10 == SPDK_NVME_NS_MANAGEMENT_CREATE); + CU_ASSERT(req->cmd.nsid == 0); +} + +static void verify_namespace_delete(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_NS_MANAGEMENT); + CU_ASSERT(req->cmd.cdw10 == SPDK_NVME_NS_MANAGEMENT_DELETE); + CU_ASSERT(req->cmd.nsid == namespace_management_nsid); +} + +static void verify_format_nvme(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_FORMAT_NVM); + CU_ASSERT(req->cmd.cdw10 == 0); + CU_ASSERT(req->cmd.nsid == format_nvme_nsid); +} + +static void verify_fw_commit(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_FIRMWARE_COMMIT); + CU_ASSERT(req->cmd.cdw10 == 0x09); +} + +static void verify_fw_image_download(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_FIRMWARE_IMAGE_DOWNLOAD); + CU_ASSERT(req->cmd.cdw10 == (fw_img_size >> 2) - 1); + CU_ASSERT(req->cmd.cdw11 == fw_img_offset >> 2); +} + +struct nvme_request * +nvme_allocate_request_user_copy(struct spdk_nvme_qpair *qpair, void *buffer, uint32_t payload_size, + spdk_nvme_cmd_cb cb_fn, void *cb_arg, bool host_to_controller) +{ + /* For the unit test, we don't actually need to copy the buffer */ + return nvme_allocate_request_contig(qpair, buffer, payload_size, cb_fn, cb_arg); +} + +int +nvme_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req) +{ + verify_fn(req); + /* stop analyzer from thinking stack variable addresses are stored in a global */ + memset(req, 0, sizeof(*req)); + + return 0; +} + +int +nvme_ctrlr_submit_admin_request(struct spdk_nvme_ctrlr *ctrlr, struct nvme_request *req) +{ + verify_fn(req); + /* stop analyzer from thinking stack variable addresses are stored in a global */ + memset(req, 0, sizeof(*req)); + + return 0; +} + +#define DECLARE_AND_CONSTRUCT_CTRLR() \ + struct spdk_nvme_ctrlr ctrlr = {}; \ + struct spdk_nvme_qpair adminq = {}; \ + struct nvme_request req; \ + \ + STAILQ_INIT(&adminq.free_req); \ + STAILQ_INSERT_HEAD(&adminq.free_req, &req, stailq); \ + ctrlr.adminq = &adminq; + +static void +test_firmware_get_log_page(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_firmware_page payload = {}; + + verify_fn = verify_firmware_log_page; + + spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_LOG_FIRMWARE_SLOT, SPDK_NVME_GLOBAL_NS_TAG, + &payload, + sizeof(payload), 0, NULL, NULL); +} + +static void +test_health_get_log_page(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_health_information_page payload = {}; + + verify_fn = verify_health_log_page; + + spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_LOG_HEALTH_INFORMATION, health_log_nsid, + &payload, + sizeof(payload), 0, NULL, NULL); +} + +static void +test_error_get_log_page(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_error_information_entry payload = {}; + + ctrlr.cdata.elpe = CTRLR_CDATA_ELPE; + + verify_fn = verify_error_log_page; + + /* valid page */ + error_num_entries = 1; + spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_LOG_ERROR, SPDK_NVME_GLOBAL_NS_TAG, &payload, + sizeof(payload), 0, NULL, NULL); +} + +static void test_intel_smart_get_log_page(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_intel_smart_information_page payload = {}; + + verify_fn = verify_intel_smart_log_page; + + spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_LOG_SMART, health_log_nsid, &payload, + sizeof(payload), 0, NULL, NULL); +} + +static void test_intel_temperature_get_log_page(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_intel_temperature_page payload = {}; + + verify_fn = verify_intel_temperature_log_page; + + spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_LOG_TEMPERATURE, SPDK_NVME_GLOBAL_NS_TAG, + &payload, sizeof(payload), 0, NULL, NULL); +} + +static void test_intel_read_latency_get_log_page(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_intel_rw_latency_page payload = {}; + + verify_fn = verify_intel_read_latency_log_page; + + spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_LOG_READ_CMD_LATENCY, + SPDK_NVME_GLOBAL_NS_TAG, + &payload, sizeof(payload), 0, NULL, NULL); +} + +static void test_intel_write_latency_get_log_page(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_intel_rw_latency_page payload = {}; + + verify_fn = verify_intel_write_latency_log_page; + + spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_LOG_WRITE_CMD_LATENCY, + SPDK_NVME_GLOBAL_NS_TAG, + &payload, sizeof(payload), 0, NULL, NULL); +} + +static void test_intel_get_log_page_directory(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_intel_log_page_directory payload = {}; + + verify_fn = verify_intel_get_log_page_directory; + + spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_LOG_PAGE_DIRECTORY, + SPDK_NVME_GLOBAL_NS_TAG, + &payload, sizeof(payload), 0, NULL, NULL); +} + +static void test_intel_marketing_description_get_log_page(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_intel_marketing_description_page payload = {}; + + verify_fn = verify_intel_marketing_description_log_page; + + spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_MARKETING_DESCRIPTION, + SPDK_NVME_GLOBAL_NS_TAG, + &payload, sizeof(payload), 0, NULL, NULL); +} + +static void test_generic_get_log_pages(void) +{ + test_error_get_log_page(); + test_health_get_log_page(); + test_firmware_get_log_page(); +} + +static void test_intel_get_log_pages(void) +{ + test_intel_get_log_page_directory(); + test_intel_smart_get_log_page(); + test_intel_temperature_get_log_page(); + test_intel_read_latency_get_log_page(); + test_intel_write_latency_get_log_page(); + test_intel_marketing_description_get_log_page(); +} + +static void +test_set_feature_cmd(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + verify_fn = verify_set_feature_cmd; + + spdk_nvme_ctrlr_cmd_set_feature(&ctrlr, feature, feature_cdw11, feature_cdw12, NULL, 0, NULL, NULL); +} + +static void +test_get_feature_ns_cmd(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + verify_fn = verify_get_feature_ns_cmd; + + spdk_nvme_ctrlr_cmd_get_feature_ns(&ctrlr, expected_feature_cdw10, + expected_feature_cdw11, NULL, 0, + NULL, NULL, expected_feature_ns); +} + +static void +test_set_feature_ns_cmd(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + verify_fn = verify_set_feature_ns_cmd; + + spdk_nvme_ctrlr_cmd_set_feature_ns(&ctrlr, expected_feature_cdw10, + expected_feature_cdw11, expected_feature_cdw12, + NULL, 0, NULL, NULL, expected_feature_ns); +} + +static void +test_get_feature_cmd(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + verify_fn = verify_get_feature_cmd; + + spdk_nvme_ctrlr_cmd_get_feature(&ctrlr, get_feature, get_feature_cdw11, NULL, 0, NULL, NULL); +} + +static void +test_abort_cmd(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_qpair qpair = {}; + + STAILQ_INIT(&ctrlr.queued_aborts); + + verify_fn = verify_abort_cmd; + + qpair.id = abort_sqid; + spdk_nvme_ctrlr_cmd_abort(&ctrlr, &qpair, abort_cid, NULL, NULL); +} + +static void +test_io_raw_cmd(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_qpair qpair = {}; + struct spdk_nvme_cmd cmd = {}; + + verify_fn = verify_io_raw_cmd; + + spdk_nvme_ctrlr_cmd_io_raw(&ctrlr, &qpair, &cmd, NULL, 1, NULL, NULL); +} + +static void +test_io_raw_cmd_with_md(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_qpair qpair = {}; + struct spdk_nvme_cmd cmd = {}; + + verify_fn = verify_io_raw_cmd_with_md; + + spdk_nvme_ctrlr_cmd_io_raw_with_md(&ctrlr, &qpair, &cmd, NULL, 1, NULL, NULL, NULL); +} + +static void +test_get_log_pages(void) +{ + test_generic_get_log_pages(); + test_intel_get_log_pages(); +} + +static void +test_namespace_attach(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_ctrlr_list payload = {}; + + verify_fn = verify_namespace_attach; + + nvme_ctrlr_cmd_attach_ns(&ctrlr, namespace_management_nsid, &payload, NULL, NULL); +} + +static void +test_namespace_detach(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_ctrlr_list payload = {}; + + verify_fn = verify_namespace_detach; + + nvme_ctrlr_cmd_detach_ns(&ctrlr, namespace_management_nsid, &payload, NULL, NULL); +} + +static void +test_namespace_create(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_ns_data payload = {}; + + verify_fn = verify_namespace_create; + nvme_ctrlr_cmd_create_ns(&ctrlr, &payload, NULL, NULL); +} + +static void +test_namespace_delete(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + verify_fn = verify_namespace_delete; + nvme_ctrlr_cmd_delete_ns(&ctrlr, namespace_management_nsid, NULL, NULL); +} + +static void +test_format_nvme(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_format format = {}; + + verify_fn = verify_format_nvme; + + nvme_ctrlr_cmd_format(&ctrlr, format_nvme_nsid, &format, NULL, NULL); +} + +static void +test_fw_commit(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + struct spdk_nvme_fw_commit fw_commit = {}; + + fw_commit.ca = SPDK_NVME_FW_COMMIT_REPLACE_AND_ENABLE_IMG; + fw_commit.fs = 1; + + verify_fn = verify_fw_commit; + + nvme_ctrlr_cmd_fw_commit(&ctrlr, &fw_commit, NULL, NULL); +} + +static void +test_fw_image_download(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + verify_fn = verify_fw_image_download; + + nvme_ctrlr_cmd_fw_image_download(&ctrlr, fw_img_size, fw_img_offset, NULL, + NULL, NULL); +} + +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("nvme_ctrlr_cmd", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test ctrlr cmd get_log_pages", test_get_log_pages) == NULL + || CU_add_test(suite, "test ctrlr cmd set_feature", test_set_feature_cmd) == NULL + || CU_add_test(suite, "test ctrlr cmd set_feature_ns", test_set_feature_ns_cmd) == NULL + || CU_add_test(suite, "test ctrlr cmd get_feature", test_get_feature_cmd) == NULL + || CU_add_test(suite, "test ctrlr cmd get_feature_ns", test_get_feature_ns_cmd) == NULL + || CU_add_test(suite, "test ctrlr cmd abort_cmd", test_abort_cmd) == NULL + || CU_add_test(suite, "test ctrlr cmd io_raw_cmd", test_io_raw_cmd) == NULL + || CU_add_test(suite, "test ctrlr cmd io_raw_cmd_with_md", test_io_raw_cmd_with_md) == NULL + || CU_add_test(suite, "test ctrlr cmd namespace_attach", test_namespace_attach) == NULL + || CU_add_test(suite, "test ctrlr cmd namespace_detach", test_namespace_detach) == NULL + || CU_add_test(suite, "test ctrlr cmd namespace_create", test_namespace_create) == NULL + || CU_add_test(suite, "test ctrlr cmd namespace_delete", test_namespace_delete) == NULL + || CU_add_test(suite, "test ctrlr cmd format_nvme", test_format_nvme) == NULL + || CU_add_test(suite, "test ctrlr cmd fw_commit", test_fw_commit) == NULL + || CU_add_test(suite, "test ctrlr cmd fw_image_download", test_fw_image_download) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/.gitignore new file mode 100644 index 00000000..2813105d --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/.gitignore @@ -0,0 +1 @@ +nvme_ctrlr_ocssd_cmd_ut diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/Makefile new file mode 100644 index 00000000..9446b8d5 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.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 = nvme_ctrlr_ocssd_cmd_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/nvme_ctrlr_ocssd_cmd_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/nvme_ctrlr_ocssd_cmd_ut.c new file mode 100644 index 00000000..98eccf34 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/nvme_ctrlr_ocssd_cmd_ut.c @@ -0,0 +1,116 @@ +/*- + * 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_cunit.h" + +#include "nvme/nvme_ctrlr_ocssd_cmd.c" + +#define DECLARE_AND_CONSTRUCT_CTRLR() \ + struct spdk_nvme_ctrlr ctrlr = {}; \ + struct spdk_nvme_qpair adminq = {}; \ + struct nvme_request req; \ + \ + STAILQ_INIT(&adminq.free_req); \ + STAILQ_INSERT_HEAD(&adminq.free_req, &req, stailq); \ + ctrlr.adminq = &adminq; + +pid_t g_spdk_nvme_pid; +struct nvme_request g_req; +typedef void (*verify_request_fn_t)(struct nvme_request *req); +verify_request_fn_t verify_fn; + +static const uint32_t expected_geometry_ns = 1; + +int +nvme_ctrlr_submit_admin_request(struct spdk_nvme_ctrlr *ctrlr, struct nvme_request *req) +{ + verify_fn(req); + memset(req, 0, sizeof(*req)); + return 0; +} + +struct nvme_request * +nvme_allocate_request_user_copy(struct spdk_nvme_qpair *qpair, void *buffer, uint32_t payload_size, + spdk_nvme_cmd_cb cb_fn, void *cb_arg, bool host_to_controller) +{ + /* For the unit test, we don't actually need to copy the buffer */ + return nvme_allocate_request_contig(qpair, buffer, payload_size, cb_fn, cb_arg); +} + +static void verify_geometry_cmd(struct nvme_request *req) +{ + CU_ASSERT(req->cmd.opc == SPDK_OCSSD_OPC_GEOMETRY); + CU_ASSERT(req->cmd.nsid == expected_geometry_ns); +} + +static void +test_geometry_cmd(void) +{ + DECLARE_AND_CONSTRUCT_CTRLR(); + + struct spdk_ocssd_geometry_data geo; + + verify_fn = verify_geometry_cmd; + + spdk_nvme_ocssd_ctrlr_cmd_geometry(&ctrlr, expected_geometry_ns, &geo, + sizeof(geo), NULL, NULL); +} + +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("nvme_ctrlr_cmd", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test ocssd ctrlr geometry cmd ", test_geometry_cmd) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ns.c/.gitignore new file mode 100644 index 00000000..ada0ec86 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ns.c/.gitignore @@ -0,0 +1 @@ +nvme_ns_ut diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ns.c/Makefile new file mode 100644 index 00000000..add85ee9 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ns.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 = nvme_ns_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns.c/nvme_ns_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ns.c/nvme_ns_ut.c new file mode 100644 index 00000000..cdfb4951 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ns.c/nvme_ns_ut.c @@ -0,0 +1,163 @@ +/*- + * 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_cunit.h" + +#include "spdk/env.h" + +#include "nvme/nvme_ns.c" + +#include "common/lib/test_env.c" + +SPDK_LOG_REGISTER_COMPONENT("nvme", SPDK_LOG_NVME) + +DEFINE_STUB(spdk_nvme_wait_for_completion_robust_lock, int, + (struct spdk_nvme_qpair *qpair, + struct nvme_completion_poll_status *status, + pthread_mutex_t *robust_mutex), 0); + +int +nvme_ctrlr_cmd_identify(struct spdk_nvme_ctrlr *ctrlr, uint8_t cns, uint16_t cntid, uint32_t nsid, + void *payload, size_t payload_size, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + return -1; +} + +void +nvme_completion_poll_cb(void *arg, const struct spdk_nvme_cpl *cpl) +{ +} + +int32_t +spdk_nvme_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions) +{ + return -1; +} + +static void +test_nvme_ns_construct(void) +{ + struct spdk_nvme_ns ns = {}; + uint32_t id = 1; + struct spdk_nvme_ctrlr ctrlr = {}; + + nvme_ns_construct(&ns, id, &ctrlr); + CU_ASSERT(ns.id == 1); +} + +static void +test_nvme_ns_uuid(void) +{ + struct spdk_nvme_ns ns = {}; + const struct spdk_uuid *uuid; + struct spdk_uuid expected_uuid; + + memset(&expected_uuid, 0xA5, sizeof(expected_uuid)); + + /* Empty list - no UUID should be found */ + memset(ns.id_desc_list, 0, sizeof(ns.id_desc_list)); + uuid = spdk_nvme_ns_get_uuid(&ns); + CU_ASSERT(uuid == NULL); + + /* NGUID only (no UUID in list) */ + memset(ns.id_desc_list, 0, sizeof(ns.id_desc_list)); + ns.id_desc_list[0] = 0x02; /* NIDT == NGUID */ + ns.id_desc_list[1] = 0x10; /* NIDL */ + memset(&ns.id_desc_list[4], 0xCC, 0x10); + uuid = spdk_nvme_ns_get_uuid(&ns); + CU_ASSERT(uuid == NULL); + + /* Just UUID in the list */ + memset(ns.id_desc_list, 0, sizeof(ns.id_desc_list)); + ns.id_desc_list[0] = 0x03; /* NIDT == UUID */ + ns.id_desc_list[1] = 0x10; /* NIDL */ + memcpy(&ns.id_desc_list[4], &expected_uuid, sizeof(expected_uuid)); + uuid = spdk_nvme_ns_get_uuid(&ns); + SPDK_CU_ASSERT_FATAL(uuid != NULL); + CU_ASSERT(memcmp(uuid, &expected_uuid, sizeof(*uuid)) == 0); + + /* UUID followed by NGUID */ + memset(ns.id_desc_list, 0, sizeof(ns.id_desc_list)); + ns.id_desc_list[0] = 0x03; /* NIDT == UUID */ + ns.id_desc_list[1] = 0x10; /* NIDL */ + memcpy(&ns.id_desc_list[4], &expected_uuid, sizeof(expected_uuid)); + ns.id_desc_list[20] = 0x02; /* NIDT == NGUID */ + ns.id_desc_list[21] = 0x10; /* NIDL */ + memset(&ns.id_desc_list[24], 0xCC, 0x10); + uuid = spdk_nvme_ns_get_uuid(&ns); + SPDK_CU_ASSERT_FATAL(uuid != NULL); + CU_ASSERT(memcmp(uuid, &expected_uuid, sizeof(*uuid)) == 0); + + /* NGUID followed by UUID */ + memset(ns.id_desc_list, 0, sizeof(ns.id_desc_list)); + ns.id_desc_list[0] = 0x02; /* NIDT == NGUID */ + ns.id_desc_list[1] = 0x10; /* NIDL */ + memset(&ns.id_desc_list[4], 0xCC, 0x10); + ns.id_desc_list[20] = 0x03; /* NIDT = UUID */ + ns.id_desc_list[21] = 0x10; /* NIDL */ + memcpy(&ns.id_desc_list[24], &expected_uuid, sizeof(expected_uuid)); + uuid = spdk_nvme_ns_get_uuid(&ns); + SPDK_CU_ASSERT_FATAL(uuid != NULL); + CU_ASSERT(memcmp(uuid, &expected_uuid, sizeof(*uuid)) == 0); +} + +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("nvme", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_nvme_ns", test_nvme_ns_construct) == NULL || + CU_add_test(suite, "test_nvme_ns_uuid", test_nvme_ns_uuid) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/.gitignore new file mode 100644 index 00000000..5583ec23 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/.gitignore @@ -0,0 +1 @@ +nvme_ns_cmd_ut diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/Makefile new file mode 100644 index 00000000..ff451d72 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.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 = nvme_ns_cmd_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/nvme_ns_cmd_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/nvme_ns_cmd_ut.c new file mode 100644 index 00000000..f17ffa35 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/nvme_ns_cmd_ut.c @@ -0,0 +1,1440 @@ +/*- + * 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_cunit.h" + +#include "nvme/nvme_ns_cmd.c" +#include "nvme/nvme.c" + +#include "common/lib/test_env.c" + +DEFINE_STUB(spdk_nvme_qpair_process_completions, int32_t, + (struct spdk_nvme_qpair *qpair, + uint32_t max_completions), 0); + +static struct nvme_driver _g_nvme_driver = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +static struct nvme_request *g_request = NULL; + +int +spdk_pci_nvme_enumerate(spdk_pci_enum_cb enum_cb, void *enum_ctx) +{ + return -1; +} + +static void nvme_request_reset_sgl(void *cb_arg, uint32_t sgl_offset) +{ +} + +static int nvme_request_next_sge(void *cb_arg, void **address, uint32_t *length) +{ + uint32_t *lba_count = cb_arg; + + /* + * We need to set address to something here, since the SGL splitting code will + * use it to determine PRP compatibility. Just use a rather arbitrary address + * for now - these tests will not actually cause data to be read from or written + * to this address. + */ + *address = (void *)(uintptr_t)0x10000000; + *length = *lba_count; + return 0; +} + +bool +spdk_nvme_transport_available(enum spdk_nvme_transport_type trtype) +{ + return true; +} + +struct spdk_nvme_ctrlr *nvme_transport_ctrlr_construct(const struct spdk_nvme_transport_id *trid, + const struct spdk_nvme_ctrlr_opts *opts, + void *devhandle) +{ + return NULL; +} + +void +nvme_ctrlr_destruct(struct spdk_nvme_ctrlr *ctrlr) +{ +} + +int +nvme_ctrlr_add_process(struct spdk_nvme_ctrlr *ctrlr, void *devhandle) +{ + return 0; +} + +int +nvme_ctrlr_process_init(struct spdk_nvme_ctrlr *ctrlr) +{ + return 0; +} + +void +nvme_ctrlr_fail(struct spdk_nvme_ctrlr *ctrlr, bool hot_remove) +{ +} + +struct spdk_pci_addr +spdk_pci_device_get_addr(struct spdk_pci_device *pci_dev) +{ + struct spdk_pci_addr pci_addr; + + memset(&pci_addr, 0, sizeof(pci_addr)); + return pci_addr; +} + +struct spdk_pci_id +spdk_pci_device_get_id(struct spdk_pci_device *pci_dev) +{ + struct spdk_pci_id pci_id; + + memset(&pci_id, 0xFF, sizeof(pci_id)); + + return pci_id; +} + +void +spdk_nvme_ctrlr_get_default_ctrlr_opts(struct spdk_nvme_ctrlr_opts *opts, size_t opts_size) +{ + memset(opts, 0, sizeof(*opts)); +} + +uint32_t +spdk_nvme_ns_get_sector_size(struct spdk_nvme_ns *ns) +{ + return ns->sector_size; +} + +uint32_t +spdk_nvme_ns_get_max_io_xfer_size(struct spdk_nvme_ns *ns) +{ + return ns->ctrlr->max_xfer_size; +} + +int +nvme_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req) +{ + g_request = req; + + return 0; +} + +void +nvme_ctrlr_proc_get_ref(struct spdk_nvme_ctrlr *ctrlr) +{ + return; +} + +void +nvme_ctrlr_proc_put_ref(struct spdk_nvme_ctrlr *ctrlr) +{ + return; +} + +int +nvme_ctrlr_get_ref_count(struct spdk_nvme_ctrlr *ctrlr) +{ + return 0; +} + +int +nvme_transport_ctrlr_scan(const struct spdk_nvme_transport_id *trid, + void *cb_ctx, + spdk_nvme_probe_cb probe_cb, + spdk_nvme_remove_cb remove_cb, + bool direct_connect) +{ + return 0; +} + +static void +prepare_for_test(struct spdk_nvme_ns *ns, struct spdk_nvme_ctrlr *ctrlr, + struct spdk_nvme_qpair *qpair, + uint32_t sector_size, uint32_t md_size, uint32_t max_xfer_size, + uint32_t stripe_size, bool extended_lba) +{ + uint32_t num_requests = 32; + uint32_t i; + + ctrlr->max_xfer_size = max_xfer_size; + /* + * Clear the flags field - we especially want to make sure the SGL_SUPPORTED flag is not set + * so that we test the SGL splitting path. + */ + ctrlr->flags = 0; + ctrlr->min_page_size = 4096; + ctrlr->page_size = 4096; + memset(&ctrlr->opts, 0, sizeof(ctrlr->opts)); + memset(ns, 0, sizeof(*ns)); + ns->ctrlr = ctrlr; + ns->sector_size = sector_size; + ns->extended_lba_size = sector_size; + if (extended_lba) { + ns->flags |= SPDK_NVME_NS_EXTENDED_LBA_SUPPORTED; + ns->extended_lba_size += md_size; + } + ns->md_size = md_size; + ns->sectors_per_max_io = spdk_nvme_ns_get_max_io_xfer_size(ns) / ns->extended_lba_size; + ns->sectors_per_stripe = stripe_size / ns->extended_lba_size; + + memset(qpair, 0, sizeof(*qpair)); + qpair->ctrlr = ctrlr; + qpair->req_buf = calloc(num_requests, sizeof(struct nvme_request)); + SPDK_CU_ASSERT_FATAL(qpair->req_buf != NULL); + + for (i = 0; i < num_requests; i++) { + struct nvme_request *req = qpair->req_buf + i * sizeof(struct nvme_request); + + STAILQ_INSERT_HEAD(&qpair->free_req, req, stailq); + } + + g_request = NULL; +} + +static void +cleanup_after_test(struct spdk_nvme_qpair *qpair) +{ + free(qpair->req_buf); +} + +static void +nvme_cmd_interpret_rw(const struct spdk_nvme_cmd *cmd, + uint64_t *lba, uint32_t *num_blocks) +{ + *lba = *(const uint64_t *)&cmd->cdw10; + *num_blocks = (cmd->cdw12 & 0xFFFFu) + 1; +} + +static void +split_test(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_qpair qpair; + struct spdk_nvme_ctrlr ctrlr; + void *payload; + uint64_t lba, cmd_lba; + uint32_t lba_count, cmd_lba_count; + int rc; + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false); + payload = malloc(512); + lba = 0; + lba_count = 1; + + rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + + CU_ASSERT(g_request->num_children == 0); + nvme_cmd_interpret_rw(&g_request->cmd, &cmd_lba, &cmd_lba_count); + CU_ASSERT(cmd_lba == lba); + CU_ASSERT(cmd_lba_count == lba_count); + + free(payload); + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +split_test2(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + struct nvme_request *child; + void *payload; + uint64_t lba, cmd_lba; + uint32_t lba_count, cmd_lba_count; + int rc; + + /* + * Controller has max xfer of 128 KB (256 blocks). + * Submit an I/O of 256 KB starting at LBA 0, which should be split + * on the max I/O boundary into two I/Os of 128 KB. + */ + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false); + payload = malloc(256 * 1024); + lba = 0; + lba_count = (256 * 1024) / 512; + + rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + + CU_ASSERT(g_request->num_children == 2); + + child = TAILQ_FIRST(&g_request->children); + nvme_request_remove_child(g_request, child); + nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count); + CU_ASSERT(child->num_children == 0); + CU_ASSERT(child->payload_size == 128 * 1024); + CU_ASSERT(cmd_lba == 0); + CU_ASSERT(cmd_lba_count == 256); /* 256 * 512 byte blocks = 128 KB */ + nvme_free_request(child); + + child = TAILQ_FIRST(&g_request->children); + nvme_request_remove_child(g_request, child); + nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count); + CU_ASSERT(child->num_children == 0); + CU_ASSERT(child->payload_size == 128 * 1024); + CU_ASSERT(cmd_lba == 256); + CU_ASSERT(cmd_lba_count == 256); + nvme_free_request(child); + + CU_ASSERT(TAILQ_EMPTY(&g_request->children)); + + free(payload); + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +split_test3(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + struct nvme_request *child; + void *payload; + uint64_t lba, cmd_lba; + uint32_t lba_count, cmd_lba_count; + int rc; + + /* + * Controller has max xfer of 128 KB (256 blocks). + * Submit an I/O of 256 KB starting at LBA 10, which should be split + * into two I/Os: + * 1) LBA = 10, count = 256 blocks + * 2) LBA = 266, count = 256 blocks + */ + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false); + payload = malloc(256 * 1024); + lba = 10; /* Start at an LBA that isn't aligned to the stripe size */ + lba_count = (256 * 1024) / 512; + + rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + + SPDK_CU_ASSERT_FATAL(g_request->num_children == 2); + + child = TAILQ_FIRST(&g_request->children); + nvme_request_remove_child(g_request, child); + nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count); + CU_ASSERT(child->num_children == 0); + CU_ASSERT(child->payload_size == 128 * 1024); + CU_ASSERT(cmd_lba == 10); + CU_ASSERT(cmd_lba_count == 256); + nvme_free_request(child); + + child = TAILQ_FIRST(&g_request->children); + nvme_request_remove_child(g_request, child); + nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count); + CU_ASSERT(child->num_children == 0); + CU_ASSERT(child->payload_size == 128 * 1024); + CU_ASSERT(cmd_lba == 266); + CU_ASSERT(cmd_lba_count == 256); + nvme_free_request(child); + + CU_ASSERT(TAILQ_EMPTY(&g_request->children)); + + free(payload); + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +split_test4(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + struct nvme_request *child; + void *payload; + uint64_t lba, cmd_lba; + uint32_t lba_count, cmd_lba_count; + int rc; + + /* + * Controller has max xfer of 128 KB (256 blocks) and a stripe size of 128 KB. + * (Same as split_test3 except with driver-assisted striping enabled.) + * Submit an I/O of 256 KB starting at LBA 10, which should be split + * into three I/Os: + * 1) LBA = 10, count = 246 blocks (less than max I/O size to align to stripe size) + * 2) LBA = 256, count = 256 blocks (aligned to stripe size and max I/O size) + * 3) LBA = 512, count = 10 blocks (finish off the remaining I/O size) + */ + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 128 * 1024, false); + payload = malloc(256 * 1024); + lba = 10; /* Start at an LBA that isn't aligned to the stripe size */ + lba_count = (256 * 1024) / 512; + + rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL, + SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + + SPDK_CU_ASSERT_FATAL(g_request->num_children == 3); + + child = TAILQ_FIRST(&g_request->children); + nvme_request_remove_child(g_request, child); + nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count); + CU_ASSERT(child->num_children == 0); + CU_ASSERT(child->payload_size == (256 - 10) * 512); + CU_ASSERT(child->payload_offset == 0); + CU_ASSERT(cmd_lba == 10); + CU_ASSERT(cmd_lba_count == 256 - 10); + CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS) != 0); + CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_LIMITED_RETRY) == 0); + nvme_free_request(child); + + child = TAILQ_FIRST(&g_request->children); + nvme_request_remove_child(g_request, child); + nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count); + CU_ASSERT(child->num_children == 0); + CU_ASSERT(child->payload_size == 128 * 1024); + CU_ASSERT(child->payload_offset == (256 - 10) * 512); + CU_ASSERT(cmd_lba == 256); + CU_ASSERT(cmd_lba_count == 256); + CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS) != 0); + CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_LIMITED_RETRY) == 0); + nvme_free_request(child); + + child = TAILQ_FIRST(&g_request->children); + nvme_request_remove_child(g_request, child); + nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count); + CU_ASSERT(child->num_children == 0); + CU_ASSERT(child->payload_size == 10 * 512); + CU_ASSERT(child->payload_offset == (512 - 10) * 512); + CU_ASSERT(cmd_lba == 512); + CU_ASSERT(cmd_lba_count == 10); + CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS) != 0); + CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_LIMITED_RETRY) == 0); + nvme_free_request(child); + + CU_ASSERT(TAILQ_EMPTY(&g_request->children)); + + free(payload); + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +test_cmd_child_request(void) +{ + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + int rc = 0; + struct nvme_request *child, *tmp; + void *payload; + uint64_t lba = 0x1000; + uint32_t i = 0; + uint32_t offset = 0; + uint32_t sector_size = 512; + uint32_t max_io_size = 128 * 1024; + uint32_t sectors_per_max_io = max_io_size / sector_size; + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_io_size, 0, false); + + payload = malloc(128 * 1024); + rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, sectors_per_max_io, NULL, NULL, 0); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->payload_offset == 0); + CU_ASSERT(g_request->num_children == 0); + nvme_free_request(g_request); + + rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, sectors_per_max_io - 1, NULL, NULL, 0); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->payload_offset == 0); + CU_ASSERT(g_request->num_children == 0); + nvme_free_request(g_request); + + rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, sectors_per_max_io * 4, NULL, NULL, 0); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->num_children == 4); + + rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, (DEFAULT_IO_QUEUE_REQUESTS + 1) * sector_size, + NULL, + NULL, 0); + SPDK_CU_ASSERT_FATAL(rc == -EINVAL); + + TAILQ_FOREACH_SAFE(child, &g_request->children, child_tailq, tmp) { + nvme_request_remove_child(g_request, child); + CU_ASSERT(child->payload_offset == offset); + CU_ASSERT(child->cmd.opc == SPDK_NVME_OPC_READ); + CU_ASSERT(child->cmd.nsid == ns.id); + CU_ASSERT(child->cmd.cdw10 == (lba + sectors_per_max_io * i)); + CU_ASSERT(child->cmd.cdw12 == ((sectors_per_max_io - 1) | 0)); + offset += max_io_size; + nvme_free_request(child); + i++; + } + + free(payload); + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ns_cmd_flush(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + spdk_nvme_cmd_cb cb_fn = NULL; + void *cb_arg = NULL; + int rc; + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false); + + rc = spdk_nvme_ns_cmd_flush(&ns, &qpair, cb_fn, cb_arg); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_FLUSH); + CU_ASSERT(g_request->cmd.nsid == ns.id); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ns_cmd_write_zeroes(void) +{ + struct spdk_nvme_ns ns = { 0 }; + struct spdk_nvme_ctrlr ctrlr = { 0 }; + struct spdk_nvme_qpair qpair; + spdk_nvme_cmd_cb cb_fn = NULL; + void *cb_arg = NULL; + uint64_t cmd_lba; + uint32_t cmd_lba_count; + int rc; + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false); + + rc = spdk_nvme_ns_cmd_write_zeroes(&ns, &qpair, 0, 2, cb_fn, cb_arg, 0); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_WRITE_ZEROES); + CU_ASSERT(g_request->cmd.nsid == ns.id); + nvme_cmd_interpret_rw(&g_request->cmd, &cmd_lba, &cmd_lba_count); + CU_ASSERT_EQUAL(cmd_lba, 0); + CU_ASSERT_EQUAL(cmd_lba_count, 2); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ns_cmd_dataset_management(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + spdk_nvme_cmd_cb cb_fn = NULL; + void *cb_arg = NULL; + struct spdk_nvme_dsm_range ranges[256]; + uint16_t i; + int rc = 0; + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false); + + for (i = 0; i < 256; i++) { + ranges[i].starting_lba = i; + ranges[i].length = 1; + ranges[i].attributes.raw = 0; + } + + /* TRIM one LBA */ + rc = spdk_nvme_ns_cmd_dataset_management(&ns, &qpair, SPDK_NVME_DSM_ATTR_DEALLOCATE, + ranges, 1, cb_fn, cb_arg); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_DATASET_MANAGEMENT); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw10 == 0); + CU_ASSERT(g_request->cmd.cdw11 == SPDK_NVME_DSM_ATTR_DEALLOCATE); + spdk_dma_free(g_request->payload.contig_or_cb_arg); + nvme_free_request(g_request); + + /* TRIM 256 LBAs */ + rc = spdk_nvme_ns_cmd_dataset_management(&ns, &qpair, SPDK_NVME_DSM_ATTR_DEALLOCATE, + ranges, 256, cb_fn, cb_arg); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_DATASET_MANAGEMENT); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw10 == 255u); + CU_ASSERT(g_request->cmd.cdw11 == SPDK_NVME_DSM_ATTR_DEALLOCATE); + spdk_dma_free(g_request->payload.contig_or_cb_arg); + nvme_free_request(g_request); + + rc = spdk_nvme_ns_cmd_dataset_management(&ns, &qpair, SPDK_NVME_DSM_ATTR_DEALLOCATE, + NULL, 0, cb_fn, cb_arg); + CU_ASSERT(rc != 0); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ns_cmd_readv(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + int rc = 0; + void *cb_arg; + uint32_t lba_count = 256; + uint32_t sector_size = 512; + uint64_t sge_length = lba_count * sector_size; + + cb_arg = malloc(512); + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, 128 * 1024, 0, false); + rc = spdk_nvme_ns_cmd_readv(&ns, &qpair, 0x1000, lba_count, NULL, &sge_length, 0, + nvme_request_reset_sgl, nvme_request_next_sge); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_READ); + CU_ASSERT(nvme_payload_type(&g_request->payload) == NVME_PAYLOAD_TYPE_SGL); + CU_ASSERT(g_request->payload.reset_sgl_fn == nvme_request_reset_sgl); + CU_ASSERT(g_request->payload.next_sge_fn == nvme_request_next_sge); + CU_ASSERT(g_request->payload.contig_or_cb_arg == &sge_length); + CU_ASSERT(g_request->cmd.nsid == ns.id); + + rc = spdk_nvme_ns_cmd_readv(&ns, &qpair, 0x1000, 256, NULL, cb_arg, 0, nvme_request_reset_sgl, + NULL); + CU_ASSERT(rc != 0); + + free(cb_arg); + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ns_cmd_writev(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + int rc = 0; + void *cb_arg; + uint32_t lba_count = 256; + uint32_t sector_size = 512; + uint64_t sge_length = lba_count * sector_size; + + cb_arg = malloc(512); + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, 128 * 1024, 0, false); + rc = spdk_nvme_ns_cmd_writev(&ns, &qpair, 0x1000, lba_count, NULL, &sge_length, 0, + nvme_request_reset_sgl, nvme_request_next_sge); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_WRITE); + CU_ASSERT(nvme_payload_type(&g_request->payload) == NVME_PAYLOAD_TYPE_SGL); + CU_ASSERT(g_request->payload.reset_sgl_fn == nvme_request_reset_sgl); + CU_ASSERT(g_request->payload.next_sge_fn == nvme_request_next_sge); + CU_ASSERT(g_request->payload.contig_or_cb_arg == &sge_length); + CU_ASSERT(g_request->cmd.nsid == ns.id); + + rc = spdk_nvme_ns_cmd_writev(&ns, &qpair, 0x1000, 256, NULL, cb_arg, 0, + NULL, nvme_request_next_sge); + CU_ASSERT(rc != 0); + + free(cb_arg); + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ns_cmd_comparev(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + int rc = 0; + void *cb_arg; + uint32_t lba_count = 256; + uint32_t sector_size = 512; + uint64_t sge_length = lba_count * sector_size; + + cb_arg = malloc(512); + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, 128 * 1024, 0, false); + rc = spdk_nvme_ns_cmd_comparev(&ns, &qpair, 0x1000, lba_count, NULL, &sge_length, 0, + nvme_request_reset_sgl, nvme_request_next_sge); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_COMPARE); + CU_ASSERT(nvme_payload_type(&g_request->payload) == NVME_PAYLOAD_TYPE_SGL); + CU_ASSERT(g_request->payload.reset_sgl_fn == nvme_request_reset_sgl); + CU_ASSERT(g_request->payload.next_sge_fn == nvme_request_next_sge); + CU_ASSERT(g_request->payload.contig_or_cb_arg == &sge_length); + CU_ASSERT(g_request->cmd.nsid == ns.id); + + rc = spdk_nvme_ns_cmd_comparev(&ns, &qpair, 0x1000, 256, NULL, cb_arg, 0, + nvme_request_reset_sgl, NULL); + CU_ASSERT(rc != 0); + + free(cb_arg); + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +test_io_flags(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + void *payload; + uint64_t lba; + uint32_t lba_count; + int rc; + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 128 * 1024, false); + payload = malloc(256 * 1024); + lba = 0; + lba_count = (4 * 1024) / 512; + + rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL, + SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT((g_request->cmd.cdw12 & SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS) != 0); + CU_ASSERT((g_request->cmd.cdw12 & SPDK_NVME_IO_FLAGS_LIMITED_RETRY) == 0); + nvme_free_request(g_request); + + rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL, + SPDK_NVME_IO_FLAGS_LIMITED_RETRY); + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT((g_request->cmd.cdw12 & SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS) == 0); + CU_ASSERT((g_request->cmd.cdw12 & SPDK_NVME_IO_FLAGS_LIMITED_RETRY) != 0); + nvme_free_request(g_request); + + free(payload); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ns_cmd_reservation_register(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + struct spdk_nvme_reservation_register_data *payload; + bool ignore_key = 1; + spdk_nvme_cmd_cb cb_fn = NULL; + void *cb_arg = NULL; + int rc = 0; + uint32_t tmp_cdw10; + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false); + payload = malloc(sizeof(struct spdk_nvme_reservation_register_data)); + + rc = spdk_nvme_ns_cmd_reservation_register(&ns, &qpair, payload, ignore_key, + SPDK_NVME_RESERVE_REGISTER_KEY, + SPDK_NVME_RESERVE_PTPL_NO_CHANGES, + cb_fn, cb_arg); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_RESERVATION_REGISTER); + CU_ASSERT(g_request->cmd.nsid == ns.id); + + tmp_cdw10 = SPDK_NVME_RESERVE_REGISTER_KEY; + tmp_cdw10 |= ignore_key ? 1 << 3 : 0; + tmp_cdw10 |= (uint32_t)SPDK_NVME_RESERVE_PTPL_NO_CHANGES << 30; + + CU_ASSERT(g_request->cmd.cdw10 == tmp_cdw10); + + spdk_dma_free(g_request->payload.contig_or_cb_arg); + nvme_free_request(g_request); + free(payload); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ns_cmd_reservation_release(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + struct spdk_nvme_reservation_key_data *payload; + bool ignore_key = 1; + spdk_nvme_cmd_cb cb_fn = NULL; + void *cb_arg = NULL; + int rc = 0; + uint32_t tmp_cdw10; + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false); + payload = malloc(sizeof(struct spdk_nvme_reservation_key_data)); + + rc = spdk_nvme_ns_cmd_reservation_release(&ns, &qpair, payload, ignore_key, + SPDK_NVME_RESERVE_RELEASE, + SPDK_NVME_RESERVE_WRITE_EXCLUSIVE, + cb_fn, cb_arg); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_RESERVATION_RELEASE); + CU_ASSERT(g_request->cmd.nsid == ns.id); + + tmp_cdw10 = SPDK_NVME_RESERVE_RELEASE; + tmp_cdw10 |= ignore_key ? 1 << 3 : 0; + tmp_cdw10 |= (uint32_t)SPDK_NVME_RESERVE_WRITE_EXCLUSIVE << 8; + + CU_ASSERT(g_request->cmd.cdw10 == tmp_cdw10); + + spdk_dma_free(g_request->payload.contig_or_cb_arg); + nvme_free_request(g_request); + free(payload); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ns_cmd_reservation_acquire(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + struct spdk_nvme_reservation_acquire_data *payload; + bool ignore_key = 1; + spdk_nvme_cmd_cb cb_fn = NULL; + void *cb_arg = NULL; + int rc = 0; + uint32_t tmp_cdw10; + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false); + payload = malloc(sizeof(struct spdk_nvme_reservation_acquire_data)); + + rc = spdk_nvme_ns_cmd_reservation_acquire(&ns, &qpair, payload, ignore_key, + SPDK_NVME_RESERVE_ACQUIRE, + SPDK_NVME_RESERVE_WRITE_EXCLUSIVE, + cb_fn, cb_arg); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_RESERVATION_ACQUIRE); + CU_ASSERT(g_request->cmd.nsid == ns.id); + + tmp_cdw10 = SPDK_NVME_RESERVE_ACQUIRE; + tmp_cdw10 |= ignore_key ? 1 << 3 : 0; + tmp_cdw10 |= (uint32_t)SPDK_NVME_RESERVE_WRITE_EXCLUSIVE << 8; + + CU_ASSERT(g_request->cmd.cdw10 == tmp_cdw10); + + spdk_dma_free(g_request->payload.contig_or_cb_arg); + nvme_free_request(g_request); + free(payload); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ns_cmd_reservation_report(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + struct spdk_nvme_reservation_status_data *payload; + spdk_nvme_cmd_cb cb_fn = NULL; + void *cb_arg = NULL; + int rc = 0; + uint32_t size = sizeof(struct spdk_nvme_reservation_status_data); + + prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false); + + payload = calloc(1, size); + SPDK_CU_ASSERT_FATAL(payload != NULL); + + rc = spdk_nvme_ns_cmd_reservation_report(&ns, &qpair, payload, size, cb_fn, cb_arg); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_RESERVATION_REPORT); + CU_ASSERT(g_request->cmd.nsid == ns.id); + + CU_ASSERT(g_request->cmd.cdw10 == (size / 4)); + + spdk_dma_free(g_request->payload.contig_or_cb_arg); + nvme_free_request(g_request); + free(payload); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ns_cmd_write_with_md(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + int rc = 0; + char *buffer = NULL; + char *metadata = NULL; + uint32_t block_size, md_size; + struct nvme_request *child0, *child1; + + block_size = 512; + md_size = 128; + + buffer = malloc((block_size + md_size) * 384); + SPDK_CU_ASSERT_FATAL(buffer != NULL); + metadata = malloc(md_size * 384); + SPDK_CU_ASSERT_FATAL(metadata != NULL); + + /* + * 512 byte data + 128 byte metadata + * Separate metadata buffer + * Max data transfer size 128 KB + * No stripe size + * + * 256 blocks * 512 bytes per block = single 128 KB I/O (no splitting required) + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 128, 128 * 1024, 0, false); + + rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, metadata, 0x1000, 256, NULL, NULL, 0, 0, + 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload.md == metadata); + CU_ASSERT(g_request->payload_size == 256 * 512); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + /* + * 512 byte data + 128 byte metadata + * Extended LBA + * Max data transfer size 128 KB + * No stripe size + * + * 256 blocks * (512 + 128) bytes per block = two I/Os: + * child 0: 204 blocks - 204 * (512 + 128) = 127.5 KB + * child 1: 52 blocks + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 128, 128 * 1024, 0, true); + + rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256, NULL, NULL, 0, 0, + 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 2); + child0 = TAILQ_FIRST(&g_request->children); + + SPDK_CU_ASSERT_FATAL(child0 != NULL); + CU_ASSERT(child0->payload.md == NULL); + CU_ASSERT(child0->payload_offset == 0); + CU_ASSERT(child0->payload_size == 204 * (512 + 128)); + child1 = TAILQ_NEXT(child0, child_tailq); + + SPDK_CU_ASSERT_FATAL(child1 != NULL); + CU_ASSERT(child1->payload.md == NULL); + CU_ASSERT(child1->payload_offset == 204 * (512 + 128)); + CU_ASSERT(child1->payload_size == 52 * (512 + 128)); + + nvme_request_free_children(g_request); + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + /* + * 512 byte data + 8 byte metadata + * Extended LBA + * Max data transfer size 128 KB + * No stripe size + * No protection information + * + * 256 blocks * (512 + 8) bytes per block = two I/Os: + * child 0: 252 blocks - 252 * (512 + 8) = 127.96875 KB + * child 1: 4 blocks + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, true); + + rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256, NULL, NULL, 0, 0, + 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 2); + child0 = TAILQ_FIRST(&g_request->children); + + SPDK_CU_ASSERT_FATAL(child0 != NULL); + CU_ASSERT(child0->payload.md == NULL); + CU_ASSERT(child0->payload_offset == 0); + CU_ASSERT(child0->payload_size == 252 * (512 + 8)); + child1 = TAILQ_NEXT(child0, child_tailq); + + SPDK_CU_ASSERT_FATAL(child1 != NULL); + CU_ASSERT(child1->payload.md == NULL); + CU_ASSERT(child1->payload_offset == 252 * (512 + 8)); + CU_ASSERT(child1->payload_size == 4 * (512 + 8)); + + nvme_request_free_children(g_request); + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + /* + * 512 byte data + 8 byte metadata + * Extended LBA + * Max data transfer size 128 KB + * No stripe size + * Protection information enabled + PRACT + * + * Special case for 8-byte metadata + PI + PRACT: no metadata transferred + * In theory, 256 blocks * 512 bytes per block = one I/O (128 KB) + * However, the splitting code does not account for PRACT when calculating + * max sectors per transfer, so we actually get two I/Os: + * child 0: 252 blocks + * child 1: 4 blocks + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, true); + ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED; + + rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256, NULL, NULL, + SPDK_NVME_IO_FLAGS_PRACT, 0, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 2); + child0 = TAILQ_FIRST(&g_request->children); + + SPDK_CU_ASSERT_FATAL(child0 != NULL); + CU_ASSERT(child0->payload_offset == 0); + CU_ASSERT(child0->payload_size == 252 * 512); /* NOTE: does not include metadata! */ + child1 = TAILQ_NEXT(child0, child_tailq); + + SPDK_CU_ASSERT_FATAL(child1 != NULL); + CU_ASSERT(child1->payload.md == NULL); + CU_ASSERT(child1->payload_offset == 252 * 512); + CU_ASSERT(child1->payload_size == 4 * 512); + + nvme_request_free_children(g_request); + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + /* + * 512 byte data + 8 byte metadata + * Separate metadata buffer + * Max data transfer size 128 KB + * No stripe size + * Protection information enabled + PRACT + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, false); + ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED; + + rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, metadata, 0x1000, 256, NULL, NULL, + SPDK_NVME_IO_FLAGS_PRACT, 0, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload.md == metadata); + CU_ASSERT(g_request->payload_size == 256 * 512); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + /* + * 512 byte data + 8 byte metadata + * Separate metadata buffer + * Max data transfer size 128 KB + * No stripe size + * Protection information enabled + PRACT + * + * 384 blocks * 512 bytes = two I/Os: + * child 0: 256 blocks + * child 1: 128 blocks + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, false); + ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED; + + rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, metadata, 0x1000, 384, NULL, NULL, + SPDK_NVME_IO_FLAGS_PRACT, 0, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 2); + child0 = TAILQ_FIRST(&g_request->children); + + SPDK_CU_ASSERT_FATAL(child0 != NULL); + CU_ASSERT(child0->payload_offset == 0); + CU_ASSERT(child0->payload_size == 256 * 512); + CU_ASSERT(child0->md_offset == 0); + child1 = TAILQ_NEXT(child0, child_tailq); + + SPDK_CU_ASSERT_FATAL(child1 != NULL); + CU_ASSERT(child1->payload_offset == 256 * 512); + CU_ASSERT(child1->payload_size == 128 * 512); + CU_ASSERT(child1->md_offset == 256 * 8); + + nvme_request_free_children(g_request); + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + free(buffer); + free(metadata); +} + +static void +test_nvme_ns_cmd_read_with_md(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + int rc = 0; + char *buffer = NULL; + char *metadata = NULL; + uint32_t block_size, md_size; + + block_size = 512; + md_size = 128; + + buffer = malloc(block_size * 256); + SPDK_CU_ASSERT_FATAL(buffer != NULL); + metadata = malloc(md_size * 256); + SPDK_CU_ASSERT_FATAL(metadata != NULL); + + /* + * 512 byte data + 128 byte metadata + * Separate metadata buffer + * Max data transfer size 128 KB + * No stripe size + * + * 256 blocks * 512 bytes per block = single 128 KB I/O (no splitting required) + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 128, 128 * 1024, 0, false); + + rc = spdk_nvme_ns_cmd_read_with_md(&ns, &qpair, buffer, metadata, 0x1000, 256, NULL, NULL, 0, 0, + 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload.md == metadata); + CU_ASSERT(g_request->payload_size == 256 * 512); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + free(buffer); + free(metadata); +} + +static void +test_nvme_ns_cmd_compare_with_md(void) +{ + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + int rc = 0; + char *buffer = NULL; + char *metadata = NULL; + uint32_t block_size, md_size; + struct nvme_request *child0, *child1; + + block_size = 512; + md_size = 128; + + buffer = malloc((block_size + md_size) * 384); + SPDK_CU_ASSERT_FATAL(buffer != NULL); + metadata = malloc(md_size * 384); + SPDK_CU_ASSERT_FATAL(metadata != NULL); + + /* + * 512 byte data + 128 byte metadata + * Separate metadata buffer + * Max data transfer size 128 KB + * No stripe size + * + * 256 blocks * 512 bytes per block = single 128 KB I/O (no splitting required) + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 128, 128 * 1024, 0, false); + + rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, metadata, 0x1000, 256, + NULL, NULL, 0, 0, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload.md == metadata); + CU_ASSERT(g_request->payload_size == 256 * 512); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + /* + * 512 byte data + 128 byte metadata + * Extended LBA + * Max data transfer size 128 KB + * No stripe size + * + * 256 blocks * (512 + 128) bytes per block = two I/Os: + * child 0: 204 blocks - 204 * (512 + 128) = 127.5 KB + * child 1: 52 blocks + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 128, 128 * 1024, 0, true); + + rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256, + NULL, NULL, 0, 0, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 2); + child0 = TAILQ_FIRST(&g_request->children); + + SPDK_CU_ASSERT_FATAL(child0 != NULL); + CU_ASSERT(child0->payload.md == NULL); + CU_ASSERT(child0->payload_offset == 0); + CU_ASSERT(child0->payload_size == 204 * (512 + 128)); + child1 = TAILQ_NEXT(child0, child_tailq); + + SPDK_CU_ASSERT_FATAL(child1 != NULL); + CU_ASSERT(child1->payload.md == NULL); + CU_ASSERT(child1->payload_offset == 204 * (512 + 128)); + CU_ASSERT(child1->payload_size == 52 * (512 + 128)); + + nvme_request_free_children(g_request); + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + /* + * 512 byte data + 8 byte metadata + * Extended LBA + * Max data transfer size 128 KB + * No stripe size + * No protection information + * + * 256 blocks * (512 + 8) bytes per block = two I/Os: + * child 0: 252 blocks - 252 * (512 + 8) = 127.96875 KB + * child 1: 4 blocks + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, true); + + rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256, + NULL, NULL, 0, 0, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 2); + child0 = TAILQ_FIRST(&g_request->children); + + SPDK_CU_ASSERT_FATAL(child0 != NULL); + CU_ASSERT(child0->payload.md == NULL); + CU_ASSERT(child0->payload_offset == 0); + CU_ASSERT(child0->payload_size == 252 * (512 + 8)); + child1 = TAILQ_NEXT(child0, child_tailq); + + SPDK_CU_ASSERT_FATAL(child1 != NULL); + CU_ASSERT(child1->payload.md == NULL); + CU_ASSERT(child1->payload_offset == 252 * (512 + 8)); + CU_ASSERT(child1->payload_size == 4 * (512 + 8)); + + nvme_request_free_children(g_request); + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + /* + * 512 byte data + 8 byte metadata + * Extended LBA + * Max data transfer size 128 KB + * No stripe size + * Protection information enabled + PRACT + * + * Special case for 8-byte metadata + PI + PRACT: no metadata transferred + * In theory, 256 blocks * 512 bytes per block = one I/O (128 KB) + * However, the splitting code does not account for PRACT when calculating + * max sectors per transfer, so we actually get two I/Os: + * child 0: 252 blocks + * child 1: 4 blocks + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, true); + ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED; + + rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256, + NULL, NULL, SPDK_NVME_IO_FLAGS_PRACT, 0, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 2); + child0 = TAILQ_FIRST(&g_request->children); + + SPDK_CU_ASSERT_FATAL(child0 != NULL); + CU_ASSERT(child0->payload_offset == 0); + CU_ASSERT(child0->payload_size == 252 * 512); /* NOTE: does not include metadata! */ + child1 = TAILQ_NEXT(child0, child_tailq); + + SPDK_CU_ASSERT_FATAL(child1 != NULL); + CU_ASSERT(child1->payload.md == NULL); + CU_ASSERT(child1->payload_offset == 252 * 512); + CU_ASSERT(child1->payload_size == 4 * 512); + + nvme_request_free_children(g_request); + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + /* + * 512 byte data + 8 byte metadata + * Separate metadata buffer + * Max data transfer size 128 KB + * No stripe size + * Protection information enabled + PRACT + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, false); + ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED; + + rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, metadata, 0x1000, 256, + NULL, NULL, SPDK_NVME_IO_FLAGS_PRACT, 0, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload.md == metadata); + CU_ASSERT(g_request->payload_size == 256 * 512); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + /* + * 512 byte data + 8 byte metadata + * Separate metadata buffer + * Max data transfer size 128 KB + * No stripe size + * Protection information enabled + PRACT + * + * 384 blocks * 512 bytes = two I/Os: + * child 0: 256 blocks + * child 1: 128 blocks + */ + prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, false); + ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED; + + rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, metadata, 0x1000, 384, + NULL, NULL, SPDK_NVME_IO_FLAGS_PRACT, 0, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 2); + child0 = TAILQ_FIRST(&g_request->children); + + SPDK_CU_ASSERT_FATAL(child0 != NULL); + CU_ASSERT(child0->payload_offset == 0); + CU_ASSERT(child0->payload_size == 256 * 512); + CU_ASSERT(child0->md_offset == 0); + child1 = TAILQ_NEXT(child0, child_tailq); + + SPDK_CU_ASSERT_FATAL(child1 != NULL); + CU_ASSERT(child1->payload_offset == 256 * 512); + CU_ASSERT(child1->payload_size == 128 * 512); + CU_ASSERT(child1->md_offset == 256 * 8); + + nvme_request_free_children(g_request); + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + free(buffer); + free(metadata); +} + +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("nvme_ns_cmd", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "split_test", split_test) == NULL + || CU_add_test(suite, "split_test2", split_test2) == NULL + || CU_add_test(suite, "split_test3", split_test3) == NULL + || CU_add_test(suite, "split_test4", split_test4) == NULL + || CU_add_test(suite, "nvme_ns_cmd_flush", test_nvme_ns_cmd_flush) == NULL + || CU_add_test(suite, "nvme_ns_cmd_dataset_management", + test_nvme_ns_cmd_dataset_management) == NULL + || CU_add_test(suite, "io_flags", test_io_flags) == NULL + || CU_add_test(suite, "nvme_ns_cmd_write_zeroes", test_nvme_ns_cmd_write_zeroes) == NULL + || CU_add_test(suite, "nvme_ns_cmd_reservation_register", + test_nvme_ns_cmd_reservation_register) == NULL + || CU_add_test(suite, "nvme_ns_cmd_reservation_release", + test_nvme_ns_cmd_reservation_release) == NULL + || CU_add_test(suite, "nvme_ns_cmd_reservation_acquire", + test_nvme_ns_cmd_reservation_acquire) == NULL + || CU_add_test(suite, "nvme_ns_cmd_reservation_report", test_nvme_ns_cmd_reservation_report) == NULL + || CU_add_test(suite, "test_cmd_child_request", test_cmd_child_request) == NULL + || CU_add_test(suite, "nvme_ns_cmd_readv", test_nvme_ns_cmd_readv) == NULL + || CU_add_test(suite, "nvme_ns_cmd_read_with_md", test_nvme_ns_cmd_read_with_md) == NULL + || CU_add_test(suite, "nvme_ns_cmd_writev", test_nvme_ns_cmd_writev) == NULL + || CU_add_test(suite, "nvme_ns_cmd_write_with_md", test_nvme_ns_cmd_write_with_md) == NULL + || CU_add_test(suite, "nvme_ns_cmd_comparev", test_nvme_ns_cmd_comparev) == NULL + || CU_add_test(suite, "nvme_ns_cmd_compare_with_md", test_nvme_ns_cmd_compare_with_md) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + g_spdk_nvme_driver = &_g_nvme_driver; + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/.gitignore new file mode 100644 index 00000000..8f4f47a1 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/.gitignore @@ -0,0 +1 @@ +nvme_ns_ocssd_cmd_ut diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/Makefile new file mode 100644 index 00000000..35fdb83a --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.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 = nvme_ns_ocssd_cmd_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/nvme_ns_ocssd_cmd_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/nvme_ns_ocssd_cmd_ut.c new file mode 100644 index 00000000..2d13b7a6 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/nvme_ns_ocssd_cmd_ut.c @@ -0,0 +1,677 @@ +/*- + * 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_cunit.h" + +#include "nvme/nvme_ns_ocssd_cmd.c" +#include "nvme/nvme_ns_cmd.c" +#include "nvme/nvme.c" + +#include "common/lib/test_env.c" + +#ifndef PAGE_SIZE +#define PAGE_SIZE 4096 +#endif + +DEFINE_STUB(spdk_nvme_qpair_process_completions, int32_t, + (struct spdk_nvme_qpair *qpair, + uint32_t max_completions), 0); + +static struct nvme_driver _g_nvme_driver = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +static struct nvme_request *g_request = NULL; + +int +nvme_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req) +{ + g_request = req; + + return 0; +} + +void +nvme_ctrlr_destruct(struct spdk_nvme_ctrlr *ctrlr) +{ +} + +void +nvme_ctrlr_proc_get_ref(struct spdk_nvme_ctrlr *ctrlr) +{ + return; +} + + +int +nvme_ctrlr_process_init(struct spdk_nvme_ctrlr *ctrlr) +{ + return 0; +} + +void +nvme_ctrlr_proc_put_ref(struct spdk_nvme_ctrlr *ctrlr) +{ + return; +} + +void +spdk_nvme_ctrlr_get_default_ctrlr_opts(struct spdk_nvme_ctrlr_opts *opts, size_t opts_size) +{ + memset(opts, 0, sizeof(*opts)); +} + +bool +spdk_nvme_transport_available(enum spdk_nvme_transport_type trtype) +{ + return true; +} + +struct spdk_nvme_ctrlr *nvme_transport_ctrlr_construct(const struct spdk_nvme_transport_id *trid, + const struct spdk_nvme_ctrlr_opts *opts, + void *devhandle) +{ + return NULL; +} + +int +nvme_ctrlr_get_ref_count(struct spdk_nvme_ctrlr *ctrlr) +{ + return 0; +} + +int +nvme_transport_ctrlr_scan(const struct spdk_nvme_transport_id *trid, + void *cb_ctx, + spdk_nvme_probe_cb probe_cb, + spdk_nvme_remove_cb remove_cb, + bool direct_connect) +{ + return 0; +} + +uint32_t +spdk_nvme_ns_get_max_io_xfer_size(struct spdk_nvme_ns *ns) +{ + return ns->ctrlr->max_xfer_size; +} + +static void +prepare_for_test(struct spdk_nvme_ns *ns, struct spdk_nvme_ctrlr *ctrlr, + struct spdk_nvme_qpair *qpair, + uint32_t sector_size, uint32_t md_size, uint32_t max_xfer_size, + uint32_t stripe_size, bool extended_lba) +{ + uint32_t num_requests = 32; + uint32_t i; + + ctrlr->max_xfer_size = max_xfer_size; + /* + * Clear the flags field - we especially want to make sure the SGL_SUPPORTED flag is not set + * so that we test the SGL splitting path. + */ + ctrlr->flags = 0; + ctrlr->min_page_size = PAGE_SIZE; + ctrlr->page_size = PAGE_SIZE; + memset(&ctrlr->opts, 0, sizeof(ctrlr->opts)); + memset(ns, 0, sizeof(*ns)); + ns->ctrlr = ctrlr; + ns->sector_size = sector_size; + ns->extended_lba_size = sector_size; + if (extended_lba) { + ns->flags |= SPDK_NVME_NS_EXTENDED_LBA_SUPPORTED; + ns->extended_lba_size += md_size; + } + ns->md_size = md_size; + ns->sectors_per_max_io = spdk_nvme_ns_get_max_io_xfer_size(ns) / ns->extended_lba_size; + ns->sectors_per_stripe = stripe_size / ns->extended_lba_size; + + memset(qpair, 0, sizeof(*qpair)); + qpair->ctrlr = ctrlr; + qpair->req_buf = calloc(num_requests, sizeof(struct nvme_request)); + SPDK_CU_ASSERT_FATAL(qpair->req_buf != NULL); + + for (i = 0; i < num_requests; i++) { + struct nvme_request *req = qpair->req_buf + i * sizeof(struct nvme_request); + + STAILQ_INSERT_HEAD(&qpair->free_req, req, stailq); + } + + g_request = NULL; +} + +static void +cleanup_after_test(struct spdk_nvme_qpair *qpair) +{ + free(qpair->req_buf); +} + +static void +test_nvme_ocssd_ns_cmd_vector_reset_single_entry(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false); + uint64_t lba_list = 0x12345678; + spdk_nvme_ocssd_ns_cmd_vector_reset(&ns, &qpair, &lba_list, 1, + NULL, NULL, NULL); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_RESET); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw10 == lba_list); + CU_ASSERT(g_request->cmd.cdw12 == 0); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ocssd_ns_cmd_vector_reset(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + const uint32_t vector_size = 0x10; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false); + uint64_t lba_list[vector_size]; + spdk_nvme_ocssd_ns_cmd_vector_reset(&ns, &qpair, lba_list, vector_size, + NULL, NULL, NULL); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_RESET); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ocssd_ns_cmd_vector_read_with_md_single_entry(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + const uint32_t md_size = 0x80; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + char *buffer = malloc(sector_size); + char *metadata = malloc(md_size); + uint64_t lba_list = 0x12345678; + + SPDK_CU_ASSERT_FATAL(buffer != NULL); + SPDK_CU_ASSERT_FATAL(metadata != NULL); + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, md_size, max_xfer_size, 0, false); + rc = spdk_nvme_ocssd_ns_cmd_vector_read_with_md(&ns, &qpair, buffer, metadata, + &lba_list, 1, NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload.md == metadata); + CU_ASSERT(g_request->payload_size == PAGE_SIZE); + CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer); + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_READ); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw10 == lba_list); + CU_ASSERT(g_request->cmd.cdw12 == 0); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + free(buffer); + free(metadata); +} + +static void +test_nvme_ocssd_ns_cmd_vector_read_with_md(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + const uint32_t md_size = 0x80; + const uint32_t vector_size = 0x10; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + char *buffer = malloc(sector_size * vector_size); + char *metadata = malloc(md_size * vector_size); + uint64_t lba_list[vector_size]; + + SPDK_CU_ASSERT_FATAL(buffer != NULL); + SPDK_CU_ASSERT_FATAL(metadata != NULL); + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, md_size, max_xfer_size, 0, false); + rc = spdk_nvme_ocssd_ns_cmd_vector_read_with_md(&ns, &qpair, buffer, metadata, + lba_list, vector_size, + NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload.md == metadata); + CU_ASSERT(g_request->payload_size == max_xfer_size); + CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer); + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_READ); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + free(buffer); + free(metadata); +} + +static void +test_nvme_ocssd_ns_cmd_vector_read_single_entry(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + char *buffer = malloc(sector_size); + uint64_t lba_list = 0x12345678; + + SPDK_CU_ASSERT_FATAL(buffer != NULL); + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false); + rc = spdk_nvme_ocssd_ns_cmd_vector_read(&ns, &qpair, buffer, &lba_list, 1, + NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload_size == PAGE_SIZE); + CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer); + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_READ); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw10 == lba_list); + CU_ASSERT(g_request->cmd.cdw12 == 0); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + free(buffer); +} + +static void +test_nvme_ocssd_ns_cmd_vector_read(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + const uint32_t vector_size = 0x10; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + char *buffer = malloc(sector_size * vector_size); + uint64_t lba_list[vector_size]; + + SPDK_CU_ASSERT_FATAL(buffer != NULL); + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false); + rc = spdk_nvme_ocssd_ns_cmd_vector_read(&ns, &qpair, buffer, lba_list, vector_size, + NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload_size == max_xfer_size); + CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer); + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_READ); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + free(buffer); +} + +static void +test_nvme_ocssd_ns_cmd_vector_write_with_md_single_entry(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + const uint32_t md_size = 0x80; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + char *buffer = malloc(sector_size); + char *metadata = malloc(md_size); + uint64_t lba_list = 0x12345678; + + SPDK_CU_ASSERT_FATAL(buffer != NULL); + SPDK_CU_ASSERT_FATAL(metadata != NULL); + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, md_size, max_xfer_size, 0, false); + spdk_nvme_ocssd_ns_cmd_vector_write_with_md(&ns, &qpair, buffer, metadata, + &lba_list, 1, NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload.md == metadata); + CU_ASSERT(g_request->payload_size == PAGE_SIZE); + CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer); + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_WRITE); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw10 == lba_list); + CU_ASSERT(g_request->cmd.cdw12 == 0); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + free(buffer); + free(metadata); +} + + +static void +test_nvme_ocssd_ns_cmd_vector_write_with_md(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + const uint32_t md_size = 0x80; + const uint32_t vector_size = 0x10; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + char *buffer = malloc(sector_size * vector_size); + char *metadata = malloc(md_size * vector_size); + uint64_t lba_list[vector_size]; + + SPDK_CU_ASSERT_FATAL(buffer != NULL); + SPDK_CU_ASSERT_FATAL(metadata != NULL); + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, md_size, max_xfer_size, 0, false); + spdk_nvme_ocssd_ns_cmd_vector_write_with_md(&ns, &qpair, buffer, metadata, + lba_list, vector_size, + NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload.md == metadata); + CU_ASSERT(g_request->payload_size == max_xfer_size); + CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer); + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_WRITE); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + free(buffer); + free(metadata); +} + +static void +test_nvme_ocssd_ns_cmd_vector_write_single_entry(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + char *buffer = malloc(sector_size); + uint64_t lba_list = 0x12345678; + + SPDK_CU_ASSERT_FATAL(buffer != NULL); + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false); + spdk_nvme_ocssd_ns_cmd_vector_write(&ns, &qpair, buffer, + &lba_list, 1, NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload_size == PAGE_SIZE); + CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer); + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_WRITE); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw10 == lba_list); + CU_ASSERT(g_request->cmd.cdw12 == 0); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + free(buffer); +} + +static void +test_nvme_ocssd_ns_cmd_vector_write(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + const uint32_t vector_size = 0x10; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + char *buffer = malloc(sector_size * vector_size); + uint64_t lba_list[vector_size]; + + SPDK_CU_ASSERT_FATAL(buffer != NULL); + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false); + spdk_nvme_ocssd_ns_cmd_vector_write(&ns, &qpair, buffer, + lba_list, vector_size, + NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + + CU_ASSERT(g_request->payload_size == max_xfer_size); + CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer); + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_WRITE); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); + + free(buffer); +} + +static void +test_nvme_ocssd_ns_cmd_vector_copy_single_entry(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + uint64_t src_lba_list = 0x12345678; + uint64_t dst_lba_list = 0x87654321; + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false); + spdk_nvme_ocssd_ns_cmd_vector_copy(&ns, &qpair, &dst_lba_list, &src_lba_list, 1, + NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_COPY); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw10 == src_lba_list); + CU_ASSERT(g_request->cmd.cdw12 == 0); + CU_ASSERT(g_request->cmd.cdw14 == dst_lba_list); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +static void +test_nvme_ocssd_ns_cmd_vector_copy(void) +{ + const uint32_t max_xfer_size = 0x10000; + const uint32_t sector_size = 0x1000; + const uint32_t vector_size = 0x10; + + struct spdk_nvme_ns ns; + struct spdk_nvme_ctrlr ctrlr; + struct spdk_nvme_qpair qpair; + + int rc = 0; + + uint64_t src_lba_list[vector_size]; + uint64_t dst_lba_list[vector_size]; + + prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false); + spdk_nvme_ocssd_ns_cmd_vector_copy(&ns, &qpair, + dst_lba_list, src_lba_list, vector_size, + NULL, NULL, 0); + + SPDK_CU_ASSERT_FATAL(rc == 0); + SPDK_CU_ASSERT_FATAL(g_request != NULL); + SPDK_CU_ASSERT_FATAL(g_request->num_children == 0); + CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_COPY); + CU_ASSERT(g_request->cmd.nsid == ns.id); + CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1); + + nvme_free_request(g_request); + cleanup_after_test(&qpair); +} + +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("nvme_ns_cmd", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "nvme_ns_ocssd_cmd_vector_reset", test_nvme_ocssd_ns_cmd_vector_reset) == NULL + || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_reset_single_entry", + test_nvme_ocssd_ns_cmd_vector_reset_single_entry) == NULL + || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_read_with_md", + test_nvme_ocssd_ns_cmd_vector_read_with_md) == NULL + || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_read_with_md_single_entry", + test_nvme_ocssd_ns_cmd_vector_read_with_md_single_entry) == NULL + || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_read", test_nvme_ocssd_ns_cmd_vector_read) == NULL + || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_read_single_entry", + test_nvme_ocssd_ns_cmd_vector_read_single_entry) == NULL + || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_write_with_md", + test_nvme_ocssd_ns_cmd_vector_write_with_md) == NULL + || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_write_with_md_single_entry", + test_nvme_ocssd_ns_cmd_vector_write_with_md_single_entry) == NULL + || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_write", test_nvme_ocssd_ns_cmd_vector_write) == NULL + || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_write_single_entry", + test_nvme_ocssd_ns_cmd_vector_write_single_entry) == NULL + || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_copy", test_nvme_ocssd_ns_cmd_vector_copy) == NULL + || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_copy_single_entry", + test_nvme_ocssd_ns_cmd_vector_copy_single_entry) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + g_spdk_nvme_driver = &_g_nvme_driver; + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvme/nvme_pcie.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_pcie.c/.gitignore new file mode 100644 index 00000000..8fc29109 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_pcie.c/.gitignore @@ -0,0 +1 @@ +nvme_pcie_ut diff --git a/src/spdk/test/unit/lib/nvme/nvme_pcie.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_pcie.c/Makefile new file mode 100644 index 00000000..09032a93 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_pcie.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 = nvme_pcie_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme_pcie.c/nvme_pcie_ut.c b/src/spdk/test/unit/lib/nvme/nvme_pcie.c/nvme_pcie_ut.c new file mode 100644 index 00000000..2bec5865 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_pcie.c/nvme_pcie_ut.c @@ -0,0 +1,861 @@ +/*- + * 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 "common/lib/test_env.c" + +#include "nvme/nvme_pcie.c" + +pid_t g_spdk_nvme_pid; + +DEFINE_STUB(spdk_mem_register, int, (void *vaddr, size_t len), 0); +DEFINE_STUB(spdk_mem_unregister, int, (void *vaddr, size_t len), 0); + +DEFINE_STUB(spdk_nvme_ctrlr_get_process, + struct spdk_nvme_ctrlr_process *, + (struct spdk_nvme_ctrlr *ctrlr, pid_t pid), + NULL); + +DEFINE_STUB(spdk_nvme_ctrlr_get_current_process, + struct spdk_nvme_ctrlr_process *, + (struct spdk_nvme_ctrlr *ctrlr), + NULL); + +DEFINE_STUB(spdk_nvme_wait_for_completion, int, + (struct spdk_nvme_qpair *qpair, + struct nvme_completion_poll_status *status), 0); + +struct spdk_trace_flag SPDK_LOG_NVME = { + .name = "nvme", + .enabled = false, +}; + +static struct nvme_driver _g_nvme_driver = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; +struct nvme_driver *g_spdk_nvme_driver = &_g_nvme_driver; + +int32_t spdk_nvme_retry_count = 1; + +struct nvme_request *g_request = NULL; + +extern bool ut_fail_vtophys; + +bool fail_next_sge = false; + +struct io_request { + uint64_t address_offset; + bool invalid_addr; + bool invalid_second_addr; +}; + +void +nvme_ctrlr_fail(struct spdk_nvme_ctrlr *ctrlr, bool hot_remove) +{ + abort(); +} + +int +spdk_uevent_connect(void) +{ + abort(); +} + +int +spdk_get_uevent(int fd, struct spdk_uevent *uevent) +{ + abort(); +} + +struct spdk_pci_id +spdk_pci_device_get_id(struct spdk_pci_device *dev) +{ + abort(); +} + +int +nvme_qpair_init(struct spdk_nvme_qpair *qpair, uint16_t id, + struct spdk_nvme_ctrlr *ctrlr, + enum spdk_nvme_qprio qprio, + uint32_t num_requests) +{ + abort(); +} + +void +nvme_qpair_deinit(struct spdk_nvme_qpair *qpair) +{ + abort(); +} + +int +spdk_pci_nvme_enumerate(spdk_pci_enum_cb enum_cb, void *enum_ctx) +{ + abort(); +} + +int +spdk_pci_nvme_device_attach(spdk_pci_enum_cb enum_cb, void *enum_ctx, + struct spdk_pci_addr *pci_address) +{ + abort(); +} + +void +spdk_pci_device_detach(struct spdk_pci_device *device) +{ + abort(); +} + +int +spdk_pci_device_map_bar(struct spdk_pci_device *dev, uint32_t bar, + void **mapped_addr, uint64_t *phys_addr, uint64_t *size) +{ + abort(); +} + +int +spdk_pci_device_unmap_bar(struct spdk_pci_device *dev, uint32_t bar, void *addr) +{ + abort(); +} + +struct spdk_pci_addr +spdk_pci_device_get_addr(struct spdk_pci_device *dev) +{ + abort(); +} + +int +spdk_pci_device_cfg_read32(struct spdk_pci_device *dev, uint32_t *value, uint32_t offset) +{ + abort(); +} + +int +spdk_pci_device_cfg_write32(struct spdk_pci_device *dev, uint32_t value, uint32_t offset) +{ + abort(); +} + +int +spdk_pci_device_claim(const struct spdk_pci_addr *pci_addr) +{ + abort(); +} + +int +nvme_ctrlr_construct(struct spdk_nvme_ctrlr *ctrlr) +{ + abort(); +} + +void +nvme_ctrlr_destruct_finish(struct spdk_nvme_ctrlr *ctrlr) +{ + abort(); +} + +void +nvme_ctrlr_destruct(struct spdk_nvme_ctrlr *ctrlr) +{ + abort(); +} + +int +nvme_ctrlr_add_process(struct spdk_nvme_ctrlr *ctrlr, void *devhandle) +{ + abort(); +} + +void +nvme_ctrlr_free_processes(struct spdk_nvme_ctrlr *ctrlr) +{ + abort(); +} + +struct spdk_pci_device * +nvme_ctrlr_proc_get_devhandle(struct spdk_nvme_ctrlr *ctrlr) +{ + abort(); +} + +int +nvme_ctrlr_probe(const struct spdk_nvme_transport_id *trid, void *devhandle, + spdk_nvme_probe_cb probe_cb, void *cb_ctx) +{ + abort(); +} + +int +nvme_ctrlr_get_cap(struct spdk_nvme_ctrlr *ctrlr, union spdk_nvme_cap_register *cap) +{ + abort(); +} + +int +nvme_ctrlr_get_vs(struct spdk_nvme_ctrlr *ctrlr, union spdk_nvme_vs_register *vs) +{ + abort(); +} + +void +nvme_ctrlr_init_cap(struct spdk_nvme_ctrlr *ctrlr, const union spdk_nvme_cap_register *cap, + const union spdk_nvme_vs_register *vs) +{ + abort(); +} + +uint64_t +nvme_get_quirks(const struct spdk_pci_id *id) +{ + abort(); +} + +bool +nvme_completion_is_retry(const struct spdk_nvme_cpl *cpl) +{ + abort(); +} + +void +nvme_qpair_print_command(struct spdk_nvme_qpair *qpair, struct spdk_nvme_cmd *cmd) +{ + abort(); +} + +void +nvme_qpair_print_completion(struct spdk_nvme_qpair *qpair, struct spdk_nvme_cpl *cpl) +{ + abort(); +} + +int +nvme_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req) +{ + abort(); +} + +int +nvme_ctrlr_submit_admin_request(struct spdk_nvme_ctrlr *ctrlr, + struct nvme_request *req) +{ + abort(); +} + +void +nvme_completion_poll_cb(void *arg, const struct spdk_nvme_cpl *cpl) +{ + abort(); +} + +int32_t +spdk_nvme_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions) +{ + abort(); +} + +void +nvme_qpair_enable(struct spdk_nvme_qpair *qpair) +{ + abort(); +} + +int +nvme_request_check_timeout(struct nvme_request *req, uint16_t cid, + struct spdk_nvme_ctrlr_process *active_proc, + uint64_t now_tick) +{ + abort(); +} + +struct spdk_nvme_ctrlr * +spdk_nvme_get_ctrlr_by_trid_unsafe(const struct spdk_nvme_transport_id *trid) +{ + return NULL; +} + +union spdk_nvme_csts_register spdk_nvme_ctrlr_get_regs_csts(struct spdk_nvme_ctrlr *ctrlr) +{ + union spdk_nvme_csts_register csts = {}; + + return csts; +} + +#if 0 /* TODO: update PCIe-specific unit test */ +static void +nvme_request_reset_sgl(void *cb_arg, uint32_t sgl_offset) +{ + struct io_request *req = (struct io_request *)cb_arg; + + req->address_offset = 0; + req->invalid_addr = false; + req->invalid_second_addr = false; + switch (sgl_offset) { + case 0: + req->invalid_addr = false; + break; + case 1: + req->invalid_addr = true; + break; + case 2: + req->invalid_addr = false; + req->invalid_second_addr = true; + break; + default: + break; + } + return; +} + +static int +nvme_request_next_sge(void *cb_arg, void **address, uint32_t *length) +{ + struct io_request *req = (struct io_request *)cb_arg; + + if (req->address_offset == 0) { + if (req->invalid_addr) { + *address = (void *)7; + } else { + *address = (void *)(4096 * req->address_offset); + } + } else if (req->address_offset == 1) { + if (req->invalid_second_addr) { + *address = (void *)7; + } else { + *address = (void *)(4096 * req->address_offset); + } + } else { + *address = (void *)(4096 * req->address_offset); + } + + req->address_offset += 1; + *length = 4096; + + if (fail_next_sge) { + return - 1; + } else { + return 0; + } + +} + +static void +prepare_submit_request_test(struct spdk_nvme_qpair *qpair, + struct spdk_nvme_ctrlr *ctrlr) +{ + memset(ctrlr, 0, sizeof(*ctrlr)); + ctrlr->free_io_qids = NULL; + TAILQ_INIT(&ctrlr->active_io_qpairs); + TAILQ_INIT(&ctrlr->active_procs); + nvme_qpair_init(qpair, 1, ctrlr, 0); + + ut_fail_vtophys = false; +} + +static void +cleanup_submit_request_test(struct spdk_nvme_qpair *qpair) +{ +} + +static void +ut_insert_cq_entry(struct spdk_nvme_qpair *qpair, uint32_t slot) +{ + struct nvme_request *req; + struct nvme_tracker *tr; + struct spdk_nvme_cpl *cpl; + + req = calloc(1, sizeof(*req)); + SPDK_CU_ASSERT_FATAL(req != NULL); + memset(req, 0, sizeof(*req)); + + tr = TAILQ_FIRST(&qpair->free_tr); + TAILQ_REMOVE(&qpair->free_tr, tr, tq_list); /* remove tr from free_tr */ + TAILQ_INSERT_HEAD(&qpair->outstanding_tr, tr, tq_list); + req->cmd.cid = tr->cid; + tr->req = req; + qpair->tr[tr->cid].active = true; + + cpl = &qpair->cpl[slot]; + cpl->status.p = qpair->phase; + cpl->cid = tr->cid; +} + +static void +expected_success_callback(void *arg, const struct spdk_nvme_cpl *cpl) +{ + CU_ASSERT(!spdk_nvme_cpl_is_error(cpl)); +} + +static void +expected_failure_callback(void *arg, const struct spdk_nvme_cpl *cpl) +{ + CU_ASSERT(spdk_nvme_cpl_is_error(cpl)); +} + +static void +test4(void) +{ + struct spdk_nvme_qpair qpair = {}; + struct nvme_request *req; + struct spdk_nvme_ctrlr ctrlr = {}; + char payload[4096]; + + prepare_submit_request_test(&qpair, &ctrlr); + + req = nvme_allocate_request_contig(payload, sizeof(payload), expected_failure_callback, NULL); + SPDK_CU_ASSERT_FATAL(req != NULL); + + /* Force vtophys to return a failure. This should + * result in the nvme_qpair manually failing + * the request with error status to signify + * a bad payload buffer. + */ + ut_fail_vtophys = true; + + CU_ASSERT(qpair.sq_tail == 0); + + CU_ASSERT(nvme_qpair_submit_request(&qpair, req) != 0); + + CU_ASSERT(qpair.sq_tail == 0); + + cleanup_submit_request_test(&qpair); +} + +static void +test_sgl_req(void) +{ + struct spdk_nvme_qpair qpair = {}; + struct nvme_request *req; + struct spdk_nvme_ctrlr ctrlr = {}; + struct nvme_payload payload = {}; + struct nvme_tracker *sgl_tr = NULL; + uint64_t i; + struct io_request io_req = {}; + + payload = NVME_PAYLOAD_SGL(nvme_request_reset_sgl, nvme_request_next_sge, &io_req, NULL); + + prepare_submit_request_test(&qpair, &ctrlr); + req = nvme_allocate_request(&payload, 0x1000, NULL, &io_req); + SPDK_CU_ASSERT_FATAL(req != NULL); + req->cmd.opc = SPDK_NVME_OPC_WRITE; + req->cmd.cdw10 = 10000; + req->cmd.cdw12 = 7 | 0; + req->payload_offset = 1; + + CU_ASSERT(nvme_qpair_submit_request(&qpair, req) != 0); + CU_ASSERT(qpair.sq_tail == 0); + cleanup_submit_request_test(&qpair); + + prepare_submit_request_test(&qpair, &ctrlr); + req = nvme_allocate_request(&payload, 0x1000, NULL, &io_req); + SPDK_CU_ASSERT_FATAL(req != NULL); + req->cmd.opc = SPDK_NVME_OPC_WRITE; + req->cmd.cdw10 = 10000; + req->cmd.cdw12 = 7 | 0; + spdk_nvme_retry_count = 1; + fail_next_sge = true; + + CU_ASSERT(nvme_qpair_submit_request(&qpair, req) != 0); + CU_ASSERT(qpair.sq_tail == 0); + cleanup_submit_request_test(&qpair); + + fail_next_sge = false; + + prepare_submit_request_test(&qpair, &ctrlr); + req = nvme_allocate_request(&payload, 2 * 0x1000, NULL, &io_req); + SPDK_CU_ASSERT_FATAL(req != NULL); + req->cmd.opc = SPDK_NVME_OPC_WRITE; + req->cmd.cdw10 = 10000; + req->cmd.cdw12 = 15 | 0; + req->payload_offset = 2; + + CU_ASSERT(nvme_qpair_submit_request(&qpair, req) != 0); + CU_ASSERT(qpair.sq_tail == 0); + cleanup_submit_request_test(&qpair); + + prepare_submit_request_test(&qpair, &ctrlr); + req = nvme_allocate_request(&payload, (NVME_MAX_PRP_LIST_ENTRIES + 1) * 0x1000, NULL, &io_req); + SPDK_CU_ASSERT_FATAL(req != NULL); + req->cmd.opc = SPDK_NVME_OPC_WRITE; + req->cmd.cdw10 = 10000; + req->cmd.cdw12 = 4095 | 0; + + CU_ASSERT(nvme_qpair_submit_request(&qpair, req) == 0); + + CU_ASSERT(req->cmd.dptr.prp.prp1 == 0); + CU_ASSERT(qpair.sq_tail == 1); + sgl_tr = TAILQ_FIRST(&qpair.outstanding_tr); + if (sgl_tr != NULL) { + for (i = 0; i < NVME_MAX_PRP_LIST_ENTRIES; i++) { + CU_ASSERT(sgl_tr->u.prp[i] == (0x1000 * (i + 1))); + } + + TAILQ_REMOVE(&qpair.outstanding_tr, sgl_tr, tq_list); + } + cleanup_submit_request_test(&qpair); + nvme_free_request(req); +} + +static void +test_hw_sgl_req(void) +{ + struct spdk_nvme_qpair qpair = {}; + struct nvme_request *req; + struct spdk_nvme_ctrlr ctrlr = {}; + struct nvme_payload payload = {}; + struct nvme_tracker *sgl_tr = NULL; + uint64_t i; + struct io_request io_req = {}; + + payload = NVME_PAYLOAD_SGL(nvme_request_reset_sgl, nvme_request_next_sge, &io_req, NULL); + + prepare_submit_request_test(&qpair, &ctrlr); + req = nvme_allocate_request(&payload, 0x1000, NULL, &io_req); + SPDK_CU_ASSERT_FATAL(req != NULL); + req->cmd.opc = SPDK_NVME_OPC_WRITE; + req->cmd.cdw10 = 10000; + req->cmd.cdw12 = 7 | 0; + req->payload_offset = 0; + ctrlr.flags |= SPDK_NVME_CTRLR_SGL_SUPPORTED; + + nvme_qpair_submit_request(&qpair, req); + + sgl_tr = TAILQ_FIRST(&qpair.outstanding_tr); + CU_ASSERT(sgl_tr != NULL); + CU_ASSERT(sgl_tr->u.sgl[0].generic.type == SPDK_NVME_SGL_TYPE_DATA_BLOCK); + CU_ASSERT(sgl_tr->u.sgl[0].generic.subtype == 0); + CU_ASSERT(sgl_tr->u.sgl[0].unkeyed.length == 4096); + CU_ASSERT(sgl_tr->u.sgl[0].address == 0); + CU_ASSERT(req->cmd.dptr.sgl1.generic.type == SPDK_NVME_SGL_TYPE_DATA_BLOCK); + TAILQ_REMOVE(&qpair.outstanding_tr, sgl_tr, tq_list); + cleanup_submit_request_test(&qpair); + nvme_free_request(req); + + prepare_submit_request_test(&qpair, &ctrlr); + req = nvme_allocate_request(&payload, NVME_MAX_SGL_DESCRIPTORS * 0x1000, NULL, &io_req); + SPDK_CU_ASSERT_FATAL(req != NULL); + req->cmd.opc = SPDK_NVME_OPC_WRITE; + req->cmd.cdw10 = 10000; + req->cmd.cdw12 = 2023 | 0; + req->payload_offset = 0; + ctrlr.flags |= SPDK_NVME_CTRLR_SGL_SUPPORTED; + + nvme_qpair_submit_request(&qpair, req); + + sgl_tr = TAILQ_FIRST(&qpair.outstanding_tr); + CU_ASSERT(sgl_tr != NULL); + for (i = 0; i < NVME_MAX_SGL_DESCRIPTORS; i++) { + CU_ASSERT(sgl_tr->u.sgl[i].generic.type == SPDK_NVME_SGL_TYPE_DATA_BLOCK); + CU_ASSERT(sgl_tr->u.sgl[i].generic.subtype == 0); + CU_ASSERT(sgl_tr->u.sgl[i].unkeyed.length == 4096); + CU_ASSERT(sgl_tr->u.sgl[i].address == i * 4096); + } + CU_ASSERT(req->cmd.dptr.sgl1.generic.type == SPDK_NVME_SGL_TYPE_LAST_SEGMENT); + TAILQ_REMOVE(&qpair.outstanding_tr, sgl_tr, tq_list); + cleanup_submit_request_test(&qpair); + nvme_free_request(req); +} + +static void test_nvme_qpair_fail(void) +{ + struct spdk_nvme_qpair qpair = {}; + struct nvme_request *req = NULL; + struct spdk_nvme_ctrlr ctrlr = {}; + struct nvme_tracker *tr_temp; + + prepare_submit_request_test(&qpair, &ctrlr); + + tr_temp = TAILQ_FIRST(&qpair.free_tr); + SPDK_CU_ASSERT_FATAL(tr_temp != NULL); + TAILQ_REMOVE(&qpair.free_tr, tr_temp, tq_list); + tr_temp->req = nvme_allocate_request_null(expected_failure_callback, NULL); + SPDK_CU_ASSERT_FATAL(tr_temp->req != NULL); + tr_temp->req->cmd.cid = tr_temp->cid; + + TAILQ_INSERT_HEAD(&qpair.outstanding_tr, tr_temp, tq_list); + nvme_qpair_fail(&qpair); + CU_ASSERT_TRUE(TAILQ_EMPTY(&qpair.outstanding_tr)); + + req = nvme_allocate_request_null(expected_failure_callback, NULL); + SPDK_CU_ASSERT_FATAL(req != NULL); + + STAILQ_INSERT_HEAD(&qpair.queued_req, req, stailq); + nvme_qpair_fail(&qpair); + CU_ASSERT_TRUE(STAILQ_EMPTY(&qpair.queued_req)); + + cleanup_submit_request_test(&qpair); +} + +static void +test_nvme_qpair_process_completions_limit(void) +{ + struct spdk_nvme_qpair qpair = {}; + struct spdk_nvme_ctrlr ctrlr = {}; + + prepare_submit_request_test(&qpair, &ctrlr); + qpair.is_enabled = true; + + /* Insert 4 entries into the completion queue */ + CU_ASSERT(qpair.cq_head == 0); + ut_insert_cq_entry(&qpair, 0); + ut_insert_cq_entry(&qpair, 1); + ut_insert_cq_entry(&qpair, 2); + ut_insert_cq_entry(&qpair, 3); + + /* This should only process 2 completions, and 2 should be left in the queue */ + spdk_nvme_qpair_process_completions(&qpair, 2); + CU_ASSERT(qpair.cq_head == 2); + + /* This should only process 1 completion, and 1 should be left in the queue */ + spdk_nvme_qpair_process_completions(&qpair, 1); + CU_ASSERT(qpair.cq_head == 3); + + /* This should process the remaining completion */ + spdk_nvme_qpair_process_completions(&qpair, 5); + CU_ASSERT(qpair.cq_head == 4); + + cleanup_submit_request_test(&qpair); +} + +static void test_nvme_qpair_destroy(void) +{ + struct spdk_nvme_qpair qpair = {}; + struct spdk_nvme_ctrlr ctrlr = {}; + struct nvme_tracker *tr_temp; + + memset(&ctrlr, 0, sizeof(ctrlr)); + TAILQ_INIT(&ctrlr.free_io_qpairs); + TAILQ_INIT(&ctrlr.active_io_qpairs); + TAILQ_INIT(&ctrlr.active_procs); + + nvme_qpair_init(&qpair, 1, 128, &ctrlr); + nvme_qpair_destroy(&qpair); + + + nvme_qpair_init(&qpair, 0, 128, &ctrlr); + tr_temp = TAILQ_FIRST(&qpair.free_tr); + SPDK_CU_ASSERT_FATAL(tr_temp != NULL); + TAILQ_REMOVE(&qpair.free_tr, tr_temp, tq_list); + tr_temp->req = nvme_allocate_request_null(expected_failure_callback, NULL); + SPDK_CU_ASSERT_FATAL(tr_temp->req != NULL); + + tr_temp->req->cmd.opc = SPDK_NVME_OPC_ASYNC_EVENT_REQUEST; + tr_temp->req->cmd.cid = tr_temp->cid; + TAILQ_INSERT_HEAD(&qpair.outstanding_tr, tr_temp, tq_list); + + nvme_qpair_destroy(&qpair); + CU_ASSERT(TAILQ_EMPTY(&qpair.outstanding_tr)); +} +#endif + +static void +prp_list_prep(struct nvme_tracker *tr, struct nvme_request *req, uint32_t *prp_index) +{ + memset(req, 0, sizeof(*req)); + memset(tr, 0, sizeof(*tr)); + tr->req = req; + tr->prp_sgl_bus_addr = 0xDEADBEEF; + *prp_index = 0; +} + +static void +test_prp_list_append(void) +{ + struct nvme_request req; + struct nvme_tracker tr; + uint32_t prp_index; + + /* Non-DWORD-aligned buffer (invalid) */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100001, 0x1000, 0x1000) == -EINVAL); + + /* 512-byte buffer, 4K aligned */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x200, 0x1000) == 0); + CU_ASSERT(prp_index == 1); + CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100000); + + /* 512-byte buffer, non-4K-aligned */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x108000, 0x200, 0x1000) == 0); + CU_ASSERT(prp_index == 1); + CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x108000); + + /* 4K buffer, 4K aligned */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x1000, 0x1000) == 0); + CU_ASSERT(prp_index == 1); + CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100000); + + /* 4K buffer, non-4K aligned */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, 0x1000, 0x1000) == 0); + CU_ASSERT(prp_index == 2); + CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100800); + CU_ASSERT(req.cmd.dptr.prp.prp2 == 0x101000); + + /* 8K buffer, 4K aligned */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x2000, 0x1000) == 0); + CU_ASSERT(prp_index == 2); + CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100000); + CU_ASSERT(req.cmd.dptr.prp.prp2 == 0x101000); + + /* 8K buffer, non-4K aligned */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, 0x2000, 0x1000) == 0); + CU_ASSERT(prp_index == 3); + CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100800); + CU_ASSERT(req.cmd.dptr.prp.prp2 == tr.prp_sgl_bus_addr); + CU_ASSERT(tr.u.prp[0] == 0x101000); + CU_ASSERT(tr.u.prp[1] == 0x102000); + + /* 12K buffer, 4K aligned */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x3000, 0x1000) == 0); + CU_ASSERT(prp_index == 3); + CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100000); + CU_ASSERT(req.cmd.dptr.prp.prp2 == tr.prp_sgl_bus_addr); + CU_ASSERT(tr.u.prp[0] == 0x101000); + CU_ASSERT(tr.u.prp[1] == 0x102000); + + /* 12K buffer, non-4K aligned */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, 0x3000, 0x1000) == 0); + CU_ASSERT(prp_index == 4); + CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100800); + CU_ASSERT(req.cmd.dptr.prp.prp2 == tr.prp_sgl_bus_addr); + CU_ASSERT(tr.u.prp[0] == 0x101000); + CU_ASSERT(tr.u.prp[1] == 0x102000); + CU_ASSERT(tr.u.prp[2] == 0x103000); + + /* Two 4K buffers, both 4K aligned */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x1000, 0x1000) == 0); + CU_ASSERT(prp_index == 1); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x900000, 0x1000, 0x1000) == 0); + CU_ASSERT(prp_index == 2); + CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100000); + CU_ASSERT(req.cmd.dptr.prp.prp2 == 0x900000); + + /* Two 4K buffers, first non-4K aligned, second 4K aligned */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, 0x1000, 0x1000) == 0); + CU_ASSERT(prp_index == 2); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x900000, 0x1000, 0x1000) == 0); + CU_ASSERT(prp_index == 3); + CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100800); + CU_ASSERT(req.cmd.dptr.prp.prp2 == tr.prp_sgl_bus_addr); + CU_ASSERT(tr.u.prp[0] == 0x101000); + CU_ASSERT(tr.u.prp[1] == 0x900000); + + /* Two 4K buffers, both non-4K aligned (invalid) */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, 0x1000, 0x1000) == 0); + CU_ASSERT(prp_index == 2); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x900800, 0x1000, 0x1000) == -EINVAL); + CU_ASSERT(prp_index == 2); + + /* 4K buffer, 4K aligned, but vtophys fails */ + MOCK_SET(spdk_vtophys, SPDK_VTOPHYS_ERROR); + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x1000, 0x1000) == -EINVAL); + MOCK_CLEAR(spdk_vtophys); + + /* Largest aligned buffer that can be described in NVME_MAX_PRP_LIST_ENTRIES (plus PRP1) */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, + (NVME_MAX_PRP_LIST_ENTRIES + 1) * 0x1000, 0x1000) == 0); + CU_ASSERT(prp_index == NVME_MAX_PRP_LIST_ENTRIES + 1); + + /* Largest non-4K-aligned buffer that can be described in NVME_MAX_PRP_LIST_ENTRIES (plus PRP1) */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, + NVME_MAX_PRP_LIST_ENTRIES * 0x1000, 0x1000) == 0); + CU_ASSERT(prp_index == NVME_MAX_PRP_LIST_ENTRIES + 1); + + /* Buffer too large to be described in NVME_MAX_PRP_LIST_ENTRIES */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, + (NVME_MAX_PRP_LIST_ENTRIES + 2) * 0x1000, 0x1000) == -EINVAL); + + /* Non-4K-aligned buffer too large to be described in NVME_MAX_PRP_LIST_ENTRIES */ + prp_list_prep(&tr, &req, &prp_index); + CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, + (NVME_MAX_PRP_LIST_ENTRIES + 1) * 0x1000, 0x1000) == -EINVAL); +} + +static void test_shadow_doorbell_update(void) +{ + bool ret; + + /* nvme_pcie_qpair_need_event(uint16_t event_idx, uint16_t new_idx, uint16_t old) */ + ret = nvme_pcie_qpair_need_event(10, 15, 14); + CU_ASSERT(ret == false); + + ret = nvme_pcie_qpair_need_event(14, 15, 14); + CU_ASSERT(ret == true); +} + +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("nvme_pcie", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if (CU_add_test(suite, "prp_list_append", test_prp_list_append) == NULL + || CU_add_test(suite, "shadow_doorbell_update", + test_shadow_doorbell_update) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvme/nvme_qpair.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_qpair.c/.gitignore new file mode 100644 index 00000000..1bb18e99 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_qpair.c/.gitignore @@ -0,0 +1 @@ +nvme_qpair_ut diff --git a/src/spdk/test/unit/lib/nvme/nvme_qpair.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_qpair.c/Makefile new file mode 100644 index 00000000..d7762a38 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_qpair.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 = nvme_qpair_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme_qpair.c/nvme_qpair_ut.c b/src/spdk/test/unit/lib/nvme/nvme_qpair.c/nvme_qpair_ut.c new file mode 100644 index 00000000..11fea8c7 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_qpair.c/nvme_qpair_ut.c @@ -0,0 +1,418 @@ +/*- + * 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 "common/lib/test_env.c" + +pid_t g_spdk_nvme_pid; + +bool trace_flag = false; +#define SPDK_LOG_NVME trace_flag + +#include "nvme/nvme_qpair.c" + +struct nvme_driver _g_nvme_driver = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +void +nvme_request_remove_child(struct nvme_request *parent, + struct nvme_request *child) +{ + parent->num_children--; + TAILQ_REMOVE(&parent->children, child, child_tailq); +} + +int +nvme_transport_qpair_enable(struct spdk_nvme_qpair *qpair) +{ + return 0; +} + +int +nvme_transport_qpair_disable(struct spdk_nvme_qpair *qpair) +{ + return 0; +} + +int +nvme_transport_qpair_fail(struct spdk_nvme_qpair *qpair) +{ + return 0; +} + +int +nvme_transport_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req) +{ + // TODO + return 0; +} + +int32_t +nvme_transport_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions) +{ + // TODO + return 0; +} + +int +spdk_nvme_ctrlr_free_io_qpair(struct spdk_nvme_qpair *qpair) +{ + return 0; +} + +static void +prepare_submit_request_test(struct spdk_nvme_qpair *qpair, + struct spdk_nvme_ctrlr *ctrlr) +{ + memset(ctrlr, 0, sizeof(*ctrlr)); + ctrlr->free_io_qids = NULL; + TAILQ_INIT(&ctrlr->active_io_qpairs); + TAILQ_INIT(&ctrlr->active_procs); + nvme_qpair_init(qpair, 1, ctrlr, 0, 32); +} + +static void +cleanup_submit_request_test(struct spdk_nvme_qpair *qpair) +{ + free(qpair->req_buf); +} + +static void +expected_success_callback(void *arg, const struct spdk_nvme_cpl *cpl) +{ + CU_ASSERT(!spdk_nvme_cpl_is_error(cpl)); +} + +static void +expected_failure_callback(void *arg, const struct spdk_nvme_cpl *cpl) +{ + CU_ASSERT(spdk_nvme_cpl_is_error(cpl)); +} + +static void +test3(void) +{ + struct spdk_nvme_qpair qpair = {}; + struct nvme_request *req; + struct spdk_nvme_ctrlr ctrlr = {}; + + prepare_submit_request_test(&qpair, &ctrlr); + + req = nvme_allocate_request_null(&qpair, expected_success_callback, NULL); + SPDK_CU_ASSERT_FATAL(req != NULL); + + CU_ASSERT(nvme_qpair_submit_request(&qpair, req) == 0); + + nvme_free_request(req); + + cleanup_submit_request_test(&qpair); +} + +static void +test_ctrlr_failed(void) +{ + struct spdk_nvme_qpair qpair = {}; + struct nvme_request *req; + struct spdk_nvme_ctrlr ctrlr = {}; + char payload[4096]; + + prepare_submit_request_test(&qpair, &ctrlr); + + req = nvme_allocate_request_contig(&qpair, payload, sizeof(payload), expected_failure_callback, + NULL); + SPDK_CU_ASSERT_FATAL(req != NULL); + + /* Set the controller to failed. + * Set the controller to resetting so that the qpair won't get re-enabled. + */ + ctrlr.is_failed = true; + ctrlr.is_resetting = true; + + CU_ASSERT(nvme_qpair_submit_request(&qpair, req) != 0); + + cleanup_submit_request_test(&qpair); +} + +static void struct_packing(void) +{ + /* ctrlr is the first field in nvme_qpair after the fields + * that are used in the I/O path. Make sure the I/O path fields + * all fit into two cache lines. + */ + CU_ASSERT(offsetof(struct spdk_nvme_qpair, ctrlr) <= 128); +} + +static void test_nvme_qpair_process_completions(void) +{ + struct spdk_nvme_qpair qpair = {}; + struct spdk_nvme_ctrlr ctrlr = {}; + + prepare_submit_request_test(&qpair, &ctrlr); + qpair.ctrlr->is_resetting = true; + + spdk_nvme_qpair_process_completions(&qpair, 0); + cleanup_submit_request_test(&qpair); +} + +static void test_nvme_completion_is_retry(void) +{ + struct spdk_nvme_cpl cpl = {}; + + cpl.status.sct = SPDK_NVME_SCT_GENERIC; + cpl.status.sc = SPDK_NVME_SC_NAMESPACE_NOT_READY; + cpl.status.dnr = 0; + CU_ASSERT_TRUE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_FORMAT_IN_PROGRESS; + cpl.status.dnr = 1; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + cpl.status.dnr = 0; + CU_ASSERT_TRUE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_INVALID_OPCODE; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_INVALID_FIELD; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_COMMAND_ID_CONFLICT; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_DATA_TRANSFER_ERROR; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_ABORTED_POWER_LOSS; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_INTERNAL_DEVICE_ERROR; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_ABORTED_BY_REQUEST; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_ABORTED_FAILED_FUSED; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_ABORTED_MISSING_FUSED; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_COMMAND_SEQUENCE_ERROR; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_INVALID_SGL_SEG_DESCRIPTOR; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_INVALID_NUM_SGL_DESCIRPTORS; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_DATA_SGL_LENGTH_INVALID; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_METADATA_SGL_LENGTH_INVALID; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_SGL_DESCRIPTOR_TYPE_INVALID; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_INVALID_CONTROLLER_MEM_BUF; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_INVALID_PRP_OFFSET; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_ATOMIC_WRITE_UNIT_EXCEEDED; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_LBA_OUT_OF_RANGE; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_CAPACITY_EXCEEDED; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = SPDK_NVME_SC_RESERVATION_CONFLICT; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sc = 0x70; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sct = SPDK_NVME_SCT_MEDIA_ERROR; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sct = SPDK_NVME_SCT_PATH; + cpl.status.sc = SPDK_NVME_SC_INTERNAL_PATH_ERROR; + cpl.status.dnr = 0; + CU_ASSERT_TRUE(nvme_completion_is_retry(&cpl)); + + cpl.status.sct = SPDK_NVME_SCT_PATH; + cpl.status.sc = SPDK_NVME_SC_INTERNAL_PATH_ERROR; + cpl.status.dnr = 1; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sct = SPDK_NVME_SCT_VENDOR_SPECIFIC; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); + + cpl.status.sct = 0x4; + CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl)); +} + +#ifdef DEBUG +static void +test_get_status_string(void) +{ + const char *status_string; + + status_string = get_status_string(SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_SUCCESS); + CU_ASSERT(strcmp(status_string, "SUCCESS") == 0); + + status_string = get_status_string(SPDK_NVME_SCT_COMMAND_SPECIFIC, + SPDK_NVME_SC_COMPLETION_QUEUE_INVALID); + CU_ASSERT(strcmp(status_string, "INVALID COMPLETION QUEUE") == 0); + + status_string = get_status_string(SPDK_NVME_SCT_MEDIA_ERROR, SPDK_NVME_SC_UNRECOVERED_READ_ERROR); + CU_ASSERT(strcmp(status_string, "UNRECOVERED READ ERROR") == 0); + + status_string = get_status_string(SPDK_NVME_SCT_VENDOR_SPECIFIC, 0); + CU_ASSERT(strcmp(status_string, "VENDOR SPECIFIC") == 0); + + status_string = get_status_string(100, 0); + CU_ASSERT(strcmp(status_string, "RESERVED") == 0); +} +#endif + +static void +test_nvme_qpair_add_cmd_error_injection(void) +{ + struct spdk_nvme_qpair qpair = {}; + struct spdk_nvme_ctrlr ctrlr = {}; + int rc; + + prepare_submit_request_test(&qpair, &ctrlr); + ctrlr.adminq = &qpair; + + /* Admin error injection at submission path */ + rc = spdk_nvme_qpair_add_cmd_error_injection(&ctrlr, NULL, + SPDK_NVME_OPC_GET_FEATURES, true, 5000, 1, + SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_INVALID_FIELD); + + CU_ASSERT(rc == 0); + CU_ASSERT(!TAILQ_EMPTY(&qpair.err_cmd_head)); + + /* Remove cmd error injection */ + spdk_nvme_qpair_remove_cmd_error_injection(&ctrlr, NULL, SPDK_NVME_OPC_GET_FEATURES); + + CU_ASSERT(TAILQ_EMPTY(&qpair.err_cmd_head)); + + /* IO error injection at completion path */ + rc = spdk_nvme_qpair_add_cmd_error_injection(&ctrlr, &qpair, + SPDK_NVME_OPC_READ, false, 0, 1, + SPDK_NVME_SCT_MEDIA_ERROR, SPDK_NVME_SC_UNRECOVERED_READ_ERROR); + + CU_ASSERT(rc == 0); + CU_ASSERT(!TAILQ_EMPTY(&qpair.err_cmd_head)); + + /* Provide the same opc, and check whether allocate a new entry */ + rc = spdk_nvme_qpair_add_cmd_error_injection(&ctrlr, &qpair, + SPDK_NVME_OPC_READ, false, 0, 1, + SPDK_NVME_SCT_MEDIA_ERROR, SPDK_NVME_SC_UNRECOVERED_READ_ERROR); + + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&qpair.err_cmd_head)); + CU_ASSERT(TAILQ_NEXT(TAILQ_FIRST(&qpair.err_cmd_head), link) == NULL); + + /* Remove cmd error injection */ + spdk_nvme_qpair_remove_cmd_error_injection(&ctrlr, &qpair, SPDK_NVME_OPC_READ); + + CU_ASSERT(TAILQ_EMPTY(&qpair.err_cmd_head)); + + rc = spdk_nvme_qpair_add_cmd_error_injection(&ctrlr, &qpair, + SPDK_NVME_OPC_COMPARE, true, 0, 5, + SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_COMPARE_FAILURE); + + CU_ASSERT(rc == 0); + CU_ASSERT(!TAILQ_EMPTY(&qpair.err_cmd_head)); + + /* Remove cmd error injection */ + spdk_nvme_qpair_remove_cmd_error_injection(&ctrlr, &qpair, SPDK_NVME_OPC_COMPARE); + + CU_ASSERT(TAILQ_EMPTY(&qpair.err_cmd_head)); + + cleanup_submit_request_test(&qpair); +} + +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("nvme_qpair", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if (CU_add_test(suite, "test3", test3) == NULL + || CU_add_test(suite, "ctrlr_failed", test_ctrlr_failed) == NULL + || CU_add_test(suite, "struct_packing", struct_packing) == NULL + || CU_add_test(suite, "spdk_nvme_qpair_process_completions", + test_nvme_qpair_process_completions) == NULL + || CU_add_test(suite, "nvme_completion_is_retry", test_nvme_completion_is_retry) == NULL +#ifdef DEBUG + || CU_add_test(suite, "get_status_string", test_get_status_string) == NULL +#endif + || CU_add_test(suite, "spdk_nvme_qpair_add_cmd_error_injection", + test_nvme_qpair_add_cmd_error_injection) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvme/nvme_quirks.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_quirks.c/.gitignore new file mode 100644 index 00000000..eca86651 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_quirks.c/.gitignore @@ -0,0 +1 @@ +nvme_quirks_ut diff --git a/src/spdk/test/unit/lib/nvme/nvme_quirks.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_quirks.c/Makefile new file mode 100644 index 00000000..d86887f0 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_quirks.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 = nvme_quirks_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme_quirks.c/nvme_quirks_ut.c b/src/spdk/test/unit/lib/nvme/nvme_quirks.c/nvme_quirks_ut.c new file mode 100644 index 00000000..95fdd143 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_quirks.c/nvme_quirks_ut.c @@ -0,0 +1,102 @@ +/*- + * 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_cunit.h" + +#include "nvme/nvme_quirks.c" + +SPDK_LOG_REGISTER_COMPONENT("nvme", SPDK_LOG_NVME) + +static void +test_nvme_quirks_striping(void) +{ + struct spdk_pci_id pci_id = {}; + uint64_t quirks = 0; + + /* Non-Intel device should not have striping enabled */ + quirks = nvme_get_quirks(&pci_id); + CU_ASSERT((quirks & NVME_INTEL_QUIRK_STRIPING) == 0); + + /* Set the vendor id to Intel, but no device id. No striping. */ + pci_id.vendor_id = SPDK_PCI_VID_INTEL; + quirks = nvme_get_quirks(&pci_id); + CU_ASSERT((quirks & NVME_INTEL_QUIRK_STRIPING) == 0); + + /* Device ID 0x0953 should have striping enabled */ + pci_id.device_id = 0x0953; + quirks = nvme_get_quirks(&pci_id); + CU_ASSERT((quirks & NVME_INTEL_QUIRK_STRIPING) != 0); + + /* Even if specific subvendor/subdevice ids are set, + * striping should be enabled. + */ + pci_id.subvendor_id = SPDK_PCI_VID_INTEL; + pci_id.subdevice_id = 0x3704; + quirks = nvme_get_quirks(&pci_id); + CU_ASSERT((quirks & NVME_INTEL_QUIRK_STRIPING) != 0); + + pci_id.subvendor_id = 1234; + pci_id.subdevice_id = 42; + quirks = nvme_get_quirks(&pci_id); + CU_ASSERT((quirks & NVME_INTEL_QUIRK_STRIPING) != 0); +} + +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("nvme_quirks", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test nvme_quirks striping", + test_nvme_quirks_striping) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvme/nvme_rdma.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_rdma.c/.gitignore new file mode 100644 index 00000000..66265b95 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_rdma.c/.gitignore @@ -0,0 +1 @@ +nvme_rdma_ut diff --git a/src/spdk/test/unit/lib/nvme/nvme_rdma.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_rdma.c/Makefile new file mode 100644 index 00000000..7ea42632 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_rdma.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 = nvme_rdma_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvme/nvme_rdma.c/nvme_rdma_ut.c b/src/spdk/test/unit/lib/nvme/nvme_rdma.c/nvme_rdma_ut.c new file mode 100644 index 00000000..87835ab6 --- /dev/null +++ b/src/spdk/test/unit/lib/nvme/nvme_rdma.c/nvme_rdma_ut.c @@ -0,0 +1,298 @@ +/*- + * 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 "common/lib/test_env.c" +#include "nvme/nvme_rdma.c" + +SPDK_LOG_REGISTER_COMPONENT("nvme", SPDK_LOG_NVME) + +DEFINE_STUB(nvme_qpair_submit_request, int, (struct spdk_nvme_qpair *qpair, + struct nvme_request *req), 0); + +DEFINE_STUB(nvme_qpair_init, int, (struct spdk_nvme_qpair *qpair, uint16_t id, + struct spdk_nvme_ctrlr *ctrlr, enum spdk_nvme_qprio qprio, uint32_t num_requests), 0); + +DEFINE_STUB_V(nvme_qpair_deinit, (struct spdk_nvme_qpair *qpair)); + +DEFINE_STUB(nvme_ctrlr_probe, int, (const struct spdk_nvme_transport_id *trid, void *devhandle, + spdk_nvme_probe_cb probe_cb, void *cb_ctx), 0); + +DEFINE_STUB(nvme_ctrlr_get_cap, int, (struct spdk_nvme_ctrlr *ctrlr, + union spdk_nvme_cap_register *cap), 0); + +DEFINE_STUB(nvme_ctrlr_get_vs, int, (struct spdk_nvme_ctrlr *ctrlr, + union spdk_nvme_vs_register *vs), 0); + +DEFINE_STUB_V(nvme_ctrlr_init_cap, (struct spdk_nvme_ctrlr *ctrlr, + const union spdk_nvme_cap_register *cap, const union spdk_nvme_vs_register *vs)); + +DEFINE_STUB(nvme_ctrlr_construct, int, (struct spdk_nvme_ctrlr *ctrlr), 0); + +DEFINE_STUB_V(nvme_ctrlr_destruct, (struct spdk_nvme_ctrlr *ctrlr)); + +DEFINE_STUB(nvme_ctrlr_add_process, int, (struct spdk_nvme_ctrlr *ctrlr, void *devhandle), 0); + +DEFINE_STUB_V(nvme_ctrlr_connected, (struct spdk_nvme_ctrlr *ctrlr)); + +DEFINE_STUB(nvme_ctrlr_cmd_identify, int, (struct spdk_nvme_ctrlr *ctrlr, uint8_t cns, + uint16_t cntid, uint32_t nsid, void *payload, size_t payload_size, spdk_nvme_cmd_cb cb_fn, + void *cb_arg), 0); + +DEFINE_STUB_V(spdk_nvme_ctrlr_get_default_ctrlr_opts, (struct spdk_nvme_ctrlr_opts *opts, + size_t opts_size)); + +DEFINE_STUB_V(nvme_completion_poll_cb, (void *arg, const struct spdk_nvme_cpl *cpl)); + +DEFINE_STUB(spdk_nvme_ctrlr_get_current_process, struct spdk_nvme_ctrlr_process *, + (struct spdk_nvme_ctrlr *ctrlr), NULL); + +DEFINE_STUB(spdk_nvme_wait_for_completion, int, (struct spdk_nvme_qpair *qpair, + struct nvme_completion_poll_status *status), 0); + +DEFINE_STUB(spdk_nvme_wait_for_completion_robust_lock, int, (struct spdk_nvme_qpair *qpair, + struct nvme_completion_poll_status *status, pthread_mutex_t *robust_mutex), 0); + +DEFINE_STUB(spdk_mem_map_set_translation, int, (struct spdk_mem_map *map, uint64_t vaddr, + uint64_t size, uint64_t translation), 0); + +DEFINE_STUB(spdk_mem_map_clear_translation, int, (struct spdk_mem_map *map, uint64_t vaddr, + uint64_t size), 0); + +DEFINE_STUB(spdk_mem_map_alloc, struct spdk_mem_map *, (uint64_t default_translation, + const struct spdk_mem_map_ops *ops, void *cb_ctx), NULL); + +DEFINE_STUB_V(spdk_mem_map_free, (struct spdk_mem_map **pmap)); + +DEFINE_STUB(nvme_fabric_qpair_connect, int, (struct spdk_nvme_qpair *qpair, uint32_t num_entries), + 0); + +DEFINE_STUB(nvme_transport_ctrlr_set_reg_4, int, (struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, + uint32_t value), 0); + +DEFINE_STUB(nvme_fabric_ctrlr_set_reg_4, int, (struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, + uint32_t value), 0); + +DEFINE_STUB(nvme_fabric_ctrlr_set_reg_8, int, (struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, + uint64_t value), 0); + +DEFINE_STUB(nvme_fabric_ctrlr_get_reg_4, int, (struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, + uint32_t *value), 0); + +DEFINE_STUB(nvme_fabric_ctrlr_get_reg_8, int, (struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, + uint64_t *value), 0); + +DEFINE_STUB_V(nvme_ctrlr_destruct_finish, (struct spdk_nvme_ctrlr *ctrlr)); + +DEFINE_STUB(nvme_request_check_timeout, int, (struct nvme_request *req, uint16_t cid, + struct spdk_nvme_ctrlr_process *active_proc, uint64_t now_tick), 0); + +DEFINE_STUB(nvme_fabric_ctrlr_discover, int, (struct spdk_nvme_ctrlr *ctrlr, void *cb_ctx, + spdk_nvme_probe_cb probe_cb), 0); + +/* used to mock out having to split an SGL over a memory region */ +uint64_t g_mr_size; +struct ibv_mr g_nvme_rdma_mr; + +uint64_t +spdk_mem_map_translate(const struct spdk_mem_map *map, uint64_t vaddr, uint64_t *size) +{ + if (g_mr_size != 0) { + *(uint32_t *)size = g_mr_size; + } + + return (uint64_t)&g_nvme_rdma_mr; +} + +struct nvme_rdma_ut_bdev_io { + struct iovec iovs[NVME_RDMA_MAX_SGL_DESCRIPTORS]; + int iovpos; +}; + +/* essentially a simplification of bdev_nvme_next_sge and bdev_nvme_reset_sgl */ +static void nvme_rdma_ut_reset_sgl(void *cb_arg, uint32_t offset) +{ + struct nvme_rdma_ut_bdev_io *bio = cb_arg; + struct iovec *iov; + + for (bio->iovpos = 0; bio->iovpos < NVME_RDMA_MAX_SGL_DESCRIPTORS; bio->iovpos++) { + iov = &bio->iovs[bio->iovpos]; + /* Only provide offsets at the beginning of an iov */ + if (offset == 0) { + break; + } + + offset -= iov->iov_len; + } + + SPDK_CU_ASSERT_FATAL(bio->iovpos < NVME_RDMA_MAX_SGL_DESCRIPTORS); +} + +static int nvme_rdma_ut_next_sge(void *cb_arg, void **address, uint32_t *length) +{ + struct nvme_rdma_ut_bdev_io *bio = cb_arg; + struct iovec *iov; + + SPDK_CU_ASSERT_FATAL(bio->iovpos < NVME_RDMA_MAX_SGL_DESCRIPTORS); + + iov = &bio->iovs[bio->iovpos]; + + *address = iov->iov_base; + *length = iov->iov_len; + bio->iovpos++; + + return 0; +} + +static void +test_nvme_rdma_build_sgl_request(void) +{ + struct nvme_rdma_qpair rqpair; + struct spdk_nvme_ctrlr ctrlr = {0}; + struct spdk_nvmf_cmd cmd = {{0}}; + struct spdk_nvme_rdma_req rdma_req = {0}; + struct nvme_request req = {{0}}; + struct nvme_rdma_ut_bdev_io bio; + struct spdk_nvme_rdma_mr_map rmap = {0}; + struct spdk_mem_map *map = NULL; + uint64_t i; + int rc; + + rmap.map = map; + + ctrlr.max_sges = NVME_RDMA_MAX_SGL_DESCRIPTORS; + + rqpair.mr_map = &rmap; + rqpair.qpair.ctrlr = &ctrlr; + rqpair.cmds = &cmd; + cmd.sgl[0].address = 0x1111; + + rdma_req.id = 0; + rdma_req.req = &req; + + req.payload.reset_sgl_fn = nvme_rdma_ut_reset_sgl; + req.payload.next_sge_fn = nvme_rdma_ut_next_sge; + req.payload.contig_or_cb_arg = &bio; + req.qpair = &rqpair.qpair; + + g_nvme_rdma_mr.rkey = 1; + + for (i = 0; i < NVME_RDMA_MAX_SGL_DESCRIPTORS; i++) { + bio.iovs[i].iov_base = (void *)i; + bio.iovs[i].iov_len = 0; + } + + /* Test case 1: single SGL. Expected: PASS */ + bio.iovpos = 0; + req.payload_offset = 0; + req.payload_size = 0x1000; + bio.iovs[0].iov_len = 0x1000; + rc = nvme_rdma_build_sgl_request(&rqpair, &rdma_req); + SPDK_CU_ASSERT_FATAL(rc == 0); + CU_ASSERT(bio.iovpos == 1); + CU_ASSERT(req.cmd.dptr.sgl1.keyed.type == SPDK_NVME_SGL_TYPE_KEYED_DATA_BLOCK); + CU_ASSERT(req.cmd.dptr.sgl1.keyed.subtype == SPDK_NVME_SGL_SUBTYPE_ADDRESS); + CU_ASSERT(req.cmd.dptr.sgl1.keyed.length == req.payload_size); + CU_ASSERT(req.cmd.dptr.sgl1.keyed.key == g_nvme_rdma_mr.rkey); + CU_ASSERT(req.cmd.dptr.sgl1.address == (uint64_t)bio.iovs[0].iov_base); + CU_ASSERT(rdma_req.send_sgl[0].length == sizeof(struct spdk_nvme_cmd)); + + /* Test case 2: multiple SGL. Expected: PASS */ + bio.iovpos = 0; + req.payload_offset = 0; + req.payload_size = 0x4000; + for (i = 0; i < 4; i++) { + bio.iovs[i].iov_len = 0x1000; + } + rc = nvme_rdma_build_sgl_request(&rqpair, &rdma_req); + SPDK_CU_ASSERT_FATAL(rc == 0); + CU_ASSERT(bio.iovpos == 4); + CU_ASSERT(req.cmd.dptr.sgl1.unkeyed.type == SPDK_NVME_SGL_TYPE_LAST_SEGMENT); + CU_ASSERT(req.cmd.dptr.sgl1.unkeyed.subtype == SPDK_NVME_SGL_SUBTYPE_OFFSET); + CU_ASSERT(req.cmd.dptr.sgl1.unkeyed.length == 4 * sizeof(struct spdk_nvme_sgl_descriptor)); + CU_ASSERT(req.cmd.dptr.sgl1.address == (uint64_t)0); + CU_ASSERT(rdma_req.send_sgl[0].length == 4 * sizeof(struct spdk_nvme_sgl_descriptor) + sizeof( + struct spdk_nvme_cmd)) + for (i = 0; i < 4; i++) { + CU_ASSERT(cmd.sgl[i].keyed.type == SPDK_NVME_SGL_TYPE_KEYED_DATA_BLOCK); + CU_ASSERT(cmd.sgl[i].keyed.subtype == SPDK_NVME_SGL_SUBTYPE_ADDRESS); + CU_ASSERT(cmd.sgl[i].keyed.length == bio.iovs[i].iov_len); + CU_ASSERT(cmd.sgl[i].keyed.key == g_nvme_rdma_mr.rkey); + CU_ASSERT(cmd.sgl[i].address == (uint64_t)bio.iovs[i].iov_base); + } + + /* Test case 3: Multiple SGL, SGL larger than mr size. Expected: FAIL */ + bio.iovpos = 0; + req.payload_offset = 0; + g_mr_size = 0x500; + rc = nvme_rdma_build_sgl_request(&rqpair, &rdma_req); + SPDK_CU_ASSERT_FATAL(rc != 0); + CU_ASSERT(bio.iovpos == 1); + + /* Test case 4: Multiple SGL, SGL size smaller than I/O size */ + bio.iovpos = 0; + req.payload_offset = 0; + req.payload_size = 0x6000; + g_mr_size = 0x0; + rc = nvme_rdma_build_sgl_request(&rqpair, &rdma_req); + SPDK_CU_ASSERT_FATAL(rc != 0); + CU_ASSERT(bio.iovpos == NVME_RDMA_MAX_SGL_DESCRIPTORS); +} + +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("nvme_rdma", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if (CU_add_test(suite, "build_sgl_request", test_nvme_rdma_build_sgl_request) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvmf/Makefile b/src/spdk/test/unit/lib/nvmf/Makefile new file mode 100644 index 00000000..0b02f8ba --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = request.c ctrlr.c subsystem.c ctrlr_discovery.c ctrlr_bdev.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr.c/.gitignore b/src/spdk/test/unit/lib/nvmf/ctrlr.c/.gitignore new file mode 100644 index 00000000..65e84943 --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/ctrlr.c/.gitignore @@ -0,0 +1 @@ +ctrlr_ut diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr.c/Makefile b/src/spdk/test/unit/lib/nvmf/ctrlr.c/Makefile new file mode 100644 index 00000000..c68c589a --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/ctrlr.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 = ctrlr_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr.c/ctrlr_ut.c b/src/spdk/test/unit/lib/nvmf/ctrlr.c/ctrlr_ut.c new file mode 100644 index 00000000..71555e32 --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/ctrlr.c/ctrlr_ut.c @@ -0,0 +1,797 @@ +/*- + * 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_internal/mock.h" + +#include "common/lib/test_env.c" +#include "nvmf/ctrlr.c" + +SPDK_LOG_REGISTER_COMPONENT("nvmf", SPDK_LOG_NVMF) + +struct spdk_bdev { + int ut_mock; + uint64_t blockcnt; +}; + +DEFINE_STUB(spdk_nvmf_tgt_find_subsystem, + struct spdk_nvmf_subsystem *, + (struct spdk_nvmf_tgt *tgt, const char *subnqn), + NULL); + +DEFINE_STUB(spdk_nvmf_poll_group_create, + struct spdk_nvmf_poll_group *, + (struct spdk_nvmf_tgt *tgt), + NULL); + +DEFINE_STUB_V(spdk_nvmf_poll_group_destroy, + (struct spdk_nvmf_poll_group *group)); + +DEFINE_STUB_V(spdk_nvmf_transport_qpair_fini, + (struct spdk_nvmf_qpair *qpair)); + +DEFINE_STUB(spdk_nvmf_poll_group_add, + int, + (struct spdk_nvmf_poll_group *group, struct spdk_nvmf_qpair *qpair), + 0); + +DEFINE_STUB(spdk_nvmf_subsystem_get_sn, + const char *, + (const struct spdk_nvmf_subsystem *subsystem), + NULL); + +DEFINE_STUB(spdk_nvmf_subsystem_get_ns, + struct spdk_nvmf_ns *, + (struct spdk_nvmf_subsystem *subsystem, uint32_t nsid), + NULL); + +DEFINE_STUB(spdk_nvmf_subsystem_get_first_ns, + struct spdk_nvmf_ns *, + (struct spdk_nvmf_subsystem *subsystem), + NULL); + +DEFINE_STUB(spdk_nvmf_subsystem_get_next_ns, + struct spdk_nvmf_ns *, + (struct spdk_nvmf_subsystem *subsystem, struct spdk_nvmf_ns *prev_ns), + NULL); + +DEFINE_STUB(spdk_nvmf_subsystem_host_allowed, + bool, + (struct spdk_nvmf_subsystem *subsystem, const char *hostnqn), + true); + +DEFINE_STUB(spdk_nvmf_subsystem_add_ctrlr, + int, + (struct spdk_nvmf_subsystem *subsystem, struct spdk_nvmf_ctrlr *ctrlr), + 0); + +DEFINE_STUB_V(spdk_nvmf_subsystem_remove_ctrlr, + (struct spdk_nvmf_subsystem *subsystem, struct spdk_nvmf_ctrlr *ctrlr)); + +DEFINE_STUB(spdk_nvmf_subsystem_get_ctrlr, + struct spdk_nvmf_ctrlr *, + (struct spdk_nvmf_subsystem *subsystem, uint16_t cntlid), + NULL); + +DEFINE_STUB(spdk_nvmf_ctrlr_dsm_supported, + bool, + (struct spdk_nvmf_ctrlr *ctrlr), + false); + +DEFINE_STUB(spdk_nvmf_ctrlr_write_zeroes_supported, + bool, + (struct spdk_nvmf_ctrlr *ctrlr), + false); + +DEFINE_STUB_V(spdk_nvmf_get_discovery_log_page, + (struct spdk_nvmf_tgt *tgt, void *buffer, uint64_t offset, uint32_t length)); + +DEFINE_STUB(spdk_nvmf_request_complete, + int, + (struct spdk_nvmf_request *req), + -1); + +DEFINE_STUB(spdk_nvmf_request_free, + int, + (struct spdk_nvmf_request *req), + -1); + +DEFINE_STUB(spdk_nvmf_qpair_get_listen_trid, + int, + (struct spdk_nvmf_qpair *qpair, struct spdk_nvme_transport_id *trid), + 0); + +DEFINE_STUB(spdk_nvmf_subsystem_listener_allowed, + bool, + (struct spdk_nvmf_subsystem *subsystem, struct spdk_nvme_transport_id *trid), + true); + +static void +ctrlr_ut_pass_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx) +{ + fn(ctx); +} + +void +spdk_nvmf_bdev_ctrlr_identify_ns(struct spdk_nvmf_ns *ns, struct spdk_nvme_ns_data *nsdata) +{ + uint64_t num_blocks; + + SPDK_CU_ASSERT_FATAL(ns->bdev != NULL); + num_blocks = ns->bdev->blockcnt; + nsdata->nsze = num_blocks; + nsdata->ncap = num_blocks; + nsdata->nuse = num_blocks; + nsdata->nlbaf = 0; + nsdata->flbas.format = 0; + nsdata->lbaf[0].lbads = spdk_u32log2(512); +} + +static void +test_get_log_page(void) +{ + struct spdk_nvmf_subsystem subsystem = {}; + struct spdk_nvmf_request req = {}; + struct spdk_nvmf_qpair qpair = {}; + struct spdk_nvmf_ctrlr ctrlr = {}; + union nvmf_h2c_msg cmd = {}; + union nvmf_c2h_msg rsp = {}; + char data[4096]; + + subsystem.subtype = SPDK_NVMF_SUBTYPE_NVME; + + ctrlr.subsys = &subsystem; + + qpair.ctrlr = &ctrlr; + + req.qpair = &qpair; + req.cmd = &cmd; + req.rsp = &rsp; + req.data = &data; + req.length = sizeof(data); + + /* Get Log Page - all valid */ + memset(&cmd, 0, sizeof(cmd)); + memset(&rsp, 0, sizeof(rsp)); + cmd.nvme_cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE; + cmd.nvme_cmd.cdw10 = SPDK_NVME_LOG_ERROR | (req.length / 4 - 1) << 16; + CU_ASSERT(spdk_nvmf_ctrlr_get_log_page(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(req.rsp->nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(req.rsp->nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS); + + /* Get Log Page with invalid log ID */ + memset(&cmd, 0, sizeof(cmd)); + memset(&rsp, 0, sizeof(rsp)); + cmd.nvme_cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE; + cmd.nvme_cmd.cdw10 = 0; + CU_ASSERT(spdk_nvmf_ctrlr_get_log_page(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(req.rsp->nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(req.rsp->nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_FIELD); + + /* Get Log Page with invalid offset (not dword aligned) */ + memset(&cmd, 0, sizeof(cmd)); + memset(&rsp, 0, sizeof(rsp)); + cmd.nvme_cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE; + cmd.nvme_cmd.cdw10 = SPDK_NVME_LOG_ERROR | (req.length / 4 - 1) << 16; + cmd.nvme_cmd.cdw12 = 2; + CU_ASSERT(spdk_nvmf_ctrlr_get_log_page(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(req.rsp->nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(req.rsp->nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_FIELD); + + /* Get Log Page without data buffer */ + memset(&cmd, 0, sizeof(cmd)); + memset(&rsp, 0, sizeof(rsp)); + req.data = NULL; + cmd.nvme_cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE; + cmd.nvme_cmd.cdw10 = SPDK_NVME_LOG_ERROR | (req.length / 4 - 1) << 16; + CU_ASSERT(spdk_nvmf_ctrlr_get_log_page(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(req.rsp->nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(req.rsp->nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_FIELD); + req.data = data; +} + +static void +test_process_fabrics_cmd(void) +{ + struct spdk_nvmf_request req = {}; + int ret; + struct spdk_nvmf_qpair req_qpair = {}; + union nvmf_h2c_msg req_cmd = {}; + union nvmf_c2h_msg req_rsp = {}; + + req.qpair = &req_qpair; + req.cmd = &req_cmd; + req.rsp = &req_rsp; + req.qpair->ctrlr = NULL; + + /* No ctrlr and invalid command check */ + req.cmd->nvmf_cmd.fctype = SPDK_NVMF_FABRIC_COMMAND_PROPERTY_GET; + ret = spdk_nvmf_ctrlr_process_fabrics_cmd(&req); + CU_ASSERT_EQUAL(req.rsp->nvme_cpl.status.sc, SPDK_NVME_SC_COMMAND_SEQUENCE_ERROR); + CU_ASSERT_EQUAL(ret, SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); +} + +static bool +nvme_status_success(const struct spdk_nvme_status *status) +{ + return status->sct == SPDK_NVME_SCT_GENERIC && status->sc == SPDK_NVME_SC_SUCCESS; +} + +static void +test_connect(void) +{ + struct spdk_nvmf_fabric_connect_data connect_data; + struct spdk_thread *thread; + struct spdk_nvmf_poll_group group; + struct spdk_nvmf_transport transport; + struct spdk_nvmf_subsystem subsystem; + struct spdk_nvmf_request req; + struct spdk_nvmf_qpair admin_qpair; + struct spdk_nvmf_qpair qpair; + struct spdk_nvmf_qpair qpair2; + struct spdk_nvmf_ctrlr ctrlr; + struct spdk_nvmf_tgt tgt; + union nvmf_h2c_msg cmd; + union nvmf_c2h_msg rsp; + const uint8_t hostid[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F + }; + const char subnqn[] = "nqn.2016-06.io.spdk:subsystem1"; + const char hostnqn[] = "nqn.2016-06.io.spdk:host1"; + int rc; + + thread = spdk_allocate_thread(ctrlr_ut_pass_msg, NULL, NULL, NULL, "ctrlr_ut"); + SPDK_CU_ASSERT_FATAL(thread != NULL); + + memset(&group, 0, sizeof(group)); + group.thread = thread; + + memset(&ctrlr, 0, sizeof(ctrlr)); + ctrlr.subsys = &subsystem; + ctrlr.qpair_mask = spdk_bit_array_create(3); + SPDK_CU_ASSERT_FATAL(ctrlr.qpair_mask != NULL); + ctrlr.vcprop.cc.bits.en = 1; + ctrlr.vcprop.cc.bits.iosqes = 6; + ctrlr.vcprop.cc.bits.iocqes = 4; + + memset(&admin_qpair, 0, sizeof(admin_qpair)); + admin_qpair.group = &group; + + memset(&tgt, 0, sizeof(tgt)); + memset(&transport, 0, sizeof(transport)); + transport.opts.max_queue_depth = 64; + transport.opts.max_qpairs_per_ctrlr = 3; + transport.tgt = &tgt; + + memset(&qpair, 0, sizeof(qpair)); + qpair.transport = &transport; + qpair.group = &group; + + memset(&connect_data, 0, sizeof(connect_data)); + memcpy(connect_data.hostid, hostid, sizeof(hostid)); + connect_data.cntlid = 0xFFFF; + snprintf(connect_data.subnqn, sizeof(connect_data.subnqn), "%s", subnqn); + snprintf(connect_data.hostnqn, sizeof(connect_data.hostnqn), "%s", hostnqn); + + memset(&subsystem, 0, sizeof(subsystem)); + subsystem.thread = thread; + subsystem.id = 1; + TAILQ_INIT(&subsystem.ctrlrs); + subsystem.tgt = &tgt; + subsystem.subtype = SPDK_NVMF_SUBTYPE_NVME; + snprintf(subsystem.subnqn, sizeof(subsystem.subnqn), "%s", subnqn); + + memset(&cmd, 0, sizeof(cmd)); + cmd.connect_cmd.opcode = SPDK_NVME_OPC_FABRIC; + cmd.connect_cmd.cid = 1; + cmd.connect_cmd.fctype = SPDK_NVMF_FABRIC_COMMAND_CONNECT; + cmd.connect_cmd.recfmt = 0; + cmd.connect_cmd.qid = 0; + cmd.connect_cmd.sqsize = 31; + cmd.connect_cmd.cattr = 0; + cmd.connect_cmd.kato = 120000; + + memset(&req, 0, sizeof(req)); + req.qpair = &qpair; + req.length = sizeof(connect_data); + req.xfer = SPDK_NVME_DATA_HOST_TO_CONTROLLER; + req.data = &connect_data; + req.cmd = &cmd; + req.rsp = &rsp; + + MOCK_SET(spdk_nvmf_tgt_find_subsystem, &subsystem); + MOCK_SET(spdk_nvmf_poll_group_create, &group); + + /* Valid admin connect command */ + memset(&rsp, 0, sizeof(rsp)); + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS); + CU_ASSERT(nvme_status_success(&rsp.nvme_cpl.status)); + CU_ASSERT(qpair.ctrlr != NULL); + spdk_bit_array_free(&qpair.ctrlr->qpair_mask); + free(qpair.ctrlr); + qpair.ctrlr = NULL; + + /* Invalid data length */ + memset(&rsp, 0, sizeof(rsp)); + req.length = sizeof(connect_data) - 1; + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_FIELD); + CU_ASSERT(qpair.ctrlr == NULL); + req.length = sizeof(connect_data); + + /* Invalid recfmt */ + memset(&rsp, 0, sizeof(rsp)); + cmd.connect_cmd.recfmt = 1234; + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INCOMPATIBLE_FORMAT); + CU_ASSERT(qpair.ctrlr == NULL); + cmd.connect_cmd.recfmt = 0; + + /* Unterminated subnqn */ + memset(&rsp, 0, sizeof(rsp)); + memset(connect_data.subnqn, 'a', sizeof(connect_data.subnqn)); + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 1); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 256); + CU_ASSERT(qpair.ctrlr == NULL); + snprintf(connect_data.subnqn, sizeof(connect_data.subnqn), "%s", subnqn); + + /* Subsystem not found */ + memset(&rsp, 0, sizeof(rsp)); + MOCK_SET(spdk_nvmf_tgt_find_subsystem, NULL); + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 1); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 256); + CU_ASSERT(qpair.ctrlr == NULL); + MOCK_SET(spdk_nvmf_tgt_find_subsystem, &subsystem); + + /* Unterminated hostnqn */ + memset(&rsp, 0, sizeof(rsp)); + memset(connect_data.hostnqn, 'b', sizeof(connect_data.hostnqn)); + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 1); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 512); + CU_ASSERT(qpair.ctrlr == NULL); + snprintf(connect_data.hostnqn, sizeof(connect_data.hostnqn), "%s", hostnqn); + + /* Host not allowed */ + memset(&rsp, 0, sizeof(rsp)); + MOCK_SET(spdk_nvmf_subsystem_host_allowed, false); + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_HOST); + CU_ASSERT(qpair.ctrlr == NULL); + MOCK_SET(spdk_nvmf_subsystem_host_allowed, true); + + /* Invalid sqsize == 0 */ + memset(&rsp, 0, sizeof(rsp)); + cmd.connect_cmd.sqsize = 0; + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 44); + CU_ASSERT(qpair.ctrlr == NULL); + cmd.connect_cmd.sqsize = 31; + + /* Invalid sqsize > max_queue_depth */ + memset(&rsp, 0, sizeof(rsp)); + cmd.connect_cmd.sqsize = 64; + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 44); + CU_ASSERT(qpair.ctrlr == NULL); + cmd.connect_cmd.sqsize = 31; + + /* Invalid cntlid for admin queue */ + memset(&rsp, 0, sizeof(rsp)); + connect_data.cntlid = 0x1234; + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 1); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 16); + CU_ASSERT(qpair.ctrlr == NULL); + connect_data.cntlid = 0xFFFF; + + ctrlr.admin_qpair = &admin_qpair; + ctrlr.subsys = &subsystem; + + /* Valid I/O queue connect command */ + memset(&rsp, 0, sizeof(rsp)); + MOCK_SET(spdk_nvmf_subsystem_get_ctrlr, &ctrlr); + cmd.connect_cmd.qid = 1; + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS); + CU_ASSERT(nvme_status_success(&rsp.nvme_cpl.status)); + CU_ASSERT(qpair.ctrlr == &ctrlr); + qpair.ctrlr = NULL; + + /* Non-existent controller */ + memset(&rsp, 0, sizeof(rsp)); + MOCK_SET(spdk_nvmf_subsystem_get_ctrlr, NULL); + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 1); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 16); + CU_ASSERT(qpair.ctrlr == NULL); + MOCK_SET(spdk_nvmf_subsystem_get_ctrlr, &ctrlr); + + /* I/O connect to discovery controller */ + memset(&rsp, 0, sizeof(rsp)); + subsystem.subtype = SPDK_NVMF_SUBTYPE_DISCOVERY; + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 42); + CU_ASSERT(qpair.ctrlr == NULL); + subsystem.subtype = SPDK_NVMF_SUBTYPE_NVME; + + /* I/O connect to disabled controller */ + memset(&rsp, 0, sizeof(rsp)); + ctrlr.vcprop.cc.bits.en = 0; + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 42); + CU_ASSERT(qpair.ctrlr == NULL); + ctrlr.vcprop.cc.bits.en = 1; + + /* I/O connect with invalid IOSQES */ + memset(&rsp, 0, sizeof(rsp)); + ctrlr.vcprop.cc.bits.iosqes = 3; + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 42); + CU_ASSERT(qpair.ctrlr == NULL); + ctrlr.vcprop.cc.bits.iosqes = 6; + + /* I/O connect with invalid IOCQES */ + memset(&rsp, 0, sizeof(rsp)); + ctrlr.vcprop.cc.bits.iocqes = 3; + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0); + CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 42); + CU_ASSERT(qpair.ctrlr == NULL); + ctrlr.vcprop.cc.bits.iocqes = 4; + + /* I/O connect with too many existing qpairs */ + memset(&rsp, 0, sizeof(rsp)); + spdk_bit_array_set(ctrlr.qpair_mask, 0); + spdk_bit_array_set(ctrlr.qpair_mask, 1); + spdk_bit_array_set(ctrlr.qpair_mask, 2); + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_QUEUE_IDENTIFIER); + CU_ASSERT(qpair.ctrlr == NULL); + spdk_bit_array_clear(ctrlr.qpair_mask, 0); + spdk_bit_array_clear(ctrlr.qpair_mask, 1); + spdk_bit_array_clear(ctrlr.qpair_mask, 2); + + /* I/O connect with duplicate queue ID */ + memset(&rsp, 0, sizeof(rsp)); + memset(&qpair2, 0, sizeof(qpair2)); + qpair2.group = &group; + qpair2.qid = 1; + spdk_bit_array_set(ctrlr.qpair_mask, 1); + cmd.connect_cmd.qid = 1; + rc = spdk_nvmf_ctrlr_connect(&req); + CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_QUEUE_IDENTIFIER); + CU_ASSERT(qpair.ctrlr == NULL); + + /* Clean up globals */ + MOCK_CLEAR(spdk_nvmf_tgt_find_subsystem); + MOCK_CLEAR(spdk_nvmf_poll_group_create); + + spdk_bit_array_free(&ctrlr.qpair_mask); + spdk_free_thread(); +} + +static void +test_get_ns_id_desc_list(void) +{ + struct spdk_nvmf_subsystem subsystem; + struct spdk_nvmf_qpair qpair; + struct spdk_nvmf_ctrlr ctrlr; + struct spdk_nvmf_request req; + struct spdk_nvmf_ns *ns_ptrs[1]; + struct spdk_nvmf_ns ns; + union nvmf_h2c_msg cmd; + union nvmf_c2h_msg rsp; + struct spdk_bdev bdev; + uint8_t buf[4096]; + + memset(&subsystem, 0, sizeof(subsystem)); + ns_ptrs[0] = &ns; + subsystem.ns = ns_ptrs; + subsystem.max_nsid = 1; + subsystem.subtype = SPDK_NVMF_SUBTYPE_NVME; + + memset(&ns, 0, sizeof(ns)); + ns.opts.nsid = 1; + ns.bdev = &bdev; + + memset(&qpair, 0, sizeof(qpair)); + qpair.ctrlr = &ctrlr; + + memset(&ctrlr, 0, sizeof(ctrlr)); + ctrlr.subsys = &subsystem; + ctrlr.vcprop.cc.bits.en = 1; + + memset(&req, 0, sizeof(req)); + req.qpair = &qpair; + req.cmd = &cmd; + req.rsp = &rsp; + req.xfer = SPDK_NVME_DATA_CONTROLLER_TO_HOST; + req.data = buf; + req.length = sizeof(buf); + + memset(&cmd, 0, sizeof(cmd)); + cmd.nvme_cmd.opc = SPDK_NVME_OPC_IDENTIFY; + cmd.nvme_cmd.cdw10 = SPDK_NVME_IDENTIFY_NS_ID_DESCRIPTOR_LIST; + + /* Invalid NSID */ + cmd.nvme_cmd.nsid = 0; + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT); + + /* Valid NSID, but ns has no IDs defined */ + cmd.nvme_cmd.nsid = 1; + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS); + CU_ASSERT(spdk_mem_all_zero(buf, sizeof(buf))); + + /* Valid NSID, only EUI64 defined */ + ns.opts.eui64[0] = 0x11; + ns.opts.eui64[7] = 0xFF; + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS); + CU_ASSERT(buf[0] == SPDK_NVME_NIDT_EUI64); + CU_ASSERT(buf[1] == 8); + CU_ASSERT(buf[4] == 0x11); + CU_ASSERT(buf[11] == 0xFF); + CU_ASSERT(buf[13] == 0); + + /* Valid NSID, only NGUID defined */ + memset(ns.opts.eui64, 0, sizeof(ns.opts.eui64)); + ns.opts.nguid[0] = 0x22; + ns.opts.nguid[15] = 0xEE; + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS); + CU_ASSERT(buf[0] == SPDK_NVME_NIDT_NGUID); + CU_ASSERT(buf[1] == 16); + CU_ASSERT(buf[4] == 0x22); + CU_ASSERT(buf[19] == 0xEE); + CU_ASSERT(buf[21] == 0); + + /* Valid NSID, both EUI64 and NGUID defined */ + ns.opts.eui64[0] = 0x11; + ns.opts.eui64[7] = 0xFF; + ns.opts.nguid[0] = 0x22; + ns.opts.nguid[15] = 0xEE; + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS); + CU_ASSERT(buf[0] == SPDK_NVME_NIDT_EUI64); + CU_ASSERT(buf[1] == 8); + CU_ASSERT(buf[4] == 0x11); + CU_ASSERT(buf[11] == 0xFF); + CU_ASSERT(buf[12] == SPDK_NVME_NIDT_NGUID); + CU_ASSERT(buf[13] == 16); + CU_ASSERT(buf[16] == 0x22); + CU_ASSERT(buf[31] == 0xEE); + CU_ASSERT(buf[33] == 0); + + /* Valid NSID, EUI64, NGUID, and UUID defined */ + ns.opts.eui64[0] = 0x11; + ns.opts.eui64[7] = 0xFF; + ns.opts.nguid[0] = 0x22; + ns.opts.nguid[15] = 0xEE; + ns.opts.uuid.u.raw[0] = 0x33; + ns.opts.uuid.u.raw[15] = 0xDD; + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS); + CU_ASSERT(buf[0] == SPDK_NVME_NIDT_EUI64); + CU_ASSERT(buf[1] == 8); + CU_ASSERT(buf[4] == 0x11); + CU_ASSERT(buf[11] == 0xFF); + CU_ASSERT(buf[12] == SPDK_NVME_NIDT_NGUID); + CU_ASSERT(buf[13] == 16); + CU_ASSERT(buf[16] == 0x22); + CU_ASSERT(buf[31] == 0xEE); + CU_ASSERT(buf[32] == SPDK_NVME_NIDT_UUID); + CU_ASSERT(buf[33] == 16); + CU_ASSERT(buf[36] == 0x33); + CU_ASSERT(buf[51] == 0xDD); + CU_ASSERT(buf[53] == 0); +} + +static void +test_identify_ns(void) +{ + struct spdk_nvmf_subsystem subsystem = {}; + struct spdk_nvmf_transport transport = {}; + struct spdk_nvmf_qpair admin_qpair = { .transport = &transport}; + struct spdk_nvmf_ctrlr ctrlr = { .subsys = &subsystem, .admin_qpair = &admin_qpair }; + struct spdk_nvme_cmd cmd = {}; + struct spdk_nvme_cpl rsp = {}; + struct spdk_nvme_ns_data nsdata = {}; + struct spdk_bdev bdev[3] = {{.blockcnt = 1234}, {.blockcnt = 0}, {.blockcnt = 5678}}; + struct spdk_nvmf_ns ns[3] = {{.bdev = &bdev[0]}, {.bdev = NULL}, {.bdev = &bdev[2]}}; + struct spdk_nvmf_ns *ns_arr[3] = {&ns[0], NULL, &ns[2]}; + + subsystem.ns = ns_arr; + subsystem.max_nsid = SPDK_COUNTOF(ns_arr); + + /* Invalid NSID 0 */ + cmd.nsid = 0; + memset(&nsdata, 0, sizeof(nsdata)); + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp, + &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT); + CU_ASSERT(spdk_mem_all_zero(&nsdata, sizeof(nsdata))); + + /* Valid NSID 1 */ + cmd.nsid = 1; + memset(&nsdata, 0, sizeof(nsdata)); + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp, + &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_SUCCESS); + CU_ASSERT(nsdata.nsze == 1234); + + /* Valid but inactive NSID 2 */ + cmd.nsid = 2; + memset(&nsdata, 0, sizeof(nsdata)); + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp, + &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_SUCCESS); + CU_ASSERT(spdk_mem_all_zero(&nsdata, sizeof(nsdata))); + + /* Valid NSID 3 */ + cmd.nsid = 3; + memset(&nsdata, 0, sizeof(nsdata)); + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp, + &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_SUCCESS); + CU_ASSERT(nsdata.nsze == 5678); + + /* Invalid NSID 4 */ + cmd.nsid = 4; + memset(&nsdata, 0, sizeof(nsdata)); + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp, + &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT); + CU_ASSERT(spdk_mem_all_zero(&nsdata, sizeof(nsdata))); + + /* Invalid NSID 0xFFFFFFFF (NS management not supported) */ + cmd.nsid = 0xFFFFFFFF; + memset(&nsdata, 0, sizeof(nsdata)); + memset(&rsp, 0, sizeof(rsp)); + CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp, + &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE); + CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT); + CU_ASSERT(spdk_mem_all_zero(&nsdata, sizeof(nsdata))); +} + +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("nvmf", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "get_log_page", test_get_log_page) == NULL || + CU_add_test(suite, "process_fabrics_cmd", test_process_fabrics_cmd) == NULL || + CU_add_test(suite, "connect", test_connect) == NULL || + CU_add_test(suite, "get_ns_id_desc_list", test_get_ns_id_desc_list) == NULL || + CU_add_test(suite, "identify_ns", test_identify_ns) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/.gitignore b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/.gitignore new file mode 100644 index 00000000..78fca101 --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/.gitignore @@ -0,0 +1 @@ +ctrlr_bdev_ut diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/Makefile b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/Makefile new file mode 100644 index 00000000..1d22f14b --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.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 = ctrlr_bdev_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/ctrlr_bdev_ut.c b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/ctrlr_bdev_ut.c new file mode 100644 index 00000000..1085e4d7 --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/ctrlr_bdev_ut.c @@ -0,0 +1,260 @@ +/*- + * 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 "nvmf/ctrlr_bdev.c" + + +SPDK_LOG_REGISTER_COMPONENT("nvmf", SPDK_LOG_NVMF) + +int +spdk_nvmf_request_complete(struct spdk_nvmf_request *req) +{ + return -1; +} + +const char * +spdk_bdev_get_name(const struct spdk_bdev *bdev) +{ + return "test"; +} + +uint32_t +spdk_bdev_get_block_size(const struct spdk_bdev *bdev) +{ + abort(); + return 0; +} + +uint64_t +spdk_bdev_get_num_blocks(const struct spdk_bdev *bdev) +{ + abort(); + return 0; +} + +uint32_t +spdk_bdev_get_optimal_io_boundary(const struct spdk_bdev *bdev) +{ + abort(); + return 0; +} + +struct spdk_io_channel * +spdk_bdev_get_io_channel(struct spdk_bdev_desc *desc) +{ + return NULL; +} + +int +spdk_bdev_flush_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return 0; +} + +int +spdk_bdev_unmap_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return 0; +} + +bool +spdk_bdev_io_type_supported(struct spdk_bdev *bdev, enum spdk_bdev_io_type io_type) +{ + return false; +} + +int +spdk_bdev_queue_io_wait(struct spdk_bdev *bdev, struct spdk_io_channel *ch, + struct spdk_bdev_io_wait_entry *entry) +{ + return 0; +} + +int +spdk_bdev_write_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, void *buf, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return 0; +} + +int +spdk_bdev_writev_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return 0; +} + +int +spdk_bdev_read_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, void *buf, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return 0; +} + +int spdk_bdev_readv_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return 0; +} + +int +spdk_bdev_write_zeroes_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return 0; +} + +int +spdk_bdev_nvme_io_passthru(struct spdk_bdev_desc *desc, + struct spdk_io_channel *ch, + const struct spdk_nvme_cmd *cmd, + void *buf, size_t nbytes, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return 0; +} + +void spdk_bdev_free_io(struct spdk_bdev_io *bdev_io) +{ +} + +const char *spdk_nvmf_subsystem_get_nqn(struct spdk_nvmf_subsystem *subsystem) +{ + return NULL; +} + +struct spdk_nvmf_ns * +spdk_nvmf_subsystem_get_ns(struct spdk_nvmf_subsystem *subsystem, uint32_t nsid) +{ + abort(); + return NULL; +} + +struct spdk_nvmf_ns * +spdk_nvmf_subsystem_get_first_ns(struct spdk_nvmf_subsystem *subsystem) +{ + abort(); + return NULL; +} + +struct spdk_nvmf_ns * +spdk_nvmf_subsystem_get_next_ns(struct spdk_nvmf_subsystem *subsystem, struct spdk_nvmf_ns *prev_ns) +{ + abort(); + return NULL; +} + +void spdk_bdev_io_get_nvme_status(const struct spdk_bdev_io *bdev_io, int *sct, int *sc) +{ +} + +static void +test_get_rw_params(void) +{ + struct spdk_nvme_cmd cmd = {0}; + uint64_t lba; + uint64_t count; + + lba = 0; + count = 0; + to_le64(&cmd.cdw10, 0x1234567890ABCDEF); + to_le32(&cmd.cdw12, 0x9875 | SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS); + nvmf_bdev_ctrlr_get_rw_params(&cmd, &lba, &count); + CU_ASSERT(lba == 0x1234567890ABCDEF); + CU_ASSERT(count == 0x9875 + 1); /* NOTE: this field is 0's based, hence the +1 */ +} + +static void +test_lba_in_range(void) +{ + /* Trivial cases (no overflow) */ + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 0, 1) == true); + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 0, 1000) == true); + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 0, 1001) == false); + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 1, 999) == true); + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 1, 1000) == false); + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 999, 1) == true); + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 1000, 1) == false); + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 1001, 1) == false); + + /* Overflow edge cases */ + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(UINT64_MAX, 0, UINT64_MAX) == true); + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(UINT64_MAX, 1, UINT64_MAX) == false) + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(UINT64_MAX, UINT64_MAX - 1, 1) == true); + CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(UINT64_MAX, UINT64_MAX, 1) == false); +} + +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("nvmf", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "get_rw_params", test_get_rw_params) == NULL || + CU_add_test(suite, "lba_in_range", test_lba_in_range) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/.gitignore b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/.gitignore new file mode 100644 index 00000000..a975a97e --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/.gitignore @@ -0,0 +1 @@ +ctrlr_discovery_ut diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/Makefile b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/Makefile new file mode 100644 index 00000000..e56238d2 --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.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 = ctrlr_discovery_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/ctrlr_discovery_ut.c b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/ctrlr_discovery_ut.c new file mode 100644 index 00000000..f86bdf2b --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/ctrlr_discovery_ut.c @@ -0,0 +1,306 @@ +/*- + * 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_internal/mock.h" + +#include "common/lib/test_env.c" +#include "nvmf/ctrlr_discovery.c" +#include "nvmf/subsystem.c" + +SPDK_LOG_REGISTER_COMPONENT("nvmf", SPDK_LOG_NVMF) + +DEFINE_STUB(spdk_bdev_module_claim_bdev, + int, + (struct spdk_bdev *bdev, struct spdk_bdev_desc *desc, + struct spdk_bdev_module *module), 0); + +DEFINE_STUB_V(spdk_bdev_module_release_bdev, + (struct spdk_bdev *bdev)); + +uint32_t +spdk_env_get_current_core(void) +{ + return 0; +} + +struct spdk_event * +spdk_event_allocate(uint32_t core, spdk_event_fn fn, void *arg1, void *arg2) +{ + return NULL; +} + +void +spdk_event_call(struct spdk_event *event) +{ + +} + +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) +{ + return 0; +} + +void +spdk_bdev_close(struct spdk_bdev_desc *desc) +{ +} + +const char * +spdk_bdev_get_name(const struct spdk_bdev *bdev) +{ + return "test"; +} + +const struct spdk_uuid * +spdk_bdev_get_uuid(const struct spdk_bdev *bdev) +{ + return &bdev->uuid; +} + +int +spdk_nvmf_transport_listen(struct spdk_nvmf_transport *transport, + const struct spdk_nvme_transport_id *trid) +{ + return 0; +} + +void +spdk_nvmf_transport_listener_discover(struct spdk_nvmf_transport *transport, + struct spdk_nvme_transport_id *trid, + struct spdk_nvmf_discovery_log_page_entry *entry) +{ + entry->trtype = 42; +} + +static struct spdk_nvmf_transport g_transport = {}; + +struct spdk_nvmf_transport * +spdk_nvmf_transport_create(enum spdk_nvme_transport_type type, + struct spdk_nvmf_transport_opts *tprt_opts) +{ + if (type == SPDK_NVME_TRANSPORT_RDMA) { + return &g_transport; + } + + return NULL; +} + +struct spdk_nvmf_subsystem * +spdk_nvmf_tgt_find_subsystem(struct spdk_nvmf_tgt *tgt, const char *subnqn) +{ + return NULL; +} + +struct spdk_nvmf_transport * +spdk_nvmf_tgt_get_transport(struct spdk_nvmf_tgt *tgt, enum spdk_nvme_transport_type trtype) +{ + return &g_transport; +} + +bool +spdk_nvmf_transport_qpair_is_idle(struct spdk_nvmf_qpair *qpair) +{ + return false; +} + +int +spdk_nvme_transport_id_parse_trtype(enum spdk_nvme_transport_type *trtype, const char *str) +{ + if (trtype == NULL || str == NULL) { + return -EINVAL; + } + + if (strcasecmp(str, "PCIe") == 0) { + *trtype = SPDK_NVME_TRANSPORT_PCIE; + } else if (strcasecmp(str, "RDMA") == 0) { + *trtype = SPDK_NVME_TRANSPORT_RDMA; + } else { + return -ENOENT; + } + return 0; +} + +int +spdk_nvme_transport_id_compare(const struct spdk_nvme_transport_id *trid1, + const struct spdk_nvme_transport_id *trid2) +{ + return 0; +} + +void +spdk_nvmf_ctrlr_ns_changed(struct spdk_nvmf_ctrlr *ctrlr, uint32_t nsid) +{ +} + +void +spdk_nvmf_ctrlr_destruct(struct spdk_nvmf_ctrlr *ctrlr) +{ +} + +int +spdk_nvmf_poll_group_update_subsystem(struct spdk_nvmf_poll_group *group, + struct spdk_nvmf_subsystem *subsystem) +{ + return 0; +} + +int +spdk_nvmf_poll_group_add_subsystem(struct spdk_nvmf_poll_group *group, + struct spdk_nvmf_subsystem *subsystem, + spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg) +{ + return 0; +} + +void +spdk_nvmf_poll_group_remove_subsystem(struct spdk_nvmf_poll_group *group, + struct spdk_nvmf_subsystem *subsystem, + spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg) +{ +} + +void +spdk_nvmf_poll_group_pause_subsystem(struct spdk_nvmf_poll_group *group, + struct spdk_nvmf_subsystem *subsystem, + spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg) +{ +} + +void +spdk_nvmf_poll_group_resume_subsystem(struct spdk_nvmf_poll_group *group, + struct spdk_nvmf_subsystem *subsystem, + spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg) +{ +} + +static void +test_discovery_log(void) +{ + struct spdk_nvmf_tgt tgt = {}; + struct spdk_nvmf_subsystem *subsystem; + uint8_t buffer[8192]; + struct spdk_nvmf_discovery_log_page *disc_log; + struct spdk_nvmf_discovery_log_page_entry *entry; + struct spdk_nvme_transport_id trid = {}; + + tgt.opts.max_subsystems = 1024; + tgt.subsystems = calloc(tgt.opts.max_subsystems, sizeof(struct spdk_nvmf_subsystem *)); + SPDK_CU_ASSERT_FATAL(tgt.subsystems != NULL); + + /* Add one subsystem and verify that the discovery log contains it */ + subsystem = spdk_nvmf_subsystem_create(&tgt, "nqn.2016-06.io.spdk:subsystem1", + SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem != NULL); + + trid.trtype = SPDK_NVME_TRANSPORT_RDMA; + trid.adrfam = SPDK_NVMF_ADRFAM_IPV4; + snprintf(trid.traddr, sizeof(trid.traddr), "1234"); + snprintf(trid.trsvcid, sizeof(trid.trsvcid), "5678"); + SPDK_CU_ASSERT_FATAL(spdk_nvmf_subsystem_add_listener(subsystem, &trid) == 0); + + /* Get only genctr (first field in the header) */ + memset(buffer, 0xCC, sizeof(buffer)); + disc_log = (struct spdk_nvmf_discovery_log_page *)buffer; + spdk_nvmf_get_discovery_log_page(&tgt, buffer, 0, sizeof(disc_log->genctr)); + CU_ASSERT(disc_log->genctr == 1); /* one added subsystem */ + + /* Get only the header, no entries */ + memset(buffer, 0xCC, sizeof(buffer)); + disc_log = (struct spdk_nvmf_discovery_log_page *)buffer; + spdk_nvmf_get_discovery_log_page(&tgt, buffer, 0, sizeof(*disc_log)); + CU_ASSERT(disc_log->genctr == 1); + CU_ASSERT(disc_log->numrec == 1); + + /* Offset 0, exact size match */ + memset(buffer, 0xCC, sizeof(buffer)); + disc_log = (struct spdk_nvmf_discovery_log_page *)buffer; + spdk_nvmf_get_discovery_log_page(&tgt, buffer, 0, + sizeof(*disc_log) + sizeof(disc_log->entries[0])); + CU_ASSERT(disc_log->genctr != 0); + CU_ASSERT(disc_log->numrec == 1); + CU_ASSERT(disc_log->entries[0].trtype == 42); + + /* Offset 0, oversize buffer */ + memset(buffer, 0xCC, sizeof(buffer)); + disc_log = (struct spdk_nvmf_discovery_log_page *)buffer; + spdk_nvmf_get_discovery_log_page(&tgt, buffer, 0, sizeof(buffer)); + CU_ASSERT(disc_log->genctr != 0); + CU_ASSERT(disc_log->numrec == 1); + CU_ASSERT(disc_log->entries[0].trtype == 42); + CU_ASSERT(spdk_mem_all_zero(buffer + sizeof(*disc_log) + sizeof(disc_log->entries[0]), + sizeof(buffer) - (sizeof(*disc_log) + sizeof(disc_log->entries[0])))); + + /* Get just the first entry, no header */ + memset(buffer, 0xCC, sizeof(buffer)); + entry = (struct spdk_nvmf_discovery_log_page_entry *)buffer; + spdk_nvmf_get_discovery_log_page(&tgt, buffer, + offsetof(struct spdk_nvmf_discovery_log_page, entries[0]), + sizeof(*entry)); + CU_ASSERT(entry->trtype == 42); + spdk_nvmf_subsystem_destroy(subsystem); + free(tgt.subsystems); + free(tgt.discovery_log_page); +} + +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("nvmf", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "discovery_log", test_discovery_log) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvmf/request.c/.gitignore b/src/spdk/test/unit/lib/nvmf/request.c/.gitignore new file mode 100644 index 00000000..7f06e410 --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/request.c/.gitignore @@ -0,0 +1 @@ +request_ut diff --git a/src/spdk/test/unit/lib/nvmf/request.c/Makefile b/src/spdk/test/unit/lib/nvmf/request.c/Makefile new file mode 100644 index 00000000..0c683cff --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/request.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 = request_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvmf/request.c/request_ut.c b/src/spdk/test/unit/lib/nvmf/request.c/request_ut.c new file mode 100644 index 00000000..bd21fa63 --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/request.c/request_ut.c @@ -0,0 +1,153 @@ +/*- + * 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 "nvmf/request.c" + +SPDK_LOG_REGISTER_COMPONENT("nvmf", SPDK_LOG_NVMF) + +int +spdk_nvmf_transport_req_free(struct spdk_nvmf_request *req) +{ + return 0; +} + +int +spdk_nvmf_transport_req_complete(struct spdk_nvmf_request *req) +{ + return 0; +} + +int +spdk_nvmf_ctrlr_process_fabrics_cmd(struct spdk_nvmf_request *req) +{ + return -1; +} + +int +spdk_nvmf_ctrlr_process_admin_cmd(struct spdk_nvmf_request *req) +{ + return -1; +} + +int +spdk_nvmf_ctrlr_process_io_cmd(struct spdk_nvmf_request *req) +{ + return -1; +} + +int +spdk_nvme_ctrlr_cmd_admin_raw(struct spdk_nvme_ctrlr *ctrlr, + struct spdk_nvme_cmd *cmd, + void *buf, uint32_t len, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + return -1; +} + +int +spdk_nvme_ctrlr_cmd_io_raw(struct spdk_nvme_ctrlr *ctrlr, + struct spdk_nvme_qpair *qpair, + struct spdk_nvme_cmd *cmd, + void *buf, uint32_t len, + spdk_nvme_cmd_cb cb_fn, void *cb_arg) +{ + return -1; +} + +uint32_t +spdk_nvme_ctrlr_get_num_ns(struct spdk_nvme_ctrlr *ctrlr) +{ + return 0; +} + +union spdk_nvme_vs_register spdk_nvme_ctrlr_get_regs_vs(struct spdk_nvme_ctrlr *ctrlr) +{ + union spdk_nvme_vs_register vs; + + vs.raw = 0; + return vs; +} + +bool +spdk_nvme_ns_is_active(struct spdk_nvme_ns *ns) +{ + return false; +} + +struct spdk_nvme_ns *spdk_nvme_ctrlr_get_ns(struct spdk_nvme_ctrlr *ctrlr, uint32_t ns_id) +{ + return NULL; +} + +int +spdk_nvmf_qpair_disconnect(struct spdk_nvmf_qpair *qpair, nvmf_qpair_disconnect_cb cb_fn, void *ctx) +{ + return 0; +} + +static void +test_placeholder(void) +{ +} + +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("nvmf", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "placeholder", test_placeholder) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/nvmf/subsystem.c/.gitignore b/src/spdk/test/unit/lib/nvmf/subsystem.c/.gitignore new file mode 100644 index 00000000..76ca0d33 --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/subsystem.c/.gitignore @@ -0,0 +1 @@ +subsystem_ut diff --git a/src/spdk/test/unit/lib/nvmf/subsystem.c/Makefile b/src/spdk/test/unit/lib/nvmf/subsystem.c/Makefile new file mode 100644 index 00000000..b62f1ee1 --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/subsystem.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 = subsystem_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/nvmf/subsystem.c/subsystem_ut.c b/src/spdk/test/unit/lib/nvmf/subsystem.c/subsystem_ut.c new file mode 100644 index 00000000..1b92efd2 --- /dev/null +++ b/src/spdk/test/unit/lib/nvmf/subsystem.c/subsystem_ut.c @@ -0,0 +1,477 @@ +/*- + * 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 "common/lib/test_env.c" +#include "spdk_cunit.h" +#include "spdk_internal/mock.h" + +#include "nvmf/subsystem.c" + +SPDK_LOG_REGISTER_COMPONENT("nvmf", SPDK_LOG_NVMF) + +DEFINE_STUB(spdk_bdev_module_claim_bdev, + int, + (struct spdk_bdev *bdev, struct spdk_bdev_desc *desc, + struct spdk_bdev_module *module), 0); + +DEFINE_STUB_V(spdk_bdev_module_release_bdev, + (struct spdk_bdev *bdev)); + +static void +_subsystem_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx) +{ + fn(ctx); +} + +static void +subsystem_ns_remove_cb(struct spdk_nvmf_subsystem *subsystem, void *cb_arg, int status) +{ +} + +uint32_t +spdk_env_get_current_core(void) +{ + return 0; +} + +struct spdk_event * +spdk_event_allocate(uint32_t core, spdk_event_fn fn, void *arg1, void *arg2) +{ + return NULL; +} + +void +spdk_event_call(struct spdk_event *event) +{ + +} + +int +spdk_nvmf_transport_listen(struct spdk_nvmf_transport *transport, + const struct spdk_nvme_transport_id *trid) +{ + return 0; +} + +void +spdk_nvmf_transport_listener_discover(struct spdk_nvmf_transport *transport, + struct spdk_nvme_transport_id *trid, + struct spdk_nvmf_discovery_log_page_entry *entry) +{ + entry->trtype = 42; +} + +bool +spdk_nvmf_transport_qpair_is_idle(struct spdk_nvmf_qpair *qpair) +{ + return false; +} + +static struct spdk_nvmf_transport g_transport = {}; + +struct spdk_nvmf_transport * +spdk_nvmf_transport_create(enum spdk_nvme_transport_type type, + struct spdk_nvmf_transport_opts *tprt_opts) +{ + if (type == SPDK_NVME_TRANSPORT_RDMA) { + return &g_transport; + } + + return NULL; +} + +struct spdk_nvmf_subsystem * +spdk_nvmf_tgt_find_subsystem(struct spdk_nvmf_tgt *tgt, const char *subnqn) +{ + return NULL; +} + +struct spdk_nvmf_transport * +spdk_nvmf_tgt_get_transport(struct spdk_nvmf_tgt *tgt, enum spdk_nvme_transport_type trtype) +{ + if (trtype == SPDK_NVME_TRANSPORT_RDMA) { + return &g_transport; + } + + return NULL; +} + +int +spdk_nvmf_poll_group_update_subsystem(struct spdk_nvmf_poll_group *group, + struct spdk_nvmf_subsystem *subsystem) +{ + return 0; +} + +int +spdk_nvmf_poll_group_add_subsystem(struct spdk_nvmf_poll_group *group, + struct spdk_nvmf_subsystem *subsystem, + spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg) +{ + return 0; +} + +void +spdk_nvmf_poll_group_remove_subsystem(struct spdk_nvmf_poll_group *group, + struct spdk_nvmf_subsystem *subsystem, + spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg) +{ +} + +void +spdk_nvmf_poll_group_pause_subsystem(struct spdk_nvmf_poll_group *group, + struct spdk_nvmf_subsystem *subsystem, + spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg) +{ +} + +void +spdk_nvmf_poll_group_resume_subsystem(struct spdk_nvmf_poll_group *group, + struct spdk_nvmf_subsystem *subsystem, + spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg) +{ +} + +int +spdk_nvme_transport_id_parse_trtype(enum spdk_nvme_transport_type *trtype, const char *str) +{ + if (trtype == NULL || str == NULL) { + return -EINVAL; + } + + if (strcasecmp(str, "PCIe") == 0) { + *trtype = SPDK_NVME_TRANSPORT_PCIE; + } else if (strcasecmp(str, "RDMA") == 0) { + *trtype = SPDK_NVME_TRANSPORT_RDMA; + } else { + return -ENOENT; + } + return 0; +} + +int +spdk_nvme_transport_id_compare(const struct spdk_nvme_transport_id *trid1, + const struct spdk_nvme_transport_id *trid2) +{ + return 0; +} + +int32_t +spdk_nvme_ctrlr_process_admin_completions(struct spdk_nvme_ctrlr *ctrlr) +{ + return -1; +} + +int32_t +spdk_nvme_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions) +{ + return -1; +} + +int +spdk_nvme_detach(struct spdk_nvme_ctrlr *ctrlr) +{ + return -1; +} + +void +spdk_nvmf_ctrlr_destruct(struct spdk_nvmf_ctrlr *ctrlr) +{ +} + +void +spdk_nvmf_ctrlr_ns_changed(struct spdk_nvmf_ctrlr *ctrlr, uint32_t nsid) +{ +} + +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) +{ + return 0; +} + +void +spdk_bdev_close(struct spdk_bdev_desc *desc) +{ +} + +const char * +spdk_bdev_get_name(const struct spdk_bdev *bdev) +{ + return "test"; +} + +const struct spdk_uuid * +spdk_bdev_get_uuid(const struct spdk_bdev *bdev) +{ + return &bdev->uuid; +} + +static void +test_spdk_nvmf_subsystem_add_ns(void) +{ + struct spdk_nvmf_tgt tgt = {}; + struct spdk_nvmf_subsystem subsystem = { + .max_nsid = 0, + .ns = NULL, + .tgt = &tgt + }; + struct spdk_bdev bdev1 = {}, bdev2 = {}; + struct spdk_nvmf_ns_opts ns_opts; + uint32_t nsid; + + tgt.opts.max_subsystems = 1024; + tgt.subsystems = calloc(tgt.opts.max_subsystems, sizeof(struct spdk_nvmf_subsystem *)); + SPDK_CU_ASSERT_FATAL(tgt.subsystems != NULL); + + /* Allow NSID to be assigned automatically */ + spdk_nvmf_ns_opts_get_defaults(&ns_opts, sizeof(ns_opts)); + nsid = spdk_nvmf_subsystem_add_ns(&subsystem, &bdev1, &ns_opts, sizeof(ns_opts)); + /* NSID 1 is the first unused ID */ + CU_ASSERT(nsid == 1); + CU_ASSERT(subsystem.max_nsid == 1); + SPDK_CU_ASSERT_FATAL(subsystem.ns != NULL); + SPDK_CU_ASSERT_FATAL(subsystem.ns[nsid - 1] != NULL); + CU_ASSERT(subsystem.ns[nsid - 1]->bdev == &bdev1); + + /* Request a specific NSID */ + spdk_nvmf_ns_opts_get_defaults(&ns_opts, sizeof(ns_opts)); + ns_opts.nsid = 5; + nsid = spdk_nvmf_subsystem_add_ns(&subsystem, &bdev2, &ns_opts, sizeof(ns_opts)); + CU_ASSERT(nsid == 5); + CU_ASSERT(subsystem.max_nsid == 5); + SPDK_CU_ASSERT_FATAL(subsystem.ns[nsid - 1] != NULL); + CU_ASSERT(subsystem.ns[nsid - 1]->bdev == &bdev2); + + /* Request an NSID that is already in use */ + spdk_nvmf_ns_opts_get_defaults(&ns_opts, sizeof(ns_opts)); + ns_opts.nsid = 5; + nsid = spdk_nvmf_subsystem_add_ns(&subsystem, &bdev2, &ns_opts, sizeof(ns_opts)); + CU_ASSERT(nsid == 0); + CU_ASSERT(subsystem.max_nsid == 5); + + /* Request 0xFFFFFFFF (invalid NSID, reserved for broadcast) */ + spdk_nvmf_ns_opts_get_defaults(&ns_opts, sizeof(ns_opts)); + ns_opts.nsid = 0xFFFFFFFF; + nsid = spdk_nvmf_subsystem_add_ns(&subsystem, &bdev2, &ns_opts, sizeof(ns_opts)); + CU_ASSERT(nsid == 0); + CU_ASSERT(subsystem.max_nsid == 5); + + spdk_nvmf_subsystem_remove_ns(&subsystem, 1, subsystem_ns_remove_cb, NULL); + spdk_nvmf_subsystem_remove_ns(&subsystem, 5, subsystem_ns_remove_cb, NULL); + + free(subsystem.ns); + free(tgt.subsystems); +} + +static void +nvmf_test_create_subsystem(void) +{ + struct spdk_nvmf_tgt tgt = {}; + char nqn[256]; + struct spdk_nvmf_subsystem *subsystem; + + tgt.opts.max_subsystems = 1024; + tgt.subsystems = calloc(tgt.opts.max_subsystems, sizeof(struct spdk_nvmf_subsystem *)); + SPDK_CU_ASSERT_FATAL(tgt.subsystems != NULL); + + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:subsystem1"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem != NULL); + CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn); + spdk_nvmf_subsystem_destroy(subsystem); + + /* valid name with complex reverse domain */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk-full--rev-domain.name:subsystem1"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem != NULL); + CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn); + spdk_nvmf_subsystem_destroy(subsystem); + + /* Valid name discovery controller */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:subsystem1"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem != NULL); + CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn); + spdk_nvmf_subsystem_destroy(subsystem); + + + /* Invalid name, no user supplied string */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem == NULL); + + /* Valid name, only contains top-level domain name */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:subsystem1"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem != NULL); + CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn); + spdk_nvmf_subsystem_destroy(subsystem); + + /* Invalid name, domain label > 63 characters */ + snprintf(nqn, sizeof(nqn), + "nqn.2016-06.io.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz:sub"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem == NULL); + + /* Invalid name, domain label starts with digit */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.3spdk:sub"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem == NULL); + + /* Invalid name, domain label starts with - */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.-spdk:subsystem1"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem == NULL); + + /* Invalid name, domain label ends with - */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk-:subsystem1"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem == NULL); + + /* Invalid name, domain label with multiple consecutive periods */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io..spdk:subsystem1"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem == NULL); + + /* Longest valid name */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:"); + memset(nqn + strlen(nqn), 'a', 223 - strlen(nqn)); + nqn[223] = '\0'; + CU_ASSERT(strlen(nqn) == 223); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem != NULL); + CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn); + spdk_nvmf_subsystem_destroy(subsystem); + + /* Invalid name, too long */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:"); + memset(nqn + strlen(nqn), 'a', 224 - strlen(nqn)); + nqn[224] = '\0'; + CU_ASSERT(strlen(nqn) == 224); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + CU_ASSERT(subsystem == NULL); + + /* Valid name using uuid format */ + snprintf(nqn, sizeof(nqn), "nqn.2014-08.org.nvmexpress:uuid:11111111-aaaa-bbdd-FFEE-123456789abc"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem != NULL); + CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn); + spdk_nvmf_subsystem_destroy(subsystem); + + /* Invalid name user string contains an invalid utf-8 character */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:\xFFsubsystem1"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem == NULL); + + /* Valid name with non-ascii but valid utf-8 characters */ + snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:\xe1\x8a\x88subsystem1\xca\x80"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem != NULL); + CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn); + spdk_nvmf_subsystem_destroy(subsystem); + + /* Invalid uuid (too long) */ + snprintf(nqn, sizeof(nqn), + "nqn.2014-08.org.nvmexpress:uuid:11111111-aaaa-bbdd-FFEE-123456789abcdef"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem == NULL); + + /* Invalid uuid (dashes placed incorrectly) */ + snprintf(nqn, sizeof(nqn), "nqn.2014-08.org.nvmexpress:uuid:111111-11aaaa-bbdd-FFEE-123456789abc"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem == NULL); + + /* Invalid uuid (invalid characters in uuid) */ + snprintf(nqn, sizeof(nqn), "nqn.2014-08.org.nvmexpress:uuid:111hg111-aaaa-bbdd-FFEE-123456789abc"); + subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0); + SPDK_CU_ASSERT_FATAL(subsystem == NULL); + + free(tgt.subsystems); +} + +static void +test_spdk_nvmf_subsystem_set_sn(void) +{ + struct spdk_nvmf_subsystem subsystem = {}; + + /* Basic valid serial number */ + CU_ASSERT(spdk_nvmf_subsystem_set_sn(&subsystem, "abcd xyz") == 0); + CU_ASSERT(strcmp(subsystem.sn, "abcd xyz") == 0); + + /* Exactly 20 characters (valid) */ + CU_ASSERT(spdk_nvmf_subsystem_set_sn(&subsystem, "12345678901234567890") == 0); + CU_ASSERT(strcmp(subsystem.sn, "12345678901234567890") == 0); + + /* 21 characters (too long, invalid) */ + CU_ASSERT(spdk_nvmf_subsystem_set_sn(&subsystem, "123456789012345678901") < 0); + + /* Non-ASCII characters (invalid) */ + CU_ASSERT(spdk_nvmf_subsystem_set_sn(&subsystem, "abcd\txyz") < 0); +} + +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("nvmf", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "create_subsystem", nvmf_test_create_subsystem) == NULL || + CU_add_test(suite, "nvmf_subsystem_add_ns", test_spdk_nvmf_subsystem_add_ns) == NULL || + CU_add_test(suite, "nvmf_subsystem_set_sn", test_spdk_nvmf_subsystem_set_sn) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + spdk_allocate_thread(_subsystem_send_msg, NULL, NULL, NULL, "thread0"); + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + spdk_free_thread(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/scsi/Makefile b/src/spdk/test/unit/lib/scsi/Makefile new file mode 100644 index 00000000..9e413897 --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = dev.c lun.c scsi.c scsi_bdev.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/scsi/dev.c/.gitignore b/src/spdk/test/unit/lib/scsi/dev.c/.gitignore new file mode 100644 index 00000000..e325086b --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/dev.c/.gitignore @@ -0,0 +1 @@ +dev_ut diff --git a/src/spdk/test/unit/lib/scsi/dev.c/Makefile b/src/spdk/test/unit/lib/scsi/dev.c/Makefile new file mode 100644 index 00000000..4e7a5fa9 --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/dev.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = dev_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/scsi/dev.c/dev_ut.c b/src/spdk/test/unit/lib/scsi/dev.c/dev_ut.c new file mode 100644 index 00000000..c10a7f0a --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/dev.c/dev_ut.c @@ -0,0 +1,681 @@ +/*- + * 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 "CUnit/Basic.h" +#include "spdk_cunit.h" + +#include "spdk/util.h" + +#include "scsi/dev.c" +#include "scsi/port.c" + +/* Unit test bdev mockup */ +struct spdk_bdev { + char name[100]; +}; + +static struct spdk_bdev g_bdevs[] = { + {"malloc0"}, + {"malloc1"}, +}; + +const char * +spdk_bdev_get_name(const struct spdk_bdev *bdev) +{ + return bdev->name; +} + +static struct spdk_scsi_task * +spdk_get_task(uint32_t *owner_task_ctr) +{ + struct spdk_scsi_task *task; + + task = calloc(1, sizeof(*task)); + if (!task) { + return NULL; + } + + return task; +} + +void +spdk_scsi_task_put(struct spdk_scsi_task *task) +{ + free(task); +} + +_spdk_scsi_lun * +spdk_scsi_lun_construct(struct spdk_bdev *bdev, + void (*hotremove_cb)(const struct spdk_scsi_lun *, void *), + void *hotremove_ctx) +{ + struct spdk_scsi_lun *lun; + + lun = calloc(1, sizeof(struct spdk_scsi_lun)); + SPDK_CU_ASSERT_FATAL(lun != NULL); + + lun->bdev = bdev; + + return lun; +} + +void +spdk_scsi_lun_destruct(struct spdk_scsi_lun *lun) +{ + free(lun); +} + +struct spdk_bdev * +spdk_bdev_get_by_name(const char *bdev_name) +{ + size_t i; + + for (i = 0; i < SPDK_COUNTOF(g_bdevs); i++) { + if (strcmp(bdev_name, g_bdevs[i].name) == 0) { + return &g_bdevs[i]; + } + } + + return NULL; +} + +int +spdk_scsi_lun_task_mgmt_execute(struct spdk_scsi_task *task, enum spdk_scsi_task_func func) +{ + return 0; +} + +void +spdk_scsi_lun_execute_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task) +{ +} + +int +_spdk_scsi_lun_allocate_io_channel(struct spdk_scsi_lun *lun) +{ + return 0; +} + +void +_spdk_scsi_lun_free_io_channel(struct spdk_scsi_lun *lun) +{ +} + +bool +spdk_scsi_lun_has_pending_tasks(const struct spdk_scsi_lun *lun) +{ + return false; +} + +static void +dev_destruct_null_dev(void) +{ + /* pass null for the dev */ + spdk_scsi_dev_destruct(NULL); +} + +static void +dev_destruct_zero_luns(void) +{ + struct spdk_scsi_dev dev = { .is_allocated = 1 }; + + /* No luns attached to the dev */ + + /* free the dev */ + spdk_scsi_dev_destruct(&dev); +} + +static void +dev_destruct_null_lun(void) +{ + struct spdk_scsi_dev dev = { .is_allocated = 1 }; + + /* pass null for the lun */ + dev.lun[0] = NULL; + + /* free the dev */ + spdk_scsi_dev_destruct(&dev); +} + +static void +dev_destruct_success(void) +{ + struct spdk_scsi_dev dev = { .is_allocated = 1 }; + int rc; + + /* dev with a single lun */ + rc = spdk_scsi_dev_add_lun(&dev, "malloc0", 0, NULL, NULL); + + CU_ASSERT(rc == 0); + + /* free the dev */ + spdk_scsi_dev_destruct(&dev); + +} + +static void +dev_construct_num_luns_zero(void) +{ + struct spdk_scsi_dev *dev; + const char *bdev_name_list[1] = {}; + int lun_id_list[1] = { 0 }; + + dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 0, + SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); + + /* dev should be null since we passed num_luns = 0 */ + CU_ASSERT_TRUE(dev == NULL); +} + +static void +dev_construct_no_lun_zero(void) +{ + struct spdk_scsi_dev *dev; + const char *bdev_name_list[1] = {}; + int lun_id_list[1] = { 0 }; + + lun_id_list[0] = 1; + + dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1, + SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); + + /* dev should be null since no LUN0 was specified (lun_id_list[0] = 1) */ + CU_ASSERT_TRUE(dev == NULL); +} + +static void +dev_construct_null_lun(void) +{ + struct spdk_scsi_dev *dev; + const char *bdev_name_list[1] = {}; + int lun_id_list[1] = { 0 }; + + dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1, + SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); + + /* dev should be null since no LUN0 was specified (lun_list[0] = NULL) */ + CU_ASSERT_TRUE(dev == NULL); +} + +static void +dev_construct_name_too_long(void) +{ + struct spdk_scsi_dev *dev; + const char *bdev_name_list[1] = {"malloc0"}; + int lun_id_list[1] = { 0 }; + char name[SPDK_SCSI_DEV_MAX_NAME + 1 + 1]; + + /* Try to construct a dev with a name that is one byte longer than allowed. */ + memset(name, 'x', sizeof(name) - 1); + name[sizeof(name) - 1] = '\0'; + + dev = spdk_scsi_dev_construct(name, bdev_name_list, lun_id_list, 1, + SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); + + CU_ASSERT(dev == NULL); +} + +static void +dev_construct_success(void) +{ + struct spdk_scsi_dev *dev; + const char *bdev_name_list[1] = {"malloc0"}; + int lun_id_list[1] = { 0 }; + + dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1, + SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); + + /* Successfully constructs and returns a dev */ + CU_ASSERT_TRUE(dev != NULL); + + /* free the dev */ + spdk_scsi_dev_destruct(dev); +} + +static void +dev_construct_success_lun_zero_not_first(void) +{ + struct spdk_scsi_dev *dev; + const char *bdev_name_list[2] = {"malloc1", "malloc0"}; + int lun_id_list[2] = { 1, 0 }; + + dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 2, + SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); + + /* Successfully constructs and returns a dev */ + CU_ASSERT_TRUE(dev != NULL); + + /* free the dev */ + spdk_scsi_dev_destruct(dev); +} + +static void +dev_queue_mgmt_task_success(void) +{ + struct spdk_scsi_dev *dev; + const char *bdev_name_list[1] = {"malloc0"}; + int lun_id_list[1] = { 0 }; + struct spdk_scsi_task *task; + + dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1, + SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); + + /* Successfully constructs and returns a dev */ + CU_ASSERT_TRUE(dev != NULL); + + task = spdk_get_task(NULL); + + spdk_scsi_dev_queue_mgmt_task(dev, task, SPDK_SCSI_TASK_FUNC_LUN_RESET); + + spdk_scsi_task_put(task); + + spdk_scsi_dev_destruct(dev); +} + +static void +dev_queue_task_success(void) +{ + struct spdk_scsi_dev *dev; + const char *bdev_name_list[1] = {"malloc0"}; + int lun_id_list[1] = { 0 }; + struct spdk_scsi_task *task; + + dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1, + SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); + + /* Successfully constructs and returns a dev */ + CU_ASSERT_TRUE(dev != NULL); + + task = spdk_get_task(NULL); + + spdk_scsi_dev_queue_task(dev, task); + + spdk_scsi_task_put(task); + + spdk_scsi_dev_destruct(dev); +} + +static void +dev_stop_success(void) +{ + struct spdk_scsi_dev dev = { 0 }; + struct spdk_scsi_task *task; + struct spdk_scsi_task *task_mgmt; + + task = spdk_get_task(NULL); + + spdk_scsi_dev_queue_task(&dev, task); + + task_mgmt = spdk_get_task(NULL); + + /* Enqueue the tasks into dev->task_mgmt_submit_queue */ + spdk_scsi_dev_queue_mgmt_task(&dev, task_mgmt, SPDK_SCSI_TASK_FUNC_LUN_RESET); + + spdk_scsi_task_put(task); + spdk_scsi_task_put(task_mgmt); +} + +static void +dev_add_port_max_ports(void) +{ + struct spdk_scsi_dev dev = { 0 }; + const char *name; + int id, rc; + + /* dev is set to SPDK_SCSI_DEV_MAX_PORTS */ + dev.num_ports = SPDK_SCSI_DEV_MAX_PORTS; + name = "Name of Port"; + id = 1; + + rc = spdk_scsi_dev_add_port(&dev, id, name); + + /* returns -1; since the dev already has maximum + * number of ports (SPDK_SCSI_DEV_MAX_PORTS) */ + CU_ASSERT_TRUE(rc < 0); +} + +static void +dev_add_port_construct_failure1(void) +{ + struct spdk_scsi_dev dev = { 0 }; + const int port_name_length = SPDK_SCSI_PORT_MAX_NAME_LENGTH + 2; + char name[port_name_length]; + uint64_t id; + int rc; + + dev.num_ports = 1; + /* Set the name such that the length exceeds SPDK_SCSI_PORT_MAX_NAME_LENGTH + * SPDK_SCSI_PORT_MAX_NAME_LENGTH = 256 */ + memset(name, 'a', port_name_length - 1); + name[port_name_length - 1] = '\0'; + id = 1; + + rc = spdk_scsi_dev_add_port(&dev, id, name); + + /* returns -1; since the length of the name exceeds + * SPDK_SCSI_PORT_MAX_NAME_LENGTH */ + CU_ASSERT_TRUE(rc < 0); +} + +static void +dev_add_port_construct_failure2(void) +{ + struct spdk_scsi_dev dev = { 0 }; + const char *name; + uint64_t id; + int rc; + + dev.num_ports = 1; + name = "Name of Port"; + id = 1; + + /* Initialize port[0] to be valid and its index is set to 1 */ + dev.port[0].id = id; + dev.port[0].is_used = 1; + + rc = spdk_scsi_dev_add_port(&dev, id, name); + + /* returns -1; since the dev already has a port whose index to be 1 */ + CU_ASSERT_TRUE(rc < 0); +} + +static void +dev_add_port_success1(void) +{ + struct spdk_scsi_dev dev = { 0 }; + const char *name; + int id, rc; + + dev.num_ports = 1; + name = "Name of Port"; + id = 1; + + rc = spdk_scsi_dev_add_port(&dev, id, name); + + /* successfully adds a port */ + CU_ASSERT_EQUAL(rc, 0); + /* Assert num_ports has been incremented to 2 */ + CU_ASSERT_EQUAL(dev.num_ports, 2); +} + +static void +dev_add_port_success2(void) +{ + struct spdk_scsi_dev dev = { 0 }; + const char *name; + uint64_t id; + int rc; + + dev.num_ports = 1; + name = "Name of Port"; + id = 1; + /* set id of invalid port[0] to 1. This must be ignored */ + dev.port[0].id = id; + dev.port[0].is_used = 0; + + rc = spdk_scsi_dev_add_port(&dev, id, name); + + /* successfully adds a port */ + CU_ASSERT_EQUAL(rc, 0); + /* Assert num_ports has been incremented to 1 */ + CU_ASSERT_EQUAL(dev.num_ports, 2); +} + +static void +dev_add_port_success3(void) +{ + struct spdk_scsi_dev dev = { 0 }; + const char *name; + uint64_t add_id; + int rc; + + dev.num_ports = 1; + name = "Name of Port"; + dev.port[0].id = 1; + dev.port[0].is_used = 1; + add_id = 2; + + /* Add a port with id = 2 */ + rc = spdk_scsi_dev_add_port(&dev, add_id, name); + + /* successfully adds a port */ + CU_ASSERT_EQUAL(rc, 0); + /* Assert num_ports has been incremented to 2 */ + CU_ASSERT_EQUAL(dev.num_ports, 2); +} + +static void +dev_find_port_by_id_num_ports_zero(void) +{ + struct spdk_scsi_dev dev = { 0 }; + struct spdk_scsi_port *rp_port; + uint64_t id; + + dev.num_ports = 0; + id = 1; + + rp_port = spdk_scsi_dev_find_port_by_id(&dev, id); + + /* returns null; since dev's num_ports is 0 */ + CU_ASSERT_TRUE(rp_port == NULL); +} + +static void +dev_find_port_by_id_id_not_found_failure(void) +{ + struct spdk_scsi_dev dev = { 0 }; + struct spdk_scsi_port *rp_port; + const char *name; + int rc; + uint64_t id, find_id; + + id = 1; + dev.num_ports = 1; + name = "Name of Port"; + find_id = 2; + + /* Add a port with id = 1 */ + rc = spdk_scsi_dev_add_port(&dev, id, name); + + CU_ASSERT_EQUAL(rc, 0); + + /* Find port with id = 2 */ + rp_port = spdk_scsi_dev_find_port_by_id(&dev, find_id); + + /* returns null; failed to find port specified by id = 2 */ + CU_ASSERT_TRUE(rp_port == NULL); +} + +static void +dev_find_port_by_id_success(void) +{ + struct spdk_scsi_dev dev = { 0 }; + struct spdk_scsi_port *rp_port; + const char *name; + int rc; + uint64_t id; + + id = 1; + dev.num_ports = 1; + name = "Name of Port"; + + /* Add a port */ + rc = spdk_scsi_dev_add_port(&dev, id, name); + + CU_ASSERT_EQUAL(rc, 0); + + /* Find port by the same id as the one added above */ + rp_port = spdk_scsi_dev_find_port_by_id(&dev, id); + + /* Successfully found port specified by id */ + CU_ASSERT_TRUE(rp_port != NULL); + if (rp_port != NULL) { + /* Assert the found port's id and name are same as + * the port added. */ + CU_ASSERT_EQUAL(rp_port->id, 1); + CU_ASSERT_STRING_EQUAL(rp_port->name, "Name of Port"); + } +} + +static void +dev_add_lun_bdev_not_found(void) +{ + int rc; + struct spdk_scsi_dev dev = {0}; + + rc = spdk_scsi_dev_add_lun(&dev, "malloc2", 0, NULL, NULL); + + SPDK_CU_ASSERT_FATAL(dev.lun[0] == NULL); + CU_ASSERT_NOT_EQUAL(rc, 0); +} + +static void +dev_add_lun_no_free_lun_id(void) +{ + int rc; + int i; + struct spdk_scsi_dev dev = {0}; + struct spdk_scsi_lun lun; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + dev.lun[i] = &lun; + } + + rc = spdk_scsi_dev_add_lun(&dev, "malloc0", -1, NULL, NULL); + + CU_ASSERT_NOT_EQUAL(rc, 0); +} + +static void +dev_add_lun_success1(void) +{ + int rc; + struct spdk_scsi_dev dev = {0}; + + rc = spdk_scsi_dev_add_lun(&dev, "malloc0", -1, NULL, NULL); + + CU_ASSERT_EQUAL(rc, 0); + + spdk_scsi_dev_destruct(&dev); +} + +static void +dev_add_lun_success2(void) +{ + int rc; + struct spdk_scsi_dev dev = {0}; + + rc = spdk_scsi_dev_add_lun(&dev, "malloc0", 0, NULL, NULL); + + CU_ASSERT_EQUAL(rc, 0); + + spdk_scsi_dev_destruct(&dev); +} + +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("dev_suite", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "destruct - null dev", + dev_destruct_null_dev) == NULL + || CU_add_test(suite, "destruct - zero luns", dev_destruct_zero_luns) == NULL + || CU_add_test(suite, "destruct - null lun", dev_destruct_null_lun) == NULL + || CU_add_test(suite, "destruct - success", dev_destruct_success) == NULL + || CU_add_test(suite, "construct - queue depth gt max depth", + dev_construct_num_luns_zero) == NULL + || CU_add_test(suite, "construct - no lun0", + dev_construct_no_lun_zero) == NULL + || CU_add_test(suite, "construct - null lun", + dev_construct_null_lun) == NULL + || CU_add_test(suite, "construct - name too long", dev_construct_name_too_long) == NULL + || CU_add_test(suite, "construct - success", dev_construct_success) == NULL + || CU_add_test(suite, "construct - success - LUN zero not first", + dev_construct_success_lun_zero_not_first) == NULL + || CU_add_test(suite, "dev queue task mgmt - success", + dev_queue_mgmt_task_success) == NULL + || CU_add_test(suite, "dev queue task - success", + dev_queue_task_success) == NULL + || CU_add_test(suite, "dev stop - success", dev_stop_success) == NULL + || CU_add_test(suite, "dev add port - max ports", + dev_add_port_max_ports) == NULL + || CU_add_test(suite, "dev add port - construct port failure 1", + dev_add_port_construct_failure1) == NULL + || CU_add_test(suite, "dev add port - construct port failure 2", + dev_add_port_construct_failure2) == NULL + || CU_add_test(suite, "dev add port - success 1", + dev_add_port_success1) == NULL + || CU_add_test(suite, "dev add port - success 2", + dev_add_port_success2) == NULL + || CU_add_test(suite, "dev add port - success 3", + dev_add_port_success3) == NULL + || CU_add_test(suite, "dev find port by id - num ports zero", + dev_find_port_by_id_num_ports_zero) == NULL + || CU_add_test(suite, "dev find port by id - different port id failure", + dev_find_port_by_id_id_not_found_failure) == NULL + || CU_add_test(suite, "dev find port by id - success", + dev_find_port_by_id_success) == NULL + || CU_add_test(suite, "dev add lun - bdev not found", + dev_add_lun_bdev_not_found) == NULL + || CU_add_test(suite, "dev add lun - no free lun id", + dev_add_lun_no_free_lun_id) == NULL + || CU_add_test(suite, "dev add lun - success 1", + dev_add_lun_success1) == NULL + || CU_add_test(suite, "dev add lun - success 2", + dev_add_lun_success2) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/scsi/lun.c/.gitignore b/src/spdk/test/unit/lib/scsi/lun.c/.gitignore new file mode 100644 index 00000000..89bd2aaf --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/lun.c/.gitignore @@ -0,0 +1 @@ +lun_ut diff --git a/src/spdk/test/unit/lib/scsi/lun.c/Makefile b/src/spdk/test/unit/lib/scsi/lun.c/Makefile new file mode 100644 index 00000000..22841b0d --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/lun.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = lun_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/scsi/lun.c/lun_ut.c b/src/spdk/test/unit/lib/scsi/lun.c/lun_ut.c new file mode 100644 index 00000000..2237e8ed --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/lun.c/lun_ut.c @@ -0,0 +1,654 @@ +/*- + * 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 "scsi/task.c" +#include "scsi/lun.c" + +#include "spdk_internal/mock.h" + +/* Unit test bdev mockup */ +struct spdk_bdev { + int x; +}; + +SPDK_LOG_REGISTER_COMPONENT("scsi", SPDK_LOG_SCSI) + +struct spdk_scsi_globals g_spdk_scsi; + +static bool g_lun_execute_fail = false; +static int g_lun_execute_status = SPDK_SCSI_TASK_PENDING; +static uint32_t g_task_count = 0; + +struct spdk_poller * +spdk_poller_register(spdk_poller_fn fn, + void *arg, + uint64_t period_microseconds) +{ + return NULL; +} + +void +spdk_poller_unregister(struct spdk_poller **ppoller) +{ +} + +void +spdk_thread_send_msg(const struct spdk_thread *thread, spdk_thread_fn fn, void *ctx) +{ +} + +struct spdk_trace_histories *g_trace_histories; +void _spdk_trace_record(uint64_t tsc, uint16_t tpoint_id, uint16_t poller_id, + uint32_t size, uint64_t object_id, uint64_t arg1) +{ +} + +static void +spdk_lun_ut_cpl_task(struct spdk_scsi_task *task) +{ + SPDK_CU_ASSERT_FATAL(g_task_count > 0); + g_task_count--; +} + +static void +spdk_lun_ut_free_task(struct spdk_scsi_task *task) +{ +} + +static void +ut_init_task(struct spdk_scsi_task *task) +{ + memset(task, 0, sizeof(*task)); + spdk_scsi_task_construct(task, spdk_lun_ut_cpl_task, + spdk_lun_ut_free_task); + g_task_count++; +} + +void * +spdk_dma_malloc(size_t size, size_t align, uint64_t *phys_addr) +{ + void *buf = malloc(size); + if (phys_addr) { + *phys_addr = (uint64_t)buf; + } + return buf; +} + +void * +spdk_dma_zmalloc(size_t size, size_t align, uint64_t *phys_addr) +{ + void *buf = calloc(size, 1); + if (phys_addr) { + *phys_addr = (uint64_t)buf; + } + return buf; +} + +void +spdk_dma_free(void *buf) +{ + free(buf); +} + +void +spdk_bdev_free_io(struct spdk_bdev_io *bdev_io) +{ + CU_ASSERT(0); +} + +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) +{ + return 0; +} + +void +spdk_bdev_close(struct spdk_bdev_desc *desc) +{ +} + +const char * +spdk_bdev_get_name(const struct spdk_bdev *bdev) +{ + return "test"; +} + +void spdk_scsi_dev_queue_mgmt_task(struct spdk_scsi_dev *dev, + struct spdk_scsi_task *task, + enum spdk_scsi_task_func func) +{ +} + +void spdk_scsi_dev_delete_lun(struct spdk_scsi_dev *dev, + struct spdk_scsi_lun *lun) +{ + return; +} + +void +spdk_bdev_scsi_reset(struct spdk_scsi_task *task) +{ + return; +} + +int +spdk_bdev_scsi_execute(struct spdk_scsi_task *task) +{ + if (g_lun_execute_fail) { + return -EINVAL; + } else { + task->status = SPDK_SCSI_STATUS_GOOD; + + if (g_lun_execute_status == SPDK_SCSI_TASK_PENDING) { + return g_lun_execute_status; + } else if (g_lun_execute_status == SPDK_SCSI_TASK_COMPLETE) { + return g_lun_execute_status; + } else { + return 0; + } + } +} + +struct spdk_io_channel * +spdk_bdev_get_io_channel(struct spdk_bdev_desc *desc) +{ + return NULL; +} + +void +spdk_put_io_channel(struct spdk_io_channel *ch) +{ +} + +DEFINE_STUB(spdk_io_channel_get_thread, struct spdk_thread *, (struct spdk_io_channel *ch), NULL) +DEFINE_STUB(spdk_get_thread, struct spdk_thread *, (void), NULL) + +static _spdk_scsi_lun * +lun_construct(void) +{ + struct spdk_scsi_lun *lun; + struct spdk_bdev bdev; + + lun = spdk_scsi_lun_construct(&bdev, NULL, NULL); + + SPDK_CU_ASSERT_FATAL(lun != NULL); + return lun; +} + +static void +lun_destruct(struct spdk_scsi_lun *lun) +{ + /* LUN will defer its removal if there are any unfinished tasks */ + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&lun->tasks)); + + spdk_scsi_lun_destruct(lun); +} + +static void +lun_task_mgmt_execute_null_task(void) +{ + int rc; + + rc = spdk_scsi_lun_task_mgmt_execute(NULL, SPDK_SCSI_TASK_FUNC_ABORT_TASK); + + /* returns -1 since we passed NULL for the task */ + CU_ASSERT_TRUE(rc < 0); + CU_ASSERT_EQUAL(g_task_count, 0); +} + +static void +lun_task_mgmt_execute_abort_task_null_lun_failure(void) +{ + struct spdk_scsi_task mgmt_task = { 0 }; + struct spdk_scsi_port initiator_port = { 0 }; + int rc; + + ut_init_task(&mgmt_task); + mgmt_task.lun = NULL; + mgmt_task.initiator_port = &initiator_port; + + rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_ABORT_TASK); + + /* returns -1 since we passed NULL for LUN */ + CU_ASSERT_TRUE(rc < 0); + CU_ASSERT_EQUAL(g_task_count, 0); +} + +static void +lun_task_mgmt_execute_abort_task_not_supported(void) +{ + struct spdk_scsi_lun *lun; + struct spdk_scsi_task task = { 0 }; + struct spdk_scsi_task mgmt_task = { 0 }; + struct spdk_scsi_port initiator_port = { 0 }; + struct spdk_scsi_dev dev = { 0 }; + uint8_t cdb[6] = { 0 }; + int rc; + + lun = lun_construct(); + lun->dev = &dev; + + ut_init_task(&mgmt_task); + mgmt_task.lun = lun; + mgmt_task.initiator_port = &initiator_port; + + /* Params to add regular task to the lun->tasks */ + ut_init_task(&task); + task.lun = lun; + task.cdb = cdb; + + spdk_scsi_lun_execute_task(lun, &task); + + /* task should now be on the tasks list */ + CU_ASSERT(!TAILQ_EMPTY(&lun->tasks)); + + rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_ABORT_TASK); + + /* returns -1 since task abort is not supported */ + CU_ASSERT_TRUE(rc < 0); + CU_ASSERT(mgmt_task.response == SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED); + + /* task is still on the tasks list */ + CU_ASSERT_EQUAL(g_task_count, 1); + + spdk_scsi_lun_complete_task(lun, &task); + CU_ASSERT_EQUAL(g_task_count, 0); + + lun_destruct(lun); +} + +static void +lun_task_mgmt_execute_abort_task_all_null_lun_failure(void) +{ + struct spdk_scsi_task mgmt_task = { 0 }; + struct spdk_scsi_port initiator_port = { 0 }; + int rc; + + ut_init_task(&mgmt_task); + mgmt_task.lun = NULL; + mgmt_task.initiator_port = &initiator_port; + + rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_ABORT_TASK_SET); + + /* Returns -1 since we passed NULL for lun */ + CU_ASSERT_TRUE(rc < 0); + + CU_ASSERT_EQUAL(g_task_count, 0); +} + +static void +lun_task_mgmt_execute_abort_task_all_not_supported(void) +{ + struct spdk_scsi_lun *lun; + struct spdk_scsi_task task = { 0 }; + struct spdk_scsi_task mgmt_task = { 0 }; + struct spdk_scsi_port initiator_port = { 0 }; + struct spdk_scsi_dev dev = { 0 }; + int rc; + uint8_t cdb[6] = { 0 }; + + lun = lun_construct(); + lun->dev = &dev; + + ut_init_task(&mgmt_task); + mgmt_task.lun = lun; + mgmt_task.initiator_port = &initiator_port; + + /* Params to add regular task to the lun->tasks */ + ut_init_task(&task); + task.initiator_port = &initiator_port; + task.lun = lun; + task.cdb = cdb; + + spdk_scsi_lun_execute_task(lun, &task); + + /* task should now be on the tasks list */ + CU_ASSERT(!TAILQ_EMPTY(&lun->tasks)); + + rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_ABORT_TASK_SET); + + /* returns -1 since task abort is not supported */ + CU_ASSERT_TRUE(rc < 0); + CU_ASSERT(mgmt_task.response == SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED); + + /* task is still on the tasks list */ + CU_ASSERT_EQUAL(g_task_count, 1); + + spdk_scsi_lun_complete_task(lun, &task); + + CU_ASSERT_EQUAL(g_task_count, 0); + + lun_destruct(lun); +} + +static void +lun_task_mgmt_execute_lun_reset_failure(void) +{ + struct spdk_scsi_task mgmt_task = { 0 }; + int rc; + + ut_init_task(&mgmt_task); + mgmt_task.lun = NULL; + + rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_LUN_RESET); + + /* Returns -1 since we passed NULL for lun */ + CU_ASSERT_TRUE(rc < 0); + + CU_ASSERT_EQUAL(g_task_count, 0); +} + +static void +lun_task_mgmt_execute_lun_reset(void) +{ + struct spdk_scsi_lun *lun; + struct spdk_scsi_task mgmt_task = { 0 }; + struct spdk_scsi_dev dev = { 0 }; + int rc; + + lun = lun_construct(); + lun->dev = &dev; + + ut_init_task(&mgmt_task); + mgmt_task.lun = lun; + + rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_LUN_RESET); + + /* Returns success */ + CU_ASSERT_EQUAL(rc, 0); + + lun_destruct(lun); + + /* task is still on the tasks list */ + CU_ASSERT_EQUAL(g_task_count, 1); + g_task_count = 0; +} + +static void +lun_task_mgmt_execute_invalid_case(void) +{ + struct spdk_scsi_lun *lun; + struct spdk_scsi_task mgmt_task = { 0 }; + struct spdk_scsi_dev dev = { 0 }; + int rc; + + lun = lun_construct(); + lun->dev = &dev; + + ut_init_task(&mgmt_task); + /* Pass an invalid value to the switch statement */ + rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, 5); + + /* Returns -1 on passing an invalid value to the switch case */ + CU_ASSERT_TRUE(rc < 0); + + lun_destruct(lun); + + CU_ASSERT_EQUAL(g_task_count, 0); +} + +static void +lun_append_task_null_lun_task_cdb_spc_inquiry(void) +{ + struct spdk_scsi_task task = { 0 }; + uint8_t cdb[6] = { 0 }; + + ut_init_task(&task); + task.cdb = cdb; + task.cdb[0] = SPDK_SPC_INQUIRY; + /* alloc_len >= 4096 */ + task.cdb[3] = 0xFF; + task.cdb[4] = 0xFF; + task.lun = NULL; + + spdk_scsi_task_process_null_lun(&task); + + CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_GOOD); + + spdk_scsi_task_put(&task); + + /* spdk_scsi_task_process_null_lun() does not call cpl_fn */ + CU_ASSERT_EQUAL(g_task_count, 1); + g_task_count = 0; +} + +static void +lun_append_task_null_lun_alloc_len_lt_4096(void) +{ + struct spdk_scsi_task task = { 0 }; + uint8_t cdb[6] = { 0 }; + + ut_init_task(&task); + task.cdb = cdb; + task.cdb[0] = SPDK_SPC_INQUIRY; + /* alloc_len < 4096 */ + task.cdb[3] = 0; + task.cdb[4] = 0; + /* alloc_len is set to a minimal value of 4096 + * Hence, buf of size 4096 is allocated */ + spdk_scsi_task_process_null_lun(&task); + + CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_GOOD); + + spdk_scsi_task_put(&task); + + /* spdk_scsi_task_process_null_lun() does not call cpl_fn */ + CU_ASSERT_EQUAL(g_task_count, 1); + g_task_count = 0; +} + +static void +lun_append_task_null_lun_not_supported(void) +{ + struct spdk_scsi_task task = { 0 }; + uint8_t cdb[6] = { 0 }; + + ut_init_task(&task); + task.cdb = cdb; + task.lun = NULL; + + spdk_scsi_task_process_null_lun(&task); + + CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_CHECK_CONDITION); + /* LUN not supported; task's data transferred should be 0 */ + CU_ASSERT_EQUAL(task.data_transferred, 0); + + /* spdk_scsi_task_process_null_lun() does not call cpl_fn */ + CU_ASSERT_EQUAL(g_task_count, 1); + g_task_count = 0; +} + +static void +lun_execute_scsi_task_pending(void) +{ + struct spdk_scsi_lun *lun; + struct spdk_scsi_task task = { 0 }; + struct spdk_scsi_dev dev = { 0 }; + + lun = lun_construct(); + + ut_init_task(&task); + task.lun = lun; + lun->dev = &dev; + + g_lun_execute_fail = false; + g_lun_execute_status = SPDK_SCSI_TASK_PENDING; + + /* the tasks list should still be empty since it has not been + executed yet + */ + CU_ASSERT(TAILQ_EMPTY(&lun->tasks)); + + spdk_scsi_lun_execute_task(lun, &task); + + /* Assert the task has been successfully added to the tasks queue */ + CU_ASSERT(!TAILQ_EMPTY(&lun->tasks)); + + /* task is still on the tasks list */ + CU_ASSERT_EQUAL(g_task_count, 1); + + /* Need to complete task so LUN might be removed right now */ + spdk_scsi_lun_complete_task(lun, &task); + + CU_ASSERT_EQUAL(g_task_count, 0); + + lun_destruct(lun); +} + +static void +lun_execute_scsi_task_complete(void) +{ + struct spdk_scsi_lun *lun; + struct spdk_scsi_task task = { 0 }; + struct spdk_scsi_dev dev = { 0 }; + + lun = lun_construct(); + + ut_init_task(&task); + task.lun = lun; + lun->dev = &dev; + + g_lun_execute_fail = false; + g_lun_execute_status = SPDK_SCSI_TASK_COMPLETE; + + /* the tasks list should still be empty since it has not been + executed yet + */ + CU_ASSERT(TAILQ_EMPTY(&lun->tasks)); + + spdk_scsi_lun_execute_task(lun, &task); + + /* Assert the task has not been added to the tasks queue */ + CU_ASSERT(TAILQ_EMPTY(&lun->tasks)); + + lun_destruct(lun); + + CU_ASSERT_EQUAL(g_task_count, 0); +} + +static void +lun_destruct_success(void) +{ + struct spdk_scsi_lun *lun; + + lun = lun_construct(); + + spdk_scsi_lun_destruct(lun); + + CU_ASSERT_EQUAL(g_task_count, 0); +} + +static void +lun_construct_null_ctx(void) +{ + struct spdk_scsi_lun *lun; + + lun = spdk_scsi_lun_construct(NULL, NULL, NULL); + + /* lun should be NULL since we passed NULL for the ctx pointer. */ + CU_ASSERT(lun == NULL); + CU_ASSERT_EQUAL(g_task_count, 0); +} + +static void +lun_construct_success(void) +{ + struct spdk_scsi_lun *lun = lun_construct(); + + lun_destruct(lun); + + CU_ASSERT_EQUAL(g_task_count, 0); +} + +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("lun_suite", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "task management - null task failure", + lun_task_mgmt_execute_null_task) == NULL + || CU_add_test(suite, "task management abort task - null lun failure", + lun_task_mgmt_execute_abort_task_null_lun_failure) == NULL + || CU_add_test(suite, "task management abort task - not supported", + lun_task_mgmt_execute_abort_task_not_supported) == NULL + || CU_add_test(suite, "task management abort task set - null lun failure", + lun_task_mgmt_execute_abort_task_all_null_lun_failure) == NULL + || CU_add_test(suite, "task management abort task set - success", + lun_task_mgmt_execute_abort_task_all_not_supported) == NULL + || CU_add_test(suite, "task management - lun reset failure", + lun_task_mgmt_execute_lun_reset_failure) == NULL + || CU_add_test(suite, "task management - lun reset success", + lun_task_mgmt_execute_lun_reset) == NULL + || CU_add_test(suite, "task management - invalid option", + lun_task_mgmt_execute_invalid_case) == NULL + || CU_add_test(suite, "append task - null lun SPDK_SPC_INQUIRY", + lun_append_task_null_lun_task_cdb_spc_inquiry) == NULL + || CU_add_test(suite, "append task - allocated length less than 4096", + lun_append_task_null_lun_alloc_len_lt_4096) == NULL + || CU_add_test(suite, "append task - unsupported lun", + lun_append_task_null_lun_not_supported) == NULL + || CU_add_test(suite, "execute task - scsi task pending", + lun_execute_scsi_task_pending) == NULL + || CU_add_test(suite, "execute task - scsi task complete", + lun_execute_scsi_task_complete) == NULL + || CU_add_test(suite, "destruct task - success", lun_destruct_success) == NULL + || CU_add_test(suite, "construct - null ctx", lun_construct_null_ctx) == NULL + || CU_add_test(suite, "construct - success", lun_construct_success) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/scsi/scsi.c/.gitignore b/src/spdk/test/unit/lib/scsi/scsi.c/.gitignore new file mode 100644 index 00000000..99a7db2b --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/scsi.c/.gitignore @@ -0,0 +1 @@ +scsi_ut diff --git a/src/spdk/test/unit/lib/scsi/scsi.c/Makefile b/src/spdk/test/unit/lib/scsi/scsi.c/Makefile new file mode 100644 index 00000000..86893653 --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/scsi.c/Makefile @@ -0,0 +1,41 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +SPDK_LIB_LIST = trace +TEST_FILE = scsi_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/scsi/scsi.c/scsi_ut.c b/src/spdk/test/unit/lib/scsi/scsi.c/scsi_ut.c new file mode 100644 index 00000000..5a1a31f6 --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/scsi.c/scsi_ut.c @@ -0,0 +1,80 @@ +/*- + * 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/scsi.h" + +#include "spdk_cunit.h" + +#include "scsi/scsi.c" + +static void +scsi_init(void) +{ + int rc; + + rc = spdk_scsi_init(); + CU_ASSERT_EQUAL(rc, 0); +} + +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("scsi_suite", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "scsi init", \ + scsi_init) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/scsi/scsi_bdev.c/.gitignore b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/.gitignore new file mode 100644 index 00000000..8f1ecc12 --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/.gitignore @@ -0,0 +1 @@ +scsi_bdev_ut diff --git a/src/spdk/test/unit/lib/scsi/scsi_bdev.c/Makefile b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/Makefile new file mode 100644 index 00000000..abb1de50 --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = scsi_bdev_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/scsi/scsi_bdev.c/scsi_bdev_ut.c b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/scsi_bdev_ut.c new file mode 100644 index 00000000..4deb2cec --- /dev/null +++ b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/scsi_bdev_ut.c @@ -0,0 +1,988 @@ +/*- + * 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 "scsi/task.c" +#include "scsi/scsi_bdev.c" + +#include "spdk_cunit.h" + +SPDK_LOG_REGISTER_COMPONENT("scsi", SPDK_LOG_SCSI) + +struct spdk_scsi_globals g_spdk_scsi; + +static uint64_t g_test_bdev_num_blocks; + +TAILQ_HEAD(, spdk_bdev_io) g_bdev_io_queue; +int g_scsi_cb_called = 0; + +TAILQ_HEAD(, spdk_bdev_io_wait_entry) g_io_wait_queue; +bool g_bdev_io_pool_full = false; + +void * +spdk_dma_malloc(size_t size, size_t align, uint64_t *phys_addr) +{ + void *buf = malloc(size); + if (phys_addr) { + *phys_addr = (uint64_t)buf; + } + + return buf; +} + +void * +spdk_dma_zmalloc(size_t size, size_t align, uint64_t *phys_addr) +{ + void *buf = calloc(size, 1); + if (phys_addr) { + *phys_addr = (uint64_t)buf; + } + + return buf; +} + +void +spdk_dma_free(void *buf) +{ + free(buf); +} + +bool +spdk_bdev_io_type_supported(struct spdk_bdev *bdev, enum spdk_bdev_io_type io_type) +{ + abort(); + return false; +} + +void +spdk_bdev_free_io(struct spdk_bdev_io *bdev_io) +{ + CU_ASSERT(0); +} + +const char * +spdk_bdev_get_name(const struct spdk_bdev *bdev) +{ + return "test"; +} + +uint32_t +spdk_bdev_get_block_size(const struct spdk_bdev *bdev) +{ + return 512; +} + +uint64_t +spdk_bdev_get_num_blocks(const struct spdk_bdev *bdev) +{ + return g_test_bdev_num_blocks; +} + +const char * +spdk_bdev_get_product_name(const struct spdk_bdev *bdev) +{ + return "test product"; +} + +bool +spdk_bdev_has_write_cache(const struct spdk_bdev *bdev) +{ + return false; +} + +void +spdk_scsi_lun_complete_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task) +{ + g_scsi_cb_called++; +} + +void +spdk_scsi_lun_complete_mgmt_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task) +{ +} + +static void +ut_put_task(struct spdk_scsi_task *task) +{ + if (task->alloc_len) { + free(task->iov.iov_base); + } + + task->iov.iov_base = NULL; + task->iov.iov_len = 0; + task->alloc_len = 0; + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue)); +} + + +static void +ut_init_task(struct spdk_scsi_task *task) +{ + memset(task, 0xFF, sizeof(*task)); + task->iov.iov_base = NULL; + task->iovs = &task->iov; + task->iovcnt = 1; + task->alloc_len = 0; + task->dxfer_dir = SPDK_SCSI_DIR_NONE; +} + +void +spdk_bdev_io_get_scsi_status(const struct spdk_bdev_io *bdev_io, + int *sc, int *sk, int *asc, int *ascq) +{ + switch (bdev_io->internal.status) { + case SPDK_BDEV_IO_STATUS_SUCCESS: + *sc = SPDK_SCSI_STATUS_GOOD; + *sk = SPDK_SCSI_SENSE_NO_SENSE; + *asc = SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE; + *ascq = SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE; + break; + case SPDK_BDEV_IO_STATUS_SCSI_ERROR: + *sc = bdev_io->internal.error.scsi.sc; + *sk = bdev_io->internal.error.scsi.sk; + *asc = bdev_io->internal.error.scsi.asc; + *ascq = bdev_io->internal.error.scsi.ascq; + break; + default: + *sc = SPDK_SCSI_STATUS_CHECK_CONDITION; + *sk = SPDK_SCSI_SENSE_ABORTED_COMMAND; + *asc = SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE; + *ascq = SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE; + break; + } +} + +void +spdk_bdev_io_get_iovec(struct spdk_bdev_io *bdev_io, struct iovec **iovp, int *iovcntp) +{ + *iovp = NULL; + *iovcntp = 0; +} + +static void +ut_bdev_io_flush(void) +{ + struct spdk_bdev_io *bdev_io; + struct spdk_bdev_io_wait_entry *entry; + + while (!TAILQ_EMPTY(&g_bdev_io_queue) || !TAILQ_EMPTY(&g_io_wait_queue)) { + while (!TAILQ_EMPTY(&g_bdev_io_queue)) { + bdev_io = TAILQ_FIRST(&g_bdev_io_queue); + TAILQ_REMOVE(&g_bdev_io_queue, bdev_io, internal.link); + bdev_io->internal.cb(bdev_io, true, bdev_io->internal.caller_ctx); + free(bdev_io); + } + + while (!TAILQ_EMPTY(&g_io_wait_queue)) { + entry = TAILQ_FIRST(&g_io_wait_queue); + TAILQ_REMOVE(&g_io_wait_queue, entry, link); + entry->cb_fn(entry->cb_arg); + } + } +} + +static int +_spdk_bdev_io_op(spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + struct spdk_bdev_io *bdev_io; + + if (g_bdev_io_pool_full) { + g_bdev_io_pool_full = false; + return -ENOMEM; + } + + bdev_io = calloc(1, sizeof(*bdev_io)); + SPDK_CU_ASSERT_FATAL(bdev_io != NULL); + bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + bdev_io->internal.cb = cb; + bdev_io->internal.caller_ctx = cb_arg; + + TAILQ_INSERT_TAIL(&g_bdev_io_queue, bdev_io, internal.link); + + return 0; +} + +int +spdk_bdev_readv(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, uint64_t offset, uint64_t nbytes, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return _spdk_bdev_io_op(cb, cb_arg); +} + +int +spdk_bdev_writev(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, + uint64_t offset, uint64_t len, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return _spdk_bdev_io_op(cb, cb_arg); +} + +int +spdk_bdev_unmap_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return _spdk_bdev_io_op(cb, cb_arg); +} + +int +spdk_bdev_reset(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return _spdk_bdev_io_op(cb, cb_arg); +} + +int +spdk_bdev_flush_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return _spdk_bdev_io_op(cb, cb_arg); +} + +int +spdk_bdev_queue_io_wait(struct spdk_bdev *bdev, struct spdk_io_channel *ch, + struct spdk_bdev_io_wait_entry *entry) +{ + TAILQ_INSERT_TAIL(&g_io_wait_queue, entry, link); + return 0; +} + +/* + * This test specifically tests a mode select 6 command from the + * Windows SCSI compliance test that caused SPDK to crash. + */ +static void +mode_select_6_test(void) +{ + struct spdk_bdev bdev; + struct spdk_scsi_task task; + struct spdk_scsi_lun lun; + struct spdk_scsi_dev dev; + char cdb[16]; + char data[24]; + int rc; + + ut_init_task(&task); + + cdb[0] = 0x15; + cdb[1] = 0x11; + cdb[2] = 0x00; + cdb[3] = 0x00; + cdb[4] = 0x18; + cdb[5] = 0x00; + task.cdb = cdb; + + snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test"); + lun.bdev = &bdev; + lun.dev = &dev; + task.lun = &lun; + + memset(data, 0, sizeof(data)); + data[4] = 0x08; + data[5] = 0x02; + spdk_scsi_task_set_data(&task, data, sizeof(data)); + + rc = spdk_bdev_scsi_execute(&task); + + CU_ASSERT_EQUAL(rc, 0); + + ut_put_task(&task); +} + +/* + * This test specifically tests a mode select 6 command which + * contains no mode pages. + */ +static void +mode_select_6_test2(void) +{ + struct spdk_bdev bdev; + struct spdk_scsi_task task; + struct spdk_scsi_lun lun; + struct spdk_scsi_dev dev; + char cdb[16]; + int rc; + + ut_init_task(&task); + + cdb[0] = 0x15; + cdb[1] = 0x00; + cdb[2] = 0x00; + cdb[3] = 0x00; + cdb[4] = 0x00; + cdb[5] = 0x00; + task.cdb = cdb; + + snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test"); + lun.bdev = &bdev; + lun.dev = &dev; + task.lun = &lun; + + rc = spdk_bdev_scsi_execute(&task); + + CU_ASSERT_EQUAL(rc, 0); + + ut_put_task(&task); +} + +/* + * This test specifically tests a mode sense 6 command which + * return all subpage 00h mode pages. + */ +static void +mode_sense_6_test(void) +{ + struct spdk_bdev bdev; + struct spdk_scsi_task task; + struct spdk_scsi_lun lun; + struct spdk_scsi_dev dev; + char cdb[12]; + unsigned char *data; + int rc; + unsigned char mode_data_len = 0; + unsigned char medium_type = 0; + unsigned char dev_specific_param = 0; + unsigned char blk_descriptor_len = 0; + + memset(&bdev, 0, sizeof(struct spdk_bdev)); + ut_init_task(&task); + memset(cdb, 0, sizeof(cdb)); + + cdb[0] = 0x1A; + cdb[2] = 0x3F; + cdb[4] = 0xFF; + task.cdb = cdb; + + snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test"); + lun.bdev = &bdev; + lun.dev = &dev; + task.lun = &lun; + + rc = spdk_bdev_scsi_execute(&task); + SPDK_CU_ASSERT_FATAL(rc == 0); + + data = task.iovs[0].iov_base; + mode_data_len = data[0]; + medium_type = data[1]; + dev_specific_param = data[2]; + blk_descriptor_len = data[3]; + + CU_ASSERT(mode_data_len >= 11); + CU_ASSERT_EQUAL(medium_type, 0); + CU_ASSERT_EQUAL(dev_specific_param, 0); + CU_ASSERT_EQUAL(blk_descriptor_len, 8); + + ut_put_task(&task); +} + +/* + * This test specifically tests a mode sense 10 command which + * return all subpage 00h mode pages. + */ +static void +mode_sense_10_test(void) +{ + struct spdk_bdev bdev; + struct spdk_scsi_task task; + struct spdk_scsi_lun lun; + struct spdk_scsi_dev dev; + char cdb[12]; + unsigned char *data; + int rc; + unsigned short mode_data_len = 0; + unsigned char medium_type = 0; + unsigned char dev_specific_param = 0; + unsigned short blk_descriptor_len = 0; + + memset(&bdev, 0, sizeof(struct spdk_bdev)); + ut_init_task(&task); + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x5A; + cdb[2] = 0x3F; + cdb[8] = 0xFF; + task.cdb = cdb; + + snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test"); + lun.bdev = &bdev; + lun.dev = &dev; + task.lun = &lun; + + rc = spdk_bdev_scsi_execute(&task); + SPDK_CU_ASSERT_FATAL(rc == 0); + + data = task.iovs[0].iov_base; + mode_data_len = ((data[0] << 8) + data[1]); + medium_type = data[2]; + dev_specific_param = data[3]; + blk_descriptor_len = ((data[6] << 8) + data[7]); + + CU_ASSERT(mode_data_len >= 14); + CU_ASSERT_EQUAL(medium_type, 0); + CU_ASSERT_EQUAL(dev_specific_param, 0); + CU_ASSERT_EQUAL(blk_descriptor_len, 8); + + ut_put_task(&task); +} + +/* + * This test specifically tests a scsi inquiry command from the + * Windows SCSI compliance test that failed to return the + * expected SCSI error sense code. + */ +static void +inquiry_evpd_test(void) +{ + struct spdk_bdev bdev; + struct spdk_scsi_task task; + struct spdk_scsi_lun lun; + struct spdk_scsi_dev dev; + char cdb[6]; + int rc; + + ut_init_task(&task); + + cdb[0] = 0x12; + cdb[1] = 0x00; // EVPD = 0 + cdb[2] = 0xff; // PageCode non-zero + cdb[3] = 0x00; + cdb[4] = 0xff; + cdb[5] = 0x00; + task.cdb = cdb; + + snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test"); + lun.bdev = &bdev; + lun.dev = &dev; + task.lun = &lun; + + rc = spdk_bdev_scsi_execute(&task); + SPDK_CU_ASSERT_FATAL(rc == 0); + + CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT_EQUAL(task.sense_data[2] & 0xf, SPDK_SCSI_SENSE_ILLEGAL_REQUEST); + CU_ASSERT_EQUAL(task.sense_data[12], 0x24); + CU_ASSERT_EQUAL(task.sense_data[13], 0x0); + + ut_put_task(&task); +} + +/* + * This test is to verify specific return data for a standard scsi inquiry + * command: Version + */ +static void +inquiry_standard_test(void) +{ + struct spdk_bdev bdev = { .blocklen = 512 }; + struct spdk_scsi_task task; + struct spdk_scsi_lun lun; + struct spdk_scsi_dev dev; + char cdb[6]; + char *data; + struct spdk_scsi_cdb_inquiry_data *inq_data; + int rc; + + ut_init_task(&task); + + cdb[0] = 0x12; + cdb[1] = 0x00; // EVPD = 0 + cdb[2] = 0x00; // PageCode zero - requesting standard inquiry + cdb[3] = 0x00; + cdb[4] = 0xff; // Indicate data size used by conformance test + cdb[5] = 0x00; + task.cdb = cdb; + + snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test"); + lun.bdev = &bdev; + lun.dev = &dev; + task.lun = &lun; + + rc = spdk_bdev_scsi_execute(&task); + + data = task.iovs[0].iov_base; + inq_data = (struct spdk_scsi_cdb_inquiry_data *)&data[0]; + + CU_ASSERT_EQUAL(inq_data->version, SPDK_SPC_VERSION_SPC3); + CU_ASSERT_EQUAL(rc, 0); + + ut_put_task(&task); +} + +static void +_inquiry_overflow_test(uint8_t alloc_len) +{ + struct spdk_bdev bdev = { .blocklen = 512 }; + struct spdk_scsi_task task; + struct spdk_scsi_lun lun; + struct spdk_scsi_dev dev; + uint8_t cdb[6]; + int rc; + /* expects a 4K internal data buffer */ + char data[4096], data_compare[4096]; + + ut_init_task(&task); + + cdb[0] = 0x12; + cdb[1] = 0x00; // EVPD = 0 + cdb[2] = 0x00; // PageCode zero - requesting standard inquiry + cdb[3] = 0x00; + cdb[4] = alloc_len; // Indicate data size used by conformance test + cdb[5] = 0x00; + task.cdb = cdb; + + snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test"); + lun.bdev = &bdev; + lun.dev = &dev; + task.lun = &lun; + + memset(data, 0, sizeof(data)); + memset(data_compare, 0, sizeof(data_compare)); + + spdk_scsi_task_set_data(&task, data, sizeof(data)); + + rc = spdk_bdev_scsi_execute(&task); + SPDK_CU_ASSERT_FATAL(rc == 0); + + CU_ASSERT_EQUAL(memcmp(data + alloc_len, data_compare + alloc_len, sizeof(data) - alloc_len), 0); + CU_ASSERT(task.data_transferred <= alloc_len); + + ut_put_task(&task); +} + +static void +inquiry_overflow_test(void) +{ + int i; + + for (i = 0; i < 256; i++) { + _inquiry_overflow_test(i); + } +} + +static void +scsi_name_padding_test(void) +{ + char name[SPDK_SCSI_DEV_MAX_NAME + 1]; + char buf[SPDK_SCSI_DEV_MAX_NAME + 1]; + int written, i; + + /* case 1 */ + memset(name, '\0', sizeof(name)); + memset(name, 'x', 251); + written = spdk_bdev_scsi_pad_scsi_name(buf, name); + + CU_ASSERT(written == 252); + CU_ASSERT(buf[250] == 'x'); + CU_ASSERT(buf[251] == '\0'); + + /* case 2: */ + memset(name, '\0', sizeof(name)); + memset(name, 'x', 252); + written = spdk_bdev_scsi_pad_scsi_name(buf, name); + + CU_ASSERT(written == 256); + CU_ASSERT(buf[251] == 'x'); + for (i = 252; i < 256; i++) { + CU_ASSERT(buf[i] == '\0'); + } + + /* case 3 */ + memset(name, '\0', sizeof(name)); + memset(name, 'x', 255); + written = spdk_bdev_scsi_pad_scsi_name(buf, name); + + CU_ASSERT(written == 256); + CU_ASSERT(buf[254] == 'x'); + CU_ASSERT(buf[255] == '\0'); +} + +/* + * This test is to verify specific error translation from bdev to scsi. + */ +static void +task_complete_test(void) +{ + struct spdk_scsi_task task; + struct spdk_bdev_io bdev_io = {}; + struct spdk_scsi_lun lun; + + ut_init_task(&task); + + TAILQ_INIT(&lun.tasks); + TAILQ_INSERT_TAIL(&lun.tasks, &task, scsi_link); + task.lun = &lun; + + bdev_io.internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + spdk_bdev_scsi_task_complete_cmd(&bdev_io, bdev_io.internal.status, &task); + CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_GOOD); + CU_ASSERT(g_scsi_cb_called == 1); + g_scsi_cb_called = 0; + + bdev_io.internal.status = SPDK_BDEV_IO_STATUS_SCSI_ERROR; + bdev_io.internal.error.scsi.sc = SPDK_SCSI_STATUS_CHECK_CONDITION; + bdev_io.internal.error.scsi.sk = SPDK_SCSI_SENSE_HARDWARE_ERROR; + bdev_io.internal.error.scsi.asc = SPDK_SCSI_ASC_WARNING; + bdev_io.internal.error.scsi.ascq = SPDK_SCSI_ASCQ_POWER_LOSS_EXPECTED; + spdk_bdev_scsi_task_complete_cmd(&bdev_io, bdev_io.internal.status, &task); + CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT_EQUAL(task.sense_data[2] & 0xf, SPDK_SCSI_SENSE_HARDWARE_ERROR); + CU_ASSERT_EQUAL(task.sense_data[12], SPDK_SCSI_ASC_WARNING); + CU_ASSERT_EQUAL(task.sense_data[13], SPDK_SCSI_ASCQ_POWER_LOSS_EXPECTED); + CU_ASSERT(g_scsi_cb_called == 1); + g_scsi_cb_called = 0; + + bdev_io.internal.status = SPDK_BDEV_IO_STATUS_FAILED; + spdk_bdev_scsi_task_complete_cmd(&bdev_io, bdev_io.internal.status, &task); + CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT_EQUAL(task.sense_data[2] & 0xf, SPDK_SCSI_SENSE_ABORTED_COMMAND); + CU_ASSERT_EQUAL(task.sense_data[12], SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE); + CU_ASSERT_EQUAL(task.sense_data[13], SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + CU_ASSERT(g_scsi_cb_called == 1); + g_scsi_cb_called = 0; + + ut_put_task(&task); +} + +static void +lba_range_test(void) +{ + struct spdk_bdev bdev; + struct spdk_scsi_lun lun; + struct spdk_scsi_task task; + uint8_t cdb[16]; + int rc; + + lun.bdev = &bdev; + + ut_init_task(&task); + task.lun = &lun; + task.lun->bdev_desc = NULL; + task.lun->io_channel = NULL; + task.cdb = cdb; + + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x88; /* READ (16) */ + + /* Test block device size of 4 blocks */ + g_test_bdev_num_blocks = 4; + + /* LBA = 0, length = 1 (in range) */ + to_be64(&cdb[2], 0); /* LBA */ + to_be32(&cdb[10], 1); /* transfer length */ + task.transfer_len = 1 * 512; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING); + CU_ASSERT(task.status == 0xFF); + SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_bdev_io_queue)); + ut_bdev_io_flush(); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD); + CU_ASSERT(g_scsi_cb_called == 1); + g_scsi_cb_called = 0; + + /* LBA = 4, length = 1 (LBA out of range) */ + to_be64(&cdb[2], 4); /* LBA */ + to_be32(&cdb[10], 1); /* transfer length */ + task.transfer_len = 1 * 512; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_COMPLETE); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT(task.sense_data[12] == SPDK_SCSI_ASC_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE); + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue)); + + /* LBA = 0, length = 4 (in range, max valid size) */ + to_be64(&cdb[2], 0); /* LBA */ + to_be32(&cdb[10], 4); /* transfer length */ + task.transfer_len = 4 * 512; + task.status = 0xFF; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING); + CU_ASSERT(task.status == 0xFF); + SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_bdev_io_queue)); + ut_bdev_io_flush(); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD); + CU_ASSERT(g_scsi_cb_called == 1); + g_scsi_cb_called = 0; + + /* LBA = 0, length = 5 (LBA in range, length beyond end of bdev) */ + to_be64(&cdb[2], 0); /* LBA */ + to_be32(&cdb[10], 5); /* transfer length */ + task.transfer_len = 5 * 512; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_COMPLETE); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT(task.sense_data[12] == SPDK_SCSI_ASC_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE); + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue)); + + ut_put_task(&task); +} + +static void +xfer_len_test(void) +{ + struct spdk_bdev bdev; + struct spdk_scsi_lun lun; + struct spdk_scsi_task task; + uint8_t cdb[16]; + int rc; + + lun.bdev = &bdev; + + ut_init_task(&task); + task.lun = &lun; + task.lun->bdev_desc = NULL; + task.lun->io_channel = NULL; + task.cdb = cdb; + + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x88; /* READ (16) */ + + /* Test block device size of 512 MiB */ + g_test_bdev_num_blocks = 512 * 1024 * 1024; + + /* 1 block */ + to_be64(&cdb[2], 0); /* LBA */ + to_be32(&cdb[10], 1); /* transfer length */ + task.transfer_len = 1 * 512; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING); + CU_ASSERT(task.status == 0xFF); + SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_bdev_io_queue)); + ut_bdev_io_flush(); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD); + CU_ASSERT(g_scsi_cb_called == 1); + g_scsi_cb_called = 0; + + /* max transfer length (as reported in block limits VPD page) */ + to_be64(&cdb[2], 0); /* LBA */ + to_be32(&cdb[10], SPDK_WORK_BLOCK_SIZE / 512); /* transfer length */ + task.transfer_len = SPDK_WORK_BLOCK_SIZE; + task.status = 0xFF; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING); + CU_ASSERT(task.status == 0xFF); + SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_bdev_io_queue)); + ut_bdev_io_flush(); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD); + CU_ASSERT(g_scsi_cb_called == 1); + g_scsi_cb_called = 0; + + /* max transfer length plus one block (invalid) */ + to_be64(&cdb[2], 0); /* LBA */ + to_be32(&cdb[10], SPDK_WORK_BLOCK_SIZE / 512 + 1); /* transfer length */ + task.transfer_len = SPDK_WORK_BLOCK_SIZE + 512; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_COMPLETE); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT((task.sense_data[2] & 0xf) == SPDK_SCSI_SENSE_ILLEGAL_REQUEST); + CU_ASSERT(task.sense_data[12] == SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB); + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue)); + + /* zero transfer length (valid) */ + to_be64(&cdb[2], 0); /* LBA */ + to_be32(&cdb[10], 0); /* transfer length */ + task.transfer_len = 0; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_COMPLETE); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD); + CU_ASSERT(task.data_transferred == 0); + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue)); + + /* zero transfer length past end of disk (invalid) */ + to_be64(&cdb[2], g_test_bdev_num_blocks); /* LBA */ + to_be32(&cdb[10], 0); /* transfer length */ + task.transfer_len = 0; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_COMPLETE); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_CHECK_CONDITION); + CU_ASSERT(task.sense_data[12] == SPDK_SCSI_ASC_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE); + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue)); + + ut_put_task(&task); +} + +static void +_xfer_test(bool bdev_io_pool_full) +{ + struct spdk_bdev bdev; + struct spdk_scsi_lun lun; + struct spdk_scsi_task task; + uint8_t cdb[16]; + char data[4096]; + int rc; + + lun.bdev = &bdev; + + /* Test block device size of 512 MiB */ + g_test_bdev_num_blocks = 512 * 1024 * 1024; + + /* Read 1 block */ + ut_init_task(&task); + task.lun = &lun; + task.lun->bdev_desc = NULL; + task.lun->io_channel = NULL; + task.cdb = cdb; + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x88; /* READ (16) */ + to_be64(&cdb[2], 0); /* LBA */ + to_be32(&cdb[10], 1); /* transfer length */ + task.transfer_len = 1 * 512; + g_bdev_io_pool_full = bdev_io_pool_full; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING); + CU_ASSERT(task.status == 0xFF); + + ut_bdev_io_flush(); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD); + CU_ASSERT(g_scsi_cb_called == 1); + g_scsi_cb_called = 0; + ut_put_task(&task); + + /* Write 1 block */ + ut_init_task(&task); + task.lun = &lun; + task.cdb = cdb; + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x8a; /* WRITE (16) */ + to_be64(&cdb[2], 0); /* LBA */ + to_be32(&cdb[10], 1); /* transfer length */ + task.transfer_len = 1 * 512; + g_bdev_io_pool_full = bdev_io_pool_full; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING); + CU_ASSERT(task.status == 0xFF); + + ut_bdev_io_flush(); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD); + CU_ASSERT(g_scsi_cb_called == 1); + g_scsi_cb_called = 0; + ut_put_task(&task); + + /* Unmap 5 blocks using 2 descriptors */ + ut_init_task(&task); + task.lun = &lun; + task.cdb = cdb; + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x42; /* UNMAP */ + to_be16(&data[7], 2); /* 2 parameters in list */ + memset(data, 0, sizeof(data)); + to_be16(&data[2], 32); /* 2 descriptors */ + to_be64(&data[8], 1); /* LBA 1 */ + to_be32(&data[16], 2); /* 2 blocks */ + to_be64(&data[24], 10); /* LBA 10 */ + to_be32(&data[32], 3); /* 3 blocks */ + spdk_scsi_task_set_data(&task, data, sizeof(data)); + task.status = SPDK_SCSI_STATUS_GOOD; + g_bdev_io_pool_full = bdev_io_pool_full; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD); + + ut_bdev_io_flush(); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD); + CU_ASSERT(g_scsi_cb_called == 1); + g_scsi_cb_called = 0; + ut_put_task(&task); + + /* Flush 1 block */ + ut_init_task(&task); + task.lun = &lun; + task.cdb = cdb; + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x91; /* SYNCHRONIZE CACHE (16) */ + to_be64(&cdb[2], 0); /* LBA */ + to_be32(&cdb[10], 1); /* 1 blocks */ + g_bdev_io_pool_full = bdev_io_pool_full; + rc = spdk_bdev_scsi_execute(&task); + CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING); + CU_ASSERT(task.status == 0xFF); + + ut_bdev_io_flush(); + CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD); + CU_ASSERT(g_scsi_cb_called == 1); + g_scsi_cb_called = 0; + ut_put_task(&task); +} + +static void +xfer_test(void) +{ + _xfer_test(false); + _xfer_test(true); +} + +int +main(int argc, char **argv) +{ + CU_pSuite suite = NULL; + unsigned int num_failures; + + TAILQ_INIT(&g_bdev_io_queue); + TAILQ_INIT(&g_io_wait_queue); + + if (CU_initialize_registry() != CUE_SUCCESS) { + return CU_get_error(); + } + + suite = CU_add_suite("translation_suite", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "mode select 6 test", mode_select_6_test) == NULL + || CU_add_test(suite, "mode select 6 test2", mode_select_6_test2) == NULL + || CU_add_test(suite, "mode sense 6 test", mode_sense_6_test) == NULL + || CU_add_test(suite, "mode sense 10 test", mode_sense_10_test) == NULL + || CU_add_test(suite, "inquiry evpd test", inquiry_evpd_test) == NULL + || CU_add_test(suite, "inquiry standard test", inquiry_standard_test) == NULL + || CU_add_test(suite, "inquiry overflow test", inquiry_overflow_test) == NULL + || CU_add_test(suite, "task complete test", task_complete_test) == NULL + || CU_add_test(suite, "LBA range test", lba_range_test) == NULL + || CU_add_test(suite, "transfer length test", xfer_len_test) == NULL + || CU_add_test(suite, "transfer test", xfer_test) == NULL + || CU_add_test(suite, "scsi name padding test", scsi_name_padding_test) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/sock/Makefile b/src/spdk/test/unit/lib/sock/Makefile new file mode 100644 index 00000000..5e16429d --- /dev/null +++ b/src/spdk/test/unit/lib/sock/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = sock.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/sock/sock.c/.gitignore b/src/spdk/test/unit/lib/sock/sock.c/.gitignore new file mode 100644 index 00000000..bd9bf833 --- /dev/null +++ b/src/spdk/test/unit/lib/sock/sock.c/.gitignore @@ -0,0 +1 @@ +sock_ut diff --git a/src/spdk/test/unit/lib/sock/sock.c/Makefile b/src/spdk/test/unit/lib/sock/sock.c/Makefile new file mode 100644 index 00000000..845c9ade --- /dev/null +++ b/src/spdk/test/unit/lib/sock/sock.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = sock_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/sock/sock.c/sock_ut.c b/src/spdk/test/unit/lib/sock/sock.c/sock_ut.c new file mode 100644 index 00000000..a0176f11 --- /dev/null +++ b/src/spdk/test/unit/lib/sock/sock.c/sock_ut.c @@ -0,0 +1,643 @@ +/*- + * 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/util.h" + +#include "spdk_cunit.h" + +#include "sock/sock.c" +#include "sock/posix/posix.c" + +#define UT_IP "test_ip" +#define UT_PORT 1234 + +bool g_read_data_called; +ssize_t g_bytes_read; +char g_buf[256]; +struct spdk_sock *g_server_sock_read; +int g_ut_accept_count; +struct spdk_ut_sock *g_ut_listen_sock; +struct spdk_ut_sock *g_ut_client_sock; + +struct spdk_ut_sock { + struct spdk_sock base; + struct spdk_ut_sock *peer; + size_t bytes_avail; + char buf[256]; +}; + +struct spdk_ut_sock_group_impl { + struct spdk_sock_group_impl base; + struct spdk_ut_sock *sock; +}; + +#define __ut_sock(sock) (struct spdk_ut_sock *)sock +#define __ut_group(group) (struct spdk_ut_sock_group_impl *)group + +static int +spdk_ut_sock_getaddr(struct spdk_sock *_sock, char *saddr, int slen, uint16_t *sport, + char *caddr, int clen, uint16_t *cport) +{ + return 0; +} + +static struct spdk_sock * +spdk_ut_sock_listen(const char *ip, int port) +{ + struct spdk_ut_sock *sock; + + if (strcmp(ip, UT_IP) || port != UT_PORT) { + return NULL; + } + + CU_ASSERT(g_ut_listen_sock == NULL); + + sock = calloc(1, sizeof(*sock)); + SPDK_CU_ASSERT_FATAL(sock != NULL); + g_ut_listen_sock = sock; + + return &sock->base; +} + +static struct spdk_sock * +spdk_ut_sock_connect(const char *ip, int port) +{ + struct spdk_ut_sock *sock; + + if (strcmp(ip, UT_IP) || port != UT_PORT) { + return NULL; + } + + sock = calloc(1, sizeof(*sock)); + SPDK_CU_ASSERT_FATAL(sock != NULL); + g_ut_accept_count++; + CU_ASSERT(g_ut_client_sock == NULL); + g_ut_client_sock = sock; + + return &sock->base; +} + +static struct spdk_sock * +spdk_ut_sock_accept(struct spdk_sock *_sock) +{ + struct spdk_ut_sock *sock = __ut_sock(_sock); + struct spdk_ut_sock *new_sock; + + CU_ASSERT(sock == g_ut_listen_sock); + + if (g_ut_accept_count == 0) { + errno = EAGAIN; + return NULL; + } + + g_ut_accept_count--; + new_sock = calloc(1, sizeof(*sock)); + if (new_sock == NULL) { + SPDK_ERRLOG("sock allocation failed\n"); + return NULL; + } + + SPDK_CU_ASSERT_FATAL(g_ut_client_sock != NULL); + g_ut_client_sock->peer = new_sock; + + return &new_sock->base; +} + +static int +spdk_ut_sock_close(struct spdk_sock *_sock) +{ + struct spdk_ut_sock *sock = __ut_sock(_sock); + + if (sock == g_ut_listen_sock) { + g_ut_listen_sock = NULL; + } + if (sock == g_ut_client_sock) { + g_ut_client_sock = NULL; + } + free(_sock); + + return 0; +} + +static ssize_t +spdk_ut_sock_recv(struct spdk_sock *_sock, void *buf, size_t len) +{ + struct spdk_ut_sock *sock = __ut_sock(_sock); + char tmp[256]; + + len = spdk_min(len, sock->bytes_avail); + + if (len == 0) { + errno = EAGAIN; + return -1; + } + + memcpy(buf, sock->buf, len); + memcpy(tmp, &sock->buf[len], sock->bytes_avail - len); + memcpy(sock->buf, tmp, sock->bytes_avail - len); + sock->bytes_avail -= len; + + return len; +} + +static ssize_t +spdk_ut_sock_writev(struct spdk_sock *_sock, struct iovec *iov, int iovcnt) +{ + struct spdk_ut_sock *sock = __ut_sock(_sock); + struct spdk_ut_sock *peer; + + SPDK_CU_ASSERT_FATAL(sock->peer != NULL); + peer = sock->peer; + + /* Test implementation only supports single iov for now. */ + CU_ASSERT(iovcnt == 1); + + memcpy(&peer->buf[peer->bytes_avail], iov[0].iov_base, iov[0].iov_len); + peer->bytes_avail += iov[0].iov_len; + + return iov[0].iov_len; +} + +static int +spdk_ut_sock_set_recvlowat(struct spdk_sock *_sock, int nbytes) +{ + return 0; +} + +static int +spdk_ut_sock_set_recvbuf(struct spdk_sock *_sock, int sz) +{ + return 0; +} + +static int +spdk_ut_sock_set_sendbuf(struct spdk_sock *_sock, int sz) +{ + return 0; +} + +static bool +spdk_ut_sock_is_ipv6(struct spdk_sock *_sock) +{ + return false; +} + +static bool +spdk_ut_sock_is_ipv4(struct spdk_sock *_sock) +{ + return true; +} + +static struct spdk_sock_group_impl * +spdk_ut_sock_group_impl_create(void) +{ + struct spdk_ut_sock_group_impl *group_impl; + + group_impl = calloc(1, sizeof(*group_impl)); + SPDK_CU_ASSERT_FATAL(group_impl != NULL); + + return &group_impl->base; +} + +static int +spdk_ut_sock_group_impl_add_sock(struct spdk_sock_group_impl *_group, struct spdk_sock *_sock) +{ + struct spdk_ut_sock_group_impl *group = __ut_group(_group); + struct spdk_ut_sock *sock = __ut_sock(_sock); + + group->sock = sock; + + return 0; +} + +static int +spdk_ut_sock_group_impl_remove_sock(struct spdk_sock_group_impl *_group, struct spdk_sock *_sock) +{ + struct spdk_ut_sock_group_impl *group = __ut_group(_group); + struct spdk_ut_sock *sock = __ut_sock(_sock); + + CU_ASSERT(group->sock == sock); + group->sock = NULL; + + return 0; +} + +static int +spdk_ut_sock_group_impl_poll(struct spdk_sock_group_impl *_group, int max_events, + struct spdk_sock **socks) +{ + struct spdk_ut_sock_group_impl *group = __ut_group(_group); + + if (group->sock != NULL && group->sock->bytes_avail > 0) { + socks[0] = &group->sock->base; + return 1; + } + + return 0; +} + +static int +spdk_ut_sock_group_impl_close(struct spdk_sock_group_impl *_group) +{ + struct spdk_ut_sock_group_impl *group = __ut_group(_group); + + CU_ASSERT(group->sock == NULL); + + return 0; +} + +static struct spdk_net_impl g_ut_net_impl = { + .name = "ut", + .getaddr = spdk_ut_sock_getaddr, + .connect = spdk_ut_sock_connect, + .listen = spdk_ut_sock_listen, + .accept = spdk_ut_sock_accept, + .close = spdk_ut_sock_close, + .recv = spdk_ut_sock_recv, + .writev = spdk_ut_sock_writev, + .set_recvlowat = spdk_ut_sock_set_recvlowat, + .set_recvbuf = spdk_ut_sock_set_recvbuf, + .set_sendbuf = spdk_ut_sock_set_sendbuf, + .is_ipv6 = spdk_ut_sock_is_ipv6, + .is_ipv4 = spdk_ut_sock_is_ipv4, + .group_impl_create = spdk_ut_sock_group_impl_create, + .group_impl_add_sock = spdk_ut_sock_group_impl_add_sock, + .group_impl_remove_sock = spdk_ut_sock_group_impl_remove_sock, + .group_impl_poll = spdk_ut_sock_group_impl_poll, + .group_impl_close = spdk_ut_sock_group_impl_close, +}; + +SPDK_NET_IMPL_REGISTER(ut, &g_ut_net_impl); + +static void +_sock(const char *ip, int port) +{ + struct spdk_sock *listen_sock; + struct spdk_sock *server_sock; + struct spdk_sock *client_sock; + char *test_string = "abcdef"; + char buffer[64]; + ssize_t bytes_read, bytes_written; + struct iovec iov; + int rc; + + listen_sock = spdk_sock_listen(ip, port); + SPDK_CU_ASSERT_FATAL(listen_sock != NULL); + + server_sock = spdk_sock_accept(listen_sock); + CU_ASSERT(server_sock == NULL); + CU_ASSERT(errno == EAGAIN || errno == EWOULDBLOCK); + + client_sock = spdk_sock_connect(ip, port); + SPDK_CU_ASSERT_FATAL(client_sock != NULL); + + /* + * Delay a bit here before checking if server socket is + * ready. + */ + usleep(1000); + + server_sock = spdk_sock_accept(listen_sock); + SPDK_CU_ASSERT_FATAL(server_sock != NULL); + + iov.iov_base = test_string; + iov.iov_len = 7; + bytes_written = spdk_sock_writev(client_sock, &iov, 1); + CU_ASSERT(bytes_written == 7); + + usleep(1000); + + bytes_read = spdk_sock_recv(server_sock, buffer, 2); + CU_ASSERT(bytes_read == 2); + + usleep(1000); + + bytes_read += spdk_sock_recv(server_sock, buffer + 2, 5); + CU_ASSERT(bytes_read == 7); + + CU_ASSERT(strncmp(test_string, buffer, 7) == 0); + + rc = spdk_sock_close(&client_sock); + CU_ASSERT(client_sock == NULL); + CU_ASSERT(rc == 0); + + rc = spdk_sock_close(&server_sock); + CU_ASSERT(server_sock == NULL); + CU_ASSERT(rc == 0); + + rc = spdk_sock_close(&listen_sock); + CU_ASSERT(listen_sock == NULL); + CU_ASSERT(rc == 0); +} + +static void +posix_sock(void) +{ + _sock("127.0.0.1", 3260); +} + +static void +ut_sock(void) +{ + _sock(UT_IP, UT_PORT); +} + +static void +read_data(void *cb_arg, struct spdk_sock_group *group, struct spdk_sock *sock) +{ + struct spdk_sock *server_sock = cb_arg; + + CU_ASSERT(server_sock == sock); + + g_read_data_called = true; + g_bytes_read += spdk_sock_recv(server_sock, g_buf + g_bytes_read, sizeof(g_buf) - g_bytes_read); +} + +static void +_sock_group(const char *ip, int port) +{ + struct spdk_sock_group *group; + struct spdk_sock *listen_sock; + struct spdk_sock *server_sock; + struct spdk_sock *client_sock; + char *test_string = "abcdef"; + ssize_t bytes_written; + struct iovec iov; + int rc; + + listen_sock = spdk_sock_listen(ip, port); + SPDK_CU_ASSERT_FATAL(listen_sock != NULL); + + server_sock = spdk_sock_accept(listen_sock); + CU_ASSERT(server_sock == NULL); + CU_ASSERT(errno == EAGAIN || errno == EWOULDBLOCK); + + client_sock = spdk_sock_connect(ip, port); + SPDK_CU_ASSERT_FATAL(client_sock != NULL); + + usleep(1000); + + server_sock = spdk_sock_accept(listen_sock); + SPDK_CU_ASSERT_FATAL(server_sock != NULL); + + group = spdk_sock_group_create(); + SPDK_CU_ASSERT_FATAL(group != NULL); + + /* pass null cb_fn */ + rc = spdk_sock_group_add_sock(group, server_sock, NULL, NULL); + CU_ASSERT(rc == -1); + CU_ASSERT(errno == EINVAL); + + rc = spdk_sock_group_add_sock(group, server_sock, read_data, server_sock); + CU_ASSERT(rc == 0); + + /* try adding sock a second time */ + rc = spdk_sock_group_add_sock(group, server_sock, read_data, server_sock); + CU_ASSERT(rc == -1); + CU_ASSERT(errno == EBUSY); + + g_read_data_called = false; + g_bytes_read = 0; + rc = spdk_sock_group_poll(group); + + CU_ASSERT(rc == 0); + CU_ASSERT(g_read_data_called == false); + + iov.iov_base = test_string; + iov.iov_len = 7; + bytes_written = spdk_sock_writev(client_sock, &iov, 1); + CU_ASSERT(bytes_written == 7); + + usleep(1000); + + g_read_data_called = false; + g_bytes_read = 0; + rc = spdk_sock_group_poll(group); + + CU_ASSERT(rc == 0); + CU_ASSERT(g_read_data_called == true); + CU_ASSERT(g_bytes_read == 7); + + CU_ASSERT(strncmp(test_string, g_buf, 7) == 0); + + rc = spdk_sock_close(&client_sock); + CU_ASSERT(client_sock == NULL); + CU_ASSERT(rc == 0); + + /* Try to close sock_group while it still has sockets. */ + rc = spdk_sock_group_close(&group); + CU_ASSERT(rc == -1); + CU_ASSERT(errno == EBUSY); + + /* Try to close sock while it is still part of a sock_group. */ + rc = spdk_sock_close(&server_sock); + CU_ASSERT(rc == -1); + CU_ASSERT(errno == EBUSY); + + rc = spdk_sock_group_remove_sock(group, server_sock); + CU_ASSERT(rc == 0); + + rc = spdk_sock_group_close(&group); + CU_ASSERT(group == NULL); + CU_ASSERT(rc == 0); + + rc = spdk_sock_close(&server_sock); + CU_ASSERT(server_sock == NULL); + CU_ASSERT(rc == 0); + + rc = spdk_sock_close(&listen_sock); + CU_ASSERT(listen_sock == NULL); + CU_ASSERT(rc == 0); +} + +static void +posix_sock_group(void) +{ + _sock_group("127.0.0.1", 3260); +} + +static void +ut_sock_group(void) +{ + _sock_group(UT_IP, UT_PORT); +} + +static void +read_data_fairness(void *cb_arg, struct spdk_sock_group *group, struct spdk_sock *sock) +{ + struct spdk_sock *server_sock = cb_arg; + ssize_t bytes_read; + char buf[1]; + + CU_ASSERT(g_server_sock_read == NULL); + CU_ASSERT(server_sock == sock); + + g_server_sock_read = server_sock; + bytes_read = spdk_sock_recv(server_sock, buf, 1); + CU_ASSERT(bytes_read == 1); +} + +static void +posix_sock_group_fairness(void) +{ + struct spdk_sock_group *group; + struct spdk_sock *listen_sock; + struct spdk_sock *server_sock[3]; + struct spdk_sock *client_sock[3]; + char test_char = 'a'; + ssize_t bytes_written; + struct iovec iov; + int i, rc; + + listen_sock = spdk_sock_listen("127.0.0.1", 3260); + SPDK_CU_ASSERT_FATAL(listen_sock != NULL); + + group = spdk_sock_group_create(); + SPDK_CU_ASSERT_FATAL(group != NULL); + + for (i = 0; i < 3; i++) { + client_sock[i] = spdk_sock_connect("127.0.0.1", 3260); + SPDK_CU_ASSERT_FATAL(client_sock[i] != NULL); + + usleep(1000); + + server_sock[i] = spdk_sock_accept(listen_sock); + SPDK_CU_ASSERT_FATAL(server_sock[i] != NULL); + + rc = spdk_sock_group_add_sock(group, server_sock[i], + read_data_fairness, server_sock[i]); + CU_ASSERT(rc == 0); + } + + iov.iov_base = &test_char; + iov.iov_len = 1; + + for (i = 0; i < 3; i++) { + bytes_written = spdk_sock_writev(client_sock[i], &iov, 1); + CU_ASSERT(bytes_written == 1); + } + + usleep(1000); + + /* + * Poll for just one event - this should be server sock 0, since that + * is the peer of the first client sock that we wrote to. + */ + g_server_sock_read = NULL; + rc = spdk_sock_group_poll_count(group, 1); + CU_ASSERT(rc == 0); + CU_ASSERT(g_server_sock_read == server_sock[0]); + + /* + * Now write another byte to client sock 0. We want to ensure that + * the sock group does not unfairly process the event for this sock + * before the socks that were written to earlier. + */ + bytes_written = spdk_sock_writev(client_sock[0], &iov, 1); + CU_ASSERT(bytes_written == 1); + + usleep(1000); + + g_server_sock_read = NULL; + rc = spdk_sock_group_poll_count(group, 1); + CU_ASSERT(rc == 0); + CU_ASSERT(g_server_sock_read == server_sock[1]); + + g_server_sock_read = NULL; + rc = spdk_sock_group_poll_count(group, 1); + CU_ASSERT(rc == 0); + CU_ASSERT(g_server_sock_read == server_sock[2]); + + g_server_sock_read = NULL; + rc = spdk_sock_group_poll_count(group, 1); + CU_ASSERT(rc == 0); + CU_ASSERT(g_server_sock_read == server_sock[0]); + + for (i = 0; i < 3; i++) { + rc = spdk_sock_group_remove_sock(group, server_sock[i]); + CU_ASSERT(rc == 0); + + rc = spdk_sock_close(&client_sock[i]); + CU_ASSERT(client_sock[i] == NULL); + CU_ASSERT(rc == 0); + + rc = spdk_sock_close(&server_sock[i]); + CU_ASSERT(server_sock[i] == NULL); + CU_ASSERT(rc == 0); + } + + rc = spdk_sock_group_close(&group); + CU_ASSERT(group == NULL); + CU_ASSERT(rc == 0); + + rc = spdk_sock_close(&listen_sock); + CU_ASSERT(listen_sock == NULL); + CU_ASSERT(rc == 0); +} + +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("sock", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "posix_sock", posix_sock) == NULL || + CU_add_test(suite, "ut_sock", ut_sock) == NULL || + CU_add_test(suite, "posix_sock_group", posix_sock_group) == NULL || + CU_add_test(suite, "ut_sock_group", ut_sock_group) == NULL || + CU_add_test(suite, "posix_sock_group_fairness", posix_sock_group_fairness) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/thread/Makefile b/src/spdk/test/unit/lib/thread/Makefile new file mode 100644 index 00000000..d7381694 --- /dev/null +++ b/src/spdk/test/unit/lib/thread/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = thread.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/thread/thread.c/.gitignore b/src/spdk/test/unit/lib/thread/thread.c/.gitignore new file mode 100644 index 00000000..1a165acb --- /dev/null +++ b/src/spdk/test/unit/lib/thread/thread.c/.gitignore @@ -0,0 +1 @@ +thread_ut diff --git a/src/spdk/test/unit/lib/thread/thread.c/Makefile b/src/spdk/test/unit/lib/thread/thread.c/Makefile new file mode 100644 index 00000000..23cfa45a --- /dev/null +++ b/src/spdk/test/unit/lib/thread/thread.c/Makefile @@ -0,0 +1,41 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.mock.unittest.mk + +TEST_FILE = thread_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/thread/thread.c/thread_ut.c b/src/spdk/test/unit/lib/thread/thread.c/thread_ut.c new file mode 100644 index 00000000..464e430f --- /dev/null +++ b/src/spdk/test/unit/lib/thread/thread.c/thread_ut.c @@ -0,0 +1,501 @@ +/*- + * 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 "thread/thread.c" +#include "common/lib/test_env.c" +#include "common/lib/ut_multithread.c" + +static void +_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx) +{ + fn(ctx); +} + +static void +thread_alloc(void) +{ + CU_ASSERT(TAILQ_EMPTY(&g_threads)); + allocate_threads(1); + CU_ASSERT(!TAILQ_EMPTY(&g_threads)); + free_threads(); + CU_ASSERT(TAILQ_EMPTY(&g_threads)); +} + +static void +send_msg_cb(void *ctx) +{ + bool *done = ctx; + + *done = true; +} + +static void +thread_send_msg(void) +{ + struct spdk_thread *thread0; + bool done = false; + + allocate_threads(2); + set_thread(0); + thread0 = spdk_get_thread(); + + set_thread(1); + /* Simulate thread 1 sending a message to thread 0. */ + spdk_thread_send_msg(thread0, send_msg_cb, &done); + + /* We have not polled thread 0 yet, so done should be false. */ + CU_ASSERT(!done); + + /* + * Poll thread 1. The message was sent to thread 0, so this should be + * a nop and done should still be false. + */ + poll_thread(1); + CU_ASSERT(!done); + + /* + * Poll thread 0. This should execute the message and done should then + * be true. + */ + poll_thread(0); + CU_ASSERT(done); + + free_threads(); +} + +static int +poller_run_done(void *ctx) +{ + bool *poller_run = ctx; + + *poller_run = true; + + return -1; +} + +static void +thread_poller(void) +{ + struct spdk_poller *poller = NULL; + bool poller_run = false; + + allocate_threads(1); + + set_thread(0); + reset_time(); + /* Register a poller with no-wait time and test execution */ + poller = spdk_poller_register(poller_run_done, &poller_run, 0); + CU_ASSERT(poller != NULL); + + poll_threads(); + CU_ASSERT(poller_run == true); + + spdk_poller_unregister(&poller); + CU_ASSERT(poller == NULL); + + /* Register a poller with 1000us wait time and test single execution */ + poller_run = false; + poller = spdk_poller_register(poller_run_done, &poller_run, 1000); + CU_ASSERT(poller != NULL); + + poll_threads(); + CU_ASSERT(poller_run == false); + + increment_time(1000); + poll_threads(); + CU_ASSERT(poller_run == true); + + reset_time(); + poller_run = false; + poll_threads(); + CU_ASSERT(poller_run == false); + + increment_time(1000); + poll_threads(); + CU_ASSERT(poller_run == true); + + spdk_poller_unregister(&poller); + CU_ASSERT(poller == NULL); + + free_threads(); +} + +static void +for_each_cb(void *ctx) +{ + int *count = ctx; + + (*count)++; +} + +static void +thread_for_each(void) +{ + int count = 0; + int i; + + allocate_threads(3); + set_thread(0); + + spdk_for_each_thread(for_each_cb, &count, for_each_cb); + + /* We have not polled thread 0 yet, so count should be 0 */ + CU_ASSERT(count == 0); + + /* Poll each thread to verify the message is passed to each */ + for (i = 0; i < 3; i++) { + poll_thread(i); + CU_ASSERT(count == (i + 1)); + } + + /* + * After each thread is called, the completion calls it + * one more time. + */ + poll_thread(0); + CU_ASSERT(count == 4); + + free_threads(); +} + +static int +channel_create(void *io_device, void *ctx_buf) +{ + return 0; +} + +static void +channel_destroy(void *io_device, void *ctx_buf) +{ +} + +static void +channel_msg(struct spdk_io_channel_iter *i) +{ + struct spdk_io_channel *ch = spdk_io_channel_iter_get_channel(i); + int *count = spdk_io_channel_get_ctx(ch); + + (*count)++; + + spdk_for_each_channel_continue(i, 0); +} + +static void +channel_cpl(struct spdk_io_channel_iter *i, int status) +{ +} + +static void +for_each_channel_remove(void) +{ + struct spdk_io_channel *ch0, *ch1, *ch2; + int io_target; + int count = 0; + + allocate_threads(3); + spdk_io_device_register(&io_target, channel_create, channel_destroy, sizeof(int), NULL); + set_thread(0); + ch0 = spdk_get_io_channel(&io_target); + set_thread(1); + ch1 = spdk_get_io_channel(&io_target); + set_thread(2); + ch2 = spdk_get_io_channel(&io_target); + + /* + * Test that io_channel handles the case where we start to iterate through + * the channels, and during the iteration, one of the channels is deleted. + * This is done in some different and sometimes non-intuitive orders, because + * some operations are deferred and won't execute until their threads are + * polled. + * + * Case #1: Put the I/O channel before spdk_for_each_channel. + */ + set_thread(0); + spdk_put_io_channel(ch0); + spdk_for_each_channel(&io_target, channel_msg, &count, channel_cpl); + poll_threads(); + + /* + * Case #2: Put the I/O channel after spdk_for_each_channel, but before + * thread 0 is polled. + */ + ch0 = spdk_get_io_channel(&io_target); + spdk_for_each_channel(&io_target, channel_msg, &count, channel_cpl); + spdk_put_io_channel(ch0); + poll_threads(); + + set_thread(1); + spdk_put_io_channel(ch1); + set_thread(2); + spdk_put_io_channel(ch2); + spdk_io_device_unregister(&io_target, NULL); + poll_threads(); + + free_threads(); +} + +struct unreg_ctx { + bool ch_done; + bool foreach_done; +}; + +static void +unreg_ch_done(struct spdk_io_channel_iter *i) +{ + struct unreg_ctx *ctx = spdk_io_channel_iter_get_ctx(i); + + ctx->ch_done = true; + + SPDK_CU_ASSERT_FATAL(i->cur_thread != NULL); + spdk_for_each_channel_continue(i, 0); +} + +static void +unreg_foreach_done(struct spdk_io_channel_iter *i, int status) +{ + struct unreg_ctx *ctx = spdk_io_channel_iter_get_ctx(i); + + ctx->foreach_done = true; +} + +static void +for_each_channel_unreg(void) +{ + struct spdk_io_channel *ch0; + struct io_device *dev; + struct unreg_ctx ctx = {}; + int io_target; + + allocate_threads(1); + CU_ASSERT(TAILQ_EMPTY(&g_io_devices)); + spdk_io_device_register(&io_target, channel_create, channel_destroy, sizeof(int), NULL); + CU_ASSERT(!TAILQ_EMPTY(&g_io_devices)); + dev = TAILQ_FIRST(&g_io_devices); + SPDK_CU_ASSERT_FATAL(dev != NULL); + CU_ASSERT(TAILQ_NEXT(dev, tailq) == NULL); + set_thread(0); + ch0 = spdk_get_io_channel(&io_target); + spdk_for_each_channel(&io_target, unreg_ch_done, &ctx, unreg_foreach_done); + + spdk_io_device_unregister(&io_target, NULL); + /* + * There is an outstanding foreach call on the io_device, so the unregister should not + * have removed the device. + */ + CU_ASSERT(dev == TAILQ_FIRST(&g_io_devices)); + spdk_io_device_register(&io_target, channel_create, channel_destroy, sizeof(int), NULL); + /* + * There is already a device registered at &io_target, so a new io_device should not + * have been added to g_io_devices. + */ + CU_ASSERT(dev == TAILQ_FIRST(&g_io_devices)); + CU_ASSERT(TAILQ_NEXT(dev, tailq) == NULL); + + poll_thread(0); + CU_ASSERT(ctx.ch_done == true); + CU_ASSERT(ctx.foreach_done == true); + /* + * There are no more foreach operations outstanding, so we can unregister the device, + * even though a channel still exists for the device. + */ + spdk_io_device_unregister(&io_target, NULL); + CU_ASSERT(TAILQ_EMPTY(&g_io_devices)); + + set_thread(0); + spdk_put_io_channel(ch0); + + poll_threads(); + + free_threads(); +} + +static void +thread_name(void) +{ + struct spdk_thread *thread; + const char *name; + + /* Create thread with no name, which automatically generates one */ + spdk_allocate_thread(_send_msg, NULL, NULL, NULL, NULL); + thread = spdk_get_thread(); + SPDK_CU_ASSERT_FATAL(thread != NULL); + name = spdk_thread_get_name(thread); + CU_ASSERT(name != NULL); + spdk_free_thread(); + + /* Create thread named "test_thread" */ + spdk_allocate_thread(_send_msg, NULL, NULL, NULL, "test_thread"); + thread = spdk_get_thread(); + SPDK_CU_ASSERT_FATAL(thread != NULL); + name = spdk_thread_get_name(thread); + SPDK_CU_ASSERT_FATAL(name != NULL); + CU_ASSERT(strcmp(name, "test_thread") == 0); + spdk_free_thread(); +} + +static uint64_t device1; +static uint64_t device2; +static uint64_t device3; + +static uint64_t ctx1 = 0x1111; +static uint64_t ctx2 = 0x2222; + +static int g_create_cb_calls = 0; +static int g_destroy_cb_calls = 0; + +static int +create_cb_1(void *io_device, void *ctx_buf) +{ + CU_ASSERT(io_device == &device1); + *(uint64_t *)ctx_buf = ctx1; + g_create_cb_calls++; + return 0; +} + +static void +destroy_cb_1(void *io_device, void *ctx_buf) +{ + CU_ASSERT(io_device == &device1); + CU_ASSERT(*(uint64_t *)ctx_buf == ctx1); + g_destroy_cb_calls++; +} + +static int +create_cb_2(void *io_device, void *ctx_buf) +{ + CU_ASSERT(io_device == &device2); + *(uint64_t *)ctx_buf = ctx2; + g_create_cb_calls++; + return 0; +} + +static void +destroy_cb_2(void *io_device, void *ctx_buf) +{ + CU_ASSERT(io_device == &device2); + CU_ASSERT(*(uint64_t *)ctx_buf == ctx2); + g_destroy_cb_calls++; +} + +static void +channel(void) +{ + struct spdk_io_channel *ch1, *ch2; + void *ctx; + + spdk_allocate_thread(_send_msg, NULL, NULL, NULL, "thread0"); + spdk_io_device_register(&device1, create_cb_1, destroy_cb_1, sizeof(ctx1), NULL); + spdk_io_device_register(&device2, create_cb_2, destroy_cb_2, sizeof(ctx2), NULL); + + g_create_cb_calls = 0; + ch1 = spdk_get_io_channel(&device1); + CU_ASSERT(g_create_cb_calls == 1); + SPDK_CU_ASSERT_FATAL(ch1 != NULL); + + g_create_cb_calls = 0; + ch2 = spdk_get_io_channel(&device1); + CU_ASSERT(g_create_cb_calls == 0); + CU_ASSERT(ch1 == ch2); + SPDK_CU_ASSERT_FATAL(ch2 != NULL); + + g_destroy_cb_calls = 0; + spdk_put_io_channel(ch2); + CU_ASSERT(g_destroy_cb_calls == 0); + + g_create_cb_calls = 0; + ch2 = spdk_get_io_channel(&device2); + CU_ASSERT(g_create_cb_calls == 1); + CU_ASSERT(ch1 != ch2); + SPDK_CU_ASSERT_FATAL(ch2 != NULL); + + ctx = spdk_io_channel_get_ctx(ch2); + CU_ASSERT(*(uint64_t *)ctx == ctx2); + + g_destroy_cb_calls = 0; + spdk_put_io_channel(ch1); + CU_ASSERT(g_destroy_cb_calls == 1); + + g_destroy_cb_calls = 0; + spdk_put_io_channel(ch2); + CU_ASSERT(g_destroy_cb_calls == 1); + + ch1 = spdk_get_io_channel(&device3); + CU_ASSERT(ch1 == NULL); + + spdk_io_device_unregister(&device1, NULL); + spdk_io_device_unregister(&device2, NULL); + CU_ASSERT(TAILQ_EMPTY(&g_io_devices)); + spdk_free_thread(); + CU_ASSERT(TAILQ_EMPTY(&g_threads)); +} + +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("io_channel", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "thread_alloc", thread_alloc) == NULL || + CU_add_test(suite, "thread_send_msg", thread_send_msg) == NULL || + CU_add_test(suite, "thread_poller", thread_poller) == NULL || + CU_add_test(suite, "thread_for_each", thread_for_each) == NULL || + CU_add_test(suite, "for_each_channel_remove", for_each_channel_remove) == NULL || + CU_add_test(suite, "for_each_channel_unreg", for_each_channel_unreg) == NULL || + CU_add_test(suite, "thread_name", thread_name) == NULL || + CU_add_test(suite, "channel", channel) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} diff --git a/src/spdk/test/unit/lib/util/Makefile b/src/spdk/test/unit/lib/util/Makefile new file mode 100644 index 00000000..4813e63b --- /dev/null +++ b/src/spdk/test/unit/lib/util/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = base64.c bit_array.c cpuset.c crc16.c crc32_ieee.c crc32c.c string.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/util/base64.c/.gitignore b/src/spdk/test/unit/lib/util/base64.c/.gitignore new file mode 100644 index 00000000..a5b17523 --- /dev/null +++ b/src/spdk/test/unit/lib/util/base64.c/.gitignore @@ -0,0 +1 @@ +base64_ut diff --git a/src/spdk/test/unit/lib/util/base64.c/Makefile b/src/spdk/test/unit/lib/util/base64.c/Makefile new file mode 100644 index 00000000..ff6c9214 --- /dev/null +++ b/src/spdk/test/unit/lib/util/base64.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = base64_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/util/base64.c/base64_ut.c b/src/spdk/test/unit/lib/util/base64.c/base64_ut.c new file mode 100644 index 00000000..652a1e94 --- /dev/null +++ b/src/spdk/test/unit/lib/util/base64.c/base64_ut.c @@ -0,0 +1,268 @@ +/*- + * 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 "util/base64.c" + +char text_A[] = "FZB3"; +uint8_t raw_A[] = {0x15, 0x90, 0x77}; +char text_B[] = "AbC/1+c="; +char text_urlsafe_B[] = "AbC_1-c="; +uint8_t raw_B[] = {0x01, 0xB0, 0xBF, 0xD7, 0xE7}; +char text_C[] = "AbC/1+cC"; +char text_urlsafe_C[] = "AbC_1-cC"; +uint8_t raw_C[] = {0x01, 0xB0, 0xBF, 0xD7, 0xE7, 0x02}; +char text_D[] = "AbC/1w=="; +char text_urlsafe_D[] = "AbC_1w=="; +uint8_t raw_D[] = {0x01, 0xB0, 0xBF, 0xD7}; +char text_E[] = "AbC12==="; +char text_F[] = "AbCd112"; +char text_G[] = "AbCd12"; +char text_H[] = "AbC12"; + +static void +test_base64_get_encoded_strlen(void) +{ + uint32_t raw_lens[4] = {8, 9, 10, 11}; + uint32_t text_strlens[4] = {12, 12, 16, 16}; + uint32_t text_strlen; + int i; + + for (i = 0; i < 4; i++) { + text_strlen = spdk_base64_get_encoded_strlen(raw_lens[i]); + CU_ASSERT_EQUAL(text_strlen, text_strlens[i]); + } +} + +static void +test_base64_get_decoded_len(void) +{ + uint32_t text_strlens[4] = {8, 10, 11, 12}; + uint32_t raw_lens[4] = {6, 7, 8, 9}; + uint32_t bin_len; + int i; + + for (i = 0; i < 4; i++) { + bin_len = spdk_base64_get_decoded_len(text_strlens[i]); + CU_ASSERT_EQUAL(bin_len, raw_lens[i]); + } +} + +static void +test_base64_encode(void) +{ + char text[100]; + int ret; + + ret = spdk_base64_encode(text, raw_A, sizeof(raw_A)); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT(strcmp(text, text_A) == 0); + CU_ASSERT_EQUAL(strlen(text), strlen(text_A)); + + ret = spdk_base64_encode(text, raw_B, sizeof(raw_B)); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT(strcmp(text, text_B) == 0); + CU_ASSERT_EQUAL(strlen(text), strlen(text_B)); + + ret = spdk_base64_encode(text, raw_C, sizeof(raw_C)); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT(strcmp(text, text_C) == 0); + + ret = spdk_base64_encode(text, raw_D, sizeof(raw_D)); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT(strcmp(text, text_D) == 0); + + ret = spdk_base64_encode(NULL, raw_A, sizeof(raw_A)); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_encode(text, NULL, sizeof(raw_A)); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_encode(text, raw_A, 0); + CU_ASSERT_EQUAL(ret, -EINVAL); +} + +static void +test_base64_decode(void) +{ + char raw_buf[100]; + void *raw = (void *)raw_buf; + size_t raw_len; + int ret; + + ret = spdk_base64_decode(raw, &raw_len, text_A); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT_EQUAL(raw_len, sizeof(raw_A)); + CU_ASSERT(memcmp(raw, raw_A, sizeof(raw_A)) == 0); + + ret = spdk_base64_decode(raw, &raw_len, text_B); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT_EQUAL(raw_len, sizeof(raw_B)); + CU_ASSERT(memcmp(raw, raw_B, sizeof(raw_B)) == 0); + + ret = spdk_base64_decode(raw, &raw_len, text_C); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT_EQUAL(raw_len, sizeof(raw_C)); + CU_ASSERT(memcmp(raw, raw_C, sizeof(raw_C)) == 0); + + ret = spdk_base64_decode(raw, &raw_len, text_D); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT_EQUAL(raw_len, sizeof(raw_D)); + CU_ASSERT(memcmp(raw, raw_D, sizeof(raw_D)) == 0); + + ret = spdk_base64_decode(raw, &raw_len, text_E); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_decode(raw, &raw_len, text_F); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_decode(raw, &raw_len, text_G); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_decode(raw, &raw_len, text_H); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_decode(NULL, &raw_len, text_H); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_decode(raw, &raw_len, NULL); + CU_ASSERT_EQUAL(ret, -EINVAL); +} + +static void +test_base64_urlsafe_encode(void) +{ + char text[100]; + int ret; + + ret = spdk_base64_urlsafe_encode(text, raw_A, sizeof(raw_A)); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT(strcmp(text, text_A) == 0); + CU_ASSERT_EQUAL(strlen(text), strlen(text_A)); + + ret = spdk_base64_urlsafe_encode(text, raw_B, sizeof(raw_B)); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT(strcmp(text, text_urlsafe_B) == 0); + CU_ASSERT_EQUAL(strlen(text), strlen(text_urlsafe_B)); + + ret = spdk_base64_urlsafe_encode(text, raw_C, sizeof(raw_C)); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT(strcmp(text, text_urlsafe_C) == 0); + + ret = spdk_base64_urlsafe_encode(text, raw_D, sizeof(raw_D)); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT(strcmp(text, text_urlsafe_D) == 0); + + ret = spdk_base64_urlsafe_encode(NULL, raw_A, sizeof(raw_A)); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_urlsafe_encode(text, NULL, sizeof(raw_A)); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_urlsafe_encode(text, raw_A, 0); + CU_ASSERT_EQUAL(ret, -EINVAL); +} + +static void +test_base64_urlsafe_decode(void) +{ + char raw_buf[100]; + void *raw = (void *)raw_buf; + size_t raw_len = 0; + int ret; + + ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_A); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT_EQUAL(raw_len, sizeof(raw_A)); + CU_ASSERT(memcmp(raw, raw_A, sizeof(raw_A)) == 0); + + ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_urlsafe_B); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT_EQUAL(raw_len, sizeof(raw_B)); + CU_ASSERT(memcmp(raw, raw_B, sizeof(raw_B)) == 0); + + ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_urlsafe_C); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT_EQUAL(raw_len, sizeof(raw_C)); + CU_ASSERT(memcmp(raw, raw_C, sizeof(raw_C)) == 0); + + ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_urlsafe_D); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT_EQUAL(raw_len, sizeof(raw_D)); + CU_ASSERT(memcmp(raw, raw_D, sizeof(raw_D)) == 0); + + ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_E); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_F); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_G); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_H); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_urlsafe_decode(NULL, &raw_len, text_H); + CU_ASSERT_EQUAL(ret, -EINVAL); + ret = spdk_base64_urlsafe_decode(raw, &raw_len, NULL); + CU_ASSERT_EQUAL(ret, -EINVAL); +} + +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("base64", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_base64_get_encoded_strlen", test_base64_get_encoded_strlen) == NULL || + CU_add_test(suite, "test_base64_get_decoded_len", + test_base64_get_decoded_len) == NULL || + CU_add_test(suite, "test_base64_encode", test_base64_encode) == NULL || + CU_add_test(suite, "test_base64_decode", test_base64_decode) == NULL || + CU_add_test(suite, "test_base64_urlsafe_encode", test_base64_urlsafe_encode) == NULL || + CU_add_test(suite, "test_base64_urlsafe_decode", test_base64_urlsafe_decode) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/util/bit_array.c/.gitignore b/src/spdk/test/unit/lib/util/bit_array.c/.gitignore new file mode 100644 index 00000000..24300cdb --- /dev/null +++ b/src/spdk/test/unit/lib/util/bit_array.c/.gitignore @@ -0,0 +1 @@ +bit_array_ut diff --git a/src/spdk/test/unit/lib/util/bit_array.c/Makefile b/src/spdk/test/unit/lib/util/bit_array.c/Makefile new file mode 100644 index 00000000..b7f8e3f6 --- /dev/null +++ b/src/spdk/test/unit/lib/util/bit_array.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = bit_array_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/util/bit_array.c/bit_array_ut.c b/src/spdk/test/unit/lib/util/bit_array.c/bit_array_ut.c new file mode 100644 index 00000000..18d84b94 --- /dev/null +++ b/src/spdk/test/unit/lib/util/bit_array.c/bit_array_ut.c @@ -0,0 +1,327 @@ +/*- + * 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 "util/bit_array.c" + +void * +spdk_dma_realloc(void *buf, size_t size, size_t align, uint64_t *phys_addr) +{ + return realloc(buf, size); +} + +void +spdk_dma_free(void *buf) +{ + free(buf); +} + +static void +test_1bit(void) +{ + struct spdk_bit_array *ba; + + ba = spdk_bit_array_create(1); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_capacity(ba) == 1); + + CU_ASSERT(spdk_bit_array_get(ba, 0) == false); + CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == UINT32_MAX); + + /* Set bit 0 */ + CU_ASSERT(spdk_bit_array_set(ba, 0) == 0); + CU_ASSERT(spdk_bit_array_get(ba, 0) == true); + CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == 0); + + /* Clear bit 0 */ + spdk_bit_array_clear(ba, 0); + CU_ASSERT(spdk_bit_array_get(ba, 0) == false); + CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == UINT32_MAX); + + spdk_bit_array_free(&ba); + CU_ASSERT(ba == NULL); +} + +static void +test_64bit(void) +{ + struct spdk_bit_array *ba; + + ba = spdk_bit_array_create(64); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_capacity(ba) == 64); + CU_ASSERT(spdk_bit_array_get(ba, 0) == false); + CU_ASSERT(spdk_bit_array_get(ba, 63) == false); + CU_ASSERT(spdk_bit_array_get(ba, 64) == false); + CU_ASSERT(spdk_bit_array_get(ba, 1000) == false); + CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == UINT32_MAX); + + /* Set bit 1 */ + CU_ASSERT(spdk_bit_array_set(ba, 1) == 0); + CU_ASSERT(spdk_bit_array_get(ba, 0) == false); + CU_ASSERT(spdk_bit_array_get(ba, 1) == true); + CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == 1); + + /* Set bit 63 (1 still set) */ + CU_ASSERT(spdk_bit_array_set(ba, 63) == 0); + CU_ASSERT(spdk_bit_array_get(ba, 0) == false); + CU_ASSERT(spdk_bit_array_get(ba, 1) == true); + CU_ASSERT(spdk_bit_array_get(ba, 63) == true); + CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == 1); + + /* Clear bit 1 (63 still set) */ + spdk_bit_array_clear(ba, 1); + CU_ASSERT(spdk_bit_array_get(ba, 1) == false); + CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == 63); + + /* Clear bit 63 (no bits set) */ + spdk_bit_array_clear(ba, 63); + CU_ASSERT(spdk_bit_array_get(ba, 63) == false); + CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == UINT32_MAX); + + spdk_bit_array_free(&ba); +} + +static void +test_find(void) +{ + struct spdk_bit_array *ba; + uint32_t i; + + ba = spdk_bit_array_create(256); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_capacity(ba) == 256); + + /* Set all bits */ + for (i = 0; i < 256; i++) { + CU_ASSERT(spdk_bit_array_set(ba, i) == 0); + } + + /* Verify that find_first_set and find_first_clear work for each starting position */ + for (i = 0; i < 256; i++) { + CU_ASSERT(spdk_bit_array_find_first_set(ba, i) == i); + CU_ASSERT(spdk_bit_array_find_first_clear(ba, i) == UINT32_MAX); + } + CU_ASSERT(spdk_bit_array_find_first_set(ba, 256) == UINT32_MAX); + CU_ASSERT(spdk_bit_array_find_first_clear(ba, 256) == UINT32_MAX); + + /* Clear bits 0 through 31 */ + for (i = 0; i < 32; i++) { + spdk_bit_array_clear(ba, i); + } + + for (i = 0; i < 32; i++) { + CU_ASSERT(spdk_bit_array_find_first_set(ba, i) == 32); + CU_ASSERT(spdk_bit_array_find_first_clear(ba, i) == i); + } + + for (i = 32; i < 256; i++) { + CU_ASSERT(spdk_bit_array_find_first_set(ba, i) == i); + CU_ASSERT(spdk_bit_array_find_first_clear(ba, i) == UINT32_MAX); + } + + /* Clear bit 255 */ + spdk_bit_array_clear(ba, 255); + + for (i = 0; i < 32; i++) { + CU_ASSERT(spdk_bit_array_find_first_set(ba, i) == 32); + CU_ASSERT(spdk_bit_array_find_first_clear(ba, i) == i); + } + + for (i = 32; i < 255; i++) { + CU_ASSERT(spdk_bit_array_find_first_set(ba, i) == i); + CU_ASSERT(spdk_bit_array_find_first_clear(ba, i) == 255); + } + + CU_ASSERT(spdk_bit_array_find_first_clear(ba, 256) == UINT32_MAX); + + spdk_bit_array_free(&ba); +} + +static void +test_resize(void) +{ + struct spdk_bit_array *ba; + + /* Start with a 0 bit array */ + ba = spdk_bit_array_create(0); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_capacity(ba) == 0); + CU_ASSERT(spdk_bit_array_get(ba, 0) == false); + CU_ASSERT(spdk_bit_array_set(ba, 0) == -EINVAL); + spdk_bit_array_clear(ba, 0); + + /* Increase size to 1 bit */ + SPDK_CU_ASSERT_FATAL(spdk_bit_array_resize(&ba, 1) == 0); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_capacity(ba) == 1); + CU_ASSERT(spdk_bit_array_get(ba, 0) == false); + CU_ASSERT(spdk_bit_array_set(ba, 0) == 0); + CU_ASSERT(spdk_bit_array_get(ba, 0) == true); + + /* Increase size to 2 bits */ + SPDK_CU_ASSERT_FATAL(spdk_bit_array_resize(&ba, 2) == 0); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_capacity(ba) == 2); + CU_ASSERT(spdk_bit_array_get(ba, 1) == false); + CU_ASSERT(spdk_bit_array_set(ba, 1) == 0); + CU_ASSERT(spdk_bit_array_get(ba, 1) == true); + + /* Shrink size back to 1 bit */ + SPDK_CU_ASSERT_FATAL(spdk_bit_array_resize(&ba, 1) == 0); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_capacity(ba) == 1); + CU_ASSERT(spdk_bit_array_get(ba, 0) == true); + CU_ASSERT(spdk_bit_array_get(ba, 1) == false); + + /* Increase size to 65 bits */ + SPDK_CU_ASSERT_FATAL(spdk_bit_array_resize(&ba, 65) == 0); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_capacity(ba) == 65); + CU_ASSERT(spdk_bit_array_get(ba, 0) == true); + CU_ASSERT(spdk_bit_array_get(ba, 1) == false); + CU_ASSERT(spdk_bit_array_set(ba, 64) == 0); + CU_ASSERT(spdk_bit_array_get(ba, 64) == true); + + /* Shrink size back to 0 bits */ + SPDK_CU_ASSERT_FATAL(spdk_bit_array_resize(&ba, 0) == 0); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_capacity(ba) == 0); + CU_ASSERT(spdk_bit_array_get(ba, 0) == false); + CU_ASSERT(spdk_bit_array_get(ba, 1) == false); + + spdk_bit_array_free(&ba); +} + +static void +test_errors(void) +{ + /* Passing NULL to resize should fail. */ + CU_ASSERT(spdk_bit_array_resize(NULL, 0) == -EINVAL); + + /* Passing NULL to free is a no-op. */ + spdk_bit_array_free(NULL); +} + +static void +test_count(void) +{ + struct spdk_bit_array *ba; + uint32_t i; + + /* 0-bit array should have 0 bits set and 0 bits clear */ + ba = spdk_bit_array_create(0); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_count_set(ba) == 0); + CU_ASSERT(spdk_bit_array_count_clear(ba) == 0); + spdk_bit_array_free(&ba); + + /* 1-bit array */ + ba = spdk_bit_array_create(1); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_count_set(ba) == 0); + CU_ASSERT(spdk_bit_array_count_clear(ba) == 1); + spdk_bit_array_set(ba, 0); + CU_ASSERT(spdk_bit_array_count_set(ba) == 1); + CU_ASSERT(spdk_bit_array_count_clear(ba) == 0); + spdk_bit_array_free(&ba); + + /* 65-bit array */ + ba = spdk_bit_array_create(65); + SPDK_CU_ASSERT_FATAL(ba != NULL); + CU_ASSERT(spdk_bit_array_count_set(ba) == 0); + CU_ASSERT(spdk_bit_array_count_clear(ba) == 65); + spdk_bit_array_set(ba, 0); + CU_ASSERT(spdk_bit_array_count_set(ba) == 1); + CU_ASSERT(spdk_bit_array_count_clear(ba) == 64); + spdk_bit_array_set(ba, 5); + CU_ASSERT(spdk_bit_array_count_set(ba) == 2); + CU_ASSERT(spdk_bit_array_count_clear(ba) == 63); + spdk_bit_array_set(ba, 13); + CU_ASSERT(spdk_bit_array_count_set(ba) == 3); + CU_ASSERT(spdk_bit_array_count_clear(ba) == 62); + spdk_bit_array_clear(ba, 0); + CU_ASSERT(spdk_bit_array_count_set(ba) == 2); + CU_ASSERT(spdk_bit_array_count_clear(ba) == 63); + for (i = 0; i < 65; i++) { + spdk_bit_array_set(ba, i); + } + CU_ASSERT(spdk_bit_array_count_set(ba) == 65); + CU_ASSERT(spdk_bit_array_count_clear(ba) == 0); + for (i = 0; i < 65; i++) { + spdk_bit_array_clear(ba, i); + CU_ASSERT(spdk_bit_array_count_set(ba) == 65 - i - 1); + CU_ASSERT(spdk_bit_array_count_clear(ba) == i + 1); + } + spdk_bit_array_free(&ba); +} + +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("bit_array", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_1bit", test_1bit) == NULL || + CU_add_test(suite, "test_64bit", test_64bit) == NULL || + CU_add_test(suite, "test_find", test_find) == NULL || + CU_add_test(suite, "test_resize", test_resize) == NULL || + CU_add_test(suite, "test_errors", test_errors) == NULL || + CU_add_test(suite, "test_count", test_count) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/util/cpuset.c/.gitignore b/src/spdk/test/unit/lib/util/cpuset.c/.gitignore new file mode 100644 index 00000000..2ca1a2d3 --- /dev/null +++ b/src/spdk/test/unit/lib/util/cpuset.c/.gitignore @@ -0,0 +1 @@ +cpuset_ut diff --git a/src/spdk/test/unit/lib/util/cpuset.c/Makefile b/src/spdk/test/unit/lib/util/cpuset.c/Makefile new file mode 100644 index 00000000..da7a1400 --- /dev/null +++ b/src/spdk/test/unit/lib/util/cpuset.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = cpuset_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/util/cpuset.c/cpuset_ut.c b/src/spdk/test/unit/lib/util/cpuset.c/cpuset_ut.c new file mode 100644 index 00000000..6fea0ad3 --- /dev/null +++ b/src/spdk/test/unit/lib/util/cpuset.c/cpuset_ut.c @@ -0,0 +1,265 @@ +/*- + * 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/cpuset.h" + +#include "spdk_cunit.h" + +#include "util/cpuset.c" + +static int +cpuset_check_range(struct spdk_cpuset *core_mask, uint32_t min, uint32_t max, bool isset) +{ + uint32_t core; + for (core = min; core <= max; core++) { + if (isset != spdk_cpuset_get_cpu(core_mask, core)) { + return -1; + } + } + return 0; +} + +static void +test_cpuset(void) +{ + uint32_t cpu; + struct spdk_cpuset *set = spdk_cpuset_alloc(); + + SPDK_CU_ASSERT_FATAL(set != NULL); + CU_ASSERT(spdk_cpuset_count(set) == 0); + + /* Set cpu 0 */ + spdk_cpuset_set_cpu(set, 0, true); + CU_ASSERT(spdk_cpuset_get_cpu(set, 0) == true); + CU_ASSERT(cpuset_check_range(set, 1, SPDK_CPUSET_SIZE - 1, false) == 0); + CU_ASSERT(spdk_cpuset_count(set) == 1); + + /* Set last cpu (cpu 0 already set) */ + spdk_cpuset_set_cpu(set, SPDK_CPUSET_SIZE - 1, true); + CU_ASSERT(spdk_cpuset_get_cpu(set, 0) == true); + CU_ASSERT(spdk_cpuset_get_cpu(set, SPDK_CPUSET_SIZE - 1) == true); + CU_ASSERT(cpuset_check_range(set, 1, SPDK_CPUSET_SIZE - 2, false) == 0); + CU_ASSERT(spdk_cpuset_count(set) == 2); + + /* Clear cpu 0 (last cpu already set) */ + spdk_cpuset_set_cpu(set, 0, false); + CU_ASSERT(spdk_cpuset_get_cpu(set, 0) == false); + CU_ASSERT(cpuset_check_range(set, 1, SPDK_CPUSET_SIZE - 2, false) == 0); + CU_ASSERT(spdk_cpuset_get_cpu(set, SPDK_CPUSET_SIZE - 1) == true); + CU_ASSERT(spdk_cpuset_count(set) == 1); + + /* Set middle cpu (last cpu already set) */ + cpu = (SPDK_CPUSET_SIZE - 1) / 2; + spdk_cpuset_set_cpu(set, cpu, true); + CU_ASSERT(spdk_cpuset_get_cpu(set, cpu) == true); + CU_ASSERT(spdk_cpuset_get_cpu(set, SPDK_CPUSET_SIZE - 1) == true); + CU_ASSERT(cpuset_check_range(set, 1, cpu - 1, false) == 0); + CU_ASSERT(cpuset_check_range(set, cpu + 1, SPDK_CPUSET_SIZE - 2, false) == 0); + CU_ASSERT(spdk_cpuset_count(set) == 2); + + /* Set all cpus */ + for (cpu = 0; cpu < SPDK_CPUSET_SIZE; cpu++) { + spdk_cpuset_set_cpu(set, cpu, true); + } + CU_ASSERT(cpuset_check_range(set, 0, SPDK_CPUSET_SIZE - 1, true) == 0); + CU_ASSERT(spdk_cpuset_count(set) == SPDK_CPUSET_SIZE); + + /* Clear all cpus */ + spdk_cpuset_zero(set); + CU_ASSERT(cpuset_check_range(set, 0, SPDK_CPUSET_SIZE - 1, false) == 0); + CU_ASSERT(spdk_cpuset_count(set) == 0); + + spdk_cpuset_free(set); +} + +static void +test_cpuset_parse(void) +{ + int rc; + struct spdk_cpuset *core_mask; + char buf[1024]; + + core_mask = spdk_cpuset_alloc(); + SPDK_CU_ASSERT_FATAL(core_mask != NULL); + + /* Only core 0 should be set */ + rc = spdk_cpuset_parse(core_mask, "0x1"); + CU_ASSERT(rc >= 0); + CU_ASSERT(cpuset_check_range(core_mask, 0, 0, true) == 0); + CU_ASSERT(cpuset_check_range(core_mask, 1, SPDK_CPUSET_SIZE - 1, false) == 0); + + /* Only core 1 should be set */ + rc = spdk_cpuset_parse(core_mask, "[1]"); + CU_ASSERT(rc >= 0); + CU_ASSERT(cpuset_check_range(core_mask, 0, 0, false) == 0); + CU_ASSERT(cpuset_check_range(core_mask, 1, 1, true) == 0); + CU_ASSERT(cpuset_check_range(core_mask, 2, SPDK_CPUSET_SIZE - 1, false) == 0); + + /* Set cores 0-10,12,128-254 */ + rc = spdk_cpuset_parse(core_mask, "[0-10,12,128-254]"); + CU_ASSERT(rc >= 0); + CU_ASSERT(cpuset_check_range(core_mask, 0, 10, true) == 0); + CU_ASSERT(cpuset_check_range(core_mask, 11, 11, false) == 0); + CU_ASSERT(cpuset_check_range(core_mask, 12, 12, true) == 0); + CU_ASSERT(cpuset_check_range(core_mask, 13, 127, false) == 0); + CU_ASSERT(cpuset_check_range(core_mask, 128, 254, true) == 0); + CU_ASSERT(cpuset_check_range(core_mask, 255, SPDK_CPUSET_SIZE - 1, false) == 0); + + /* Set all cores */ + snprintf(buf, sizeof(buf), "[0-%d]", SPDK_CPUSET_SIZE - 1); + rc = spdk_cpuset_parse(core_mask, buf); + CU_ASSERT(rc >= 0); + CU_ASSERT(cpuset_check_range(core_mask, 0, SPDK_CPUSET_SIZE - 1, true) == 0); + + /* Null parameters not allowed */ + rc = spdk_cpuset_parse(core_mask, NULL); + CU_ASSERT(rc < 0); + + rc = spdk_cpuset_parse(NULL, "[1]"); + CU_ASSERT(rc < 0); + + /* Wrong formated core lists */ + rc = spdk_cpuset_parse(core_mask, ""); + CU_ASSERT(rc < 0); + + rc = spdk_cpuset_parse(core_mask, "["); + CU_ASSERT(rc < 0); + + rc = spdk_cpuset_parse(core_mask, "[]"); + CU_ASSERT(rc < 0); + + rc = spdk_cpuset_parse(core_mask, "[10--11]"); + CU_ASSERT(rc < 0); + + rc = spdk_cpuset_parse(core_mask, "[11-10]"); + CU_ASSERT(rc < 0); + + rc = spdk_cpuset_parse(core_mask, "[10-11,]"); + CU_ASSERT(rc < 0); + + rc = spdk_cpuset_parse(core_mask, "[,10-11]"); + CU_ASSERT(rc < 0); + + /* Out of range value */ + snprintf(buf, sizeof(buf), "[%d]", SPDK_CPUSET_SIZE + 1); + rc = spdk_cpuset_parse(core_mask, buf); + CU_ASSERT(rc < 0); + + /* Overflow value (UINT64_MAX * 10) */ + rc = spdk_cpuset_parse(core_mask, "[184467440737095516150]"); + CU_ASSERT(rc < 0); + + spdk_cpuset_free(core_mask); +} + +static void +test_cpuset_fmt(void) +{ + int i; + uint32_t lcore; + struct spdk_cpuset *core_mask = spdk_cpuset_alloc(); + const char *hex_mask; + char hex_mask_ref[SPDK_CPUSET_SIZE / 4 + 1]; + + /* Clear coremask. hex_mask should be "0" */ + spdk_cpuset_zero(core_mask); + hex_mask = spdk_cpuset_fmt(core_mask); + SPDK_CU_ASSERT_FATAL(hex_mask != NULL); + CU_ASSERT(strcmp("0", hex_mask) == 0); + + /* Set coremask 0x51234. Result should be "51234" */ + spdk_cpuset_zero(core_mask); + spdk_cpuset_set_cpu(core_mask, 2, true); + spdk_cpuset_set_cpu(core_mask, 4, true); + spdk_cpuset_set_cpu(core_mask, 5, true); + spdk_cpuset_set_cpu(core_mask, 9, true); + spdk_cpuset_set_cpu(core_mask, 12, true); + spdk_cpuset_set_cpu(core_mask, 16, true); + spdk_cpuset_set_cpu(core_mask, 18, true); + hex_mask = spdk_cpuset_fmt(core_mask); + SPDK_CU_ASSERT_FATAL(hex_mask != NULL); + CU_ASSERT(strcmp("51234", hex_mask) == 0); + + /* Set all cores */ + spdk_cpuset_zero(core_mask); + for (lcore = 0; lcore < SPDK_CPUSET_SIZE; lcore++) { + spdk_cpuset_set_cpu(core_mask, lcore, true); + } + for (i = 0; i < SPDK_CPUSET_SIZE / 4 - 1; i++) { + hex_mask_ref[i] = 'f'; + } + hex_mask_ref[SPDK_CPUSET_SIZE / 4 - 1] = '\0'; + + hex_mask = spdk_cpuset_fmt(core_mask); + CU_ASSERT(hex_mask != NULL); + if (hex_mask != NULL) { + CU_ASSERT(strcmp(hex_mask_ref, hex_mask) == 0); + } + + spdk_cpuset_free(core_mask); +} + +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("cpuset", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_cpuset", test_cpuset) == NULL || + CU_add_test(suite, "test_cpuset_parse", test_cpuset_parse) == NULL || + CU_add_test(suite, "test_cpuset_fmt", test_cpuset_fmt) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/util/crc16.c/.gitignore b/src/spdk/test/unit/lib/util/crc16.c/.gitignore new file mode 100644 index 00000000..d026adf0 --- /dev/null +++ b/src/spdk/test/unit/lib/util/crc16.c/.gitignore @@ -0,0 +1 @@ +crc16_ut diff --git a/src/spdk/test/unit/lib/util/crc16.c/Makefile b/src/spdk/test/unit/lib/util/crc16.c/Makefile new file mode 100644 index 00000000..6b8b2ad4 --- /dev/null +++ b/src/spdk/test/unit/lib/util/crc16.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = crc16_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/util/crc16.c/crc16_ut.c b/src/spdk/test/unit/lib/util/crc16.c/crc16_ut.c new file mode 100644 index 00000000..8b05e900 --- /dev/null +++ b/src/spdk/test/unit/lib/util/crc16.c/crc16_ut.c @@ -0,0 +1,80 @@ +/*- + * 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 "util/crc16.c" + +static void +test_crc16_t10dif(void) +{ + uint16_t crc; + char buf[] = "123456789"; + + crc = spdk_crc16_t10dif(buf, strlen(buf)); + CU_ASSERT(crc == 0xd0db); +} + +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("crc16", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_crc16_t10dif", test_crc16_t10dif) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/util/crc32_ieee.c/.gitignore b/src/spdk/test/unit/lib/util/crc32_ieee.c/.gitignore new file mode 100644 index 00000000..40a85a93 --- /dev/null +++ b/src/spdk/test/unit/lib/util/crc32_ieee.c/.gitignore @@ -0,0 +1 @@ +crc32_ieee_ut diff --git a/src/spdk/test/unit/lib/util/crc32_ieee.c/Makefile b/src/spdk/test/unit/lib/util/crc32_ieee.c/Makefile new file mode 100644 index 00000000..000e1ba6 --- /dev/null +++ b/src/spdk/test/unit/lib/util/crc32_ieee.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = crc32_ieee_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/util/crc32_ieee.c/crc32_ieee_ut.c b/src/spdk/test/unit/lib/util/crc32_ieee.c/crc32_ieee_ut.c new file mode 100644 index 00000000..9a076998 --- /dev/null +++ b/src/spdk/test/unit/lib/util/crc32_ieee.c/crc32_ieee_ut.c @@ -0,0 +1,83 @@ +/*- + * 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 "util/crc32.c" +#include "util/crc32_ieee.c" + +static void +test_crc32_ieee(void) +{ + uint32_t crc; + char buf[] = "Hello world!"; + + crc = 0xFFFFFFFFu; + crc = spdk_crc32_ieee_update(buf, strlen(buf), crc); + crc ^= 0xFFFFFFFFu; + CU_ASSERT(crc == 0x1b851995); +} + +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("crc32_ieee", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_crc32_ieee", test_crc32_ieee) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/util/crc32c.c/.gitignore b/src/spdk/test/unit/lib/util/crc32c.c/.gitignore new file mode 100644 index 00000000..55bedec7 --- /dev/null +++ b/src/spdk/test/unit/lib/util/crc32c.c/.gitignore @@ -0,0 +1 @@ +crc32c_ut diff --git a/src/spdk/test/unit/lib/util/crc32c.c/Makefile b/src/spdk/test/unit/lib/util/crc32c.c/Makefile new file mode 100644 index 00000000..eba81722 --- /dev/null +++ b/src/spdk/test/unit/lib/util/crc32c.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = crc32c_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/util/crc32c.c/crc32c_ut.c b/src/spdk/test/unit/lib/util/crc32c.c/crc32c_ut.c new file mode 100644 index 00000000..49b2f852 --- /dev/null +++ b/src/spdk/test/unit/lib/util/crc32c.c/crc32c_ut.c @@ -0,0 +1,154 @@ +/*- + * 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 "util/crc32.c" +#include "util/crc32c.c" + +static void +test_crc32c(void) +{ + uint32_t crc; + char buf[1024]; + + /* Verify a string's CRC32-C value against the known correct result. */ + snprintf(buf, sizeof(buf), "%s", "Hello world!"); + crc = 0xFFFFFFFFu; + crc = spdk_crc32c_update(buf, strlen(buf), crc); + crc ^= 0xFFFFFFFFu; + CU_ASSERT(crc == 0x7b98e751); + + /* + * The main loop of the optimized CRC32-C implementation processes data in 8-byte blocks, + * followed by a loop to handle the 0-7 trailing bytes. + * Test all buffer sizes from 0 to 7 in order to hit all possible trailing byte counts. + */ + + /* 0-byte buffer should not modify CRC at all, so final result should be ~0 ^ ~0 == 0 */ + snprintf(buf, sizeof(buf), "%s", ""); + crc = 0xFFFFFFFFu; + crc = spdk_crc32c_update(buf, strlen(buf), crc); + crc ^= 0xFFFFFFFFu; + CU_ASSERT(crc == 0); + + /* 1-byte buffer */ + snprintf(buf, sizeof(buf), "%s", "1"); + crc = 0xFFFFFFFFu; + crc = spdk_crc32c_update(buf, strlen(buf), crc); + crc ^= 0xFFFFFFFFu; + CU_ASSERT(crc == 0x90F599E3); + + /* 2-byte buffer */ + snprintf(buf, sizeof(buf), "%s", "12"); + crc = 0xFFFFFFFFu; + crc = spdk_crc32c_update(buf, strlen(buf), crc); + crc ^= 0xFFFFFFFFu; + CU_ASSERT(crc == 0x7355C460); + + /* 3-byte buffer */ + snprintf(buf, sizeof(buf), "%s", "123"); + crc = 0xFFFFFFFFu; + crc = spdk_crc32c_update(buf, strlen(buf), crc); + crc ^= 0xFFFFFFFFu; + CU_ASSERT(crc == 0x107B2FB2); + + /* 4-byte buffer */ + snprintf(buf, sizeof(buf), "%s", "1234"); + crc = 0xFFFFFFFFu; + crc = spdk_crc32c_update(buf, strlen(buf), crc); + crc ^= 0xFFFFFFFFu; + CU_ASSERT(crc == 0xF63AF4EE); + + /* 5-byte buffer */ + snprintf(buf, sizeof(buf), "%s", "12345"); + crc = 0xFFFFFFFFu; + crc = spdk_crc32c_update(buf, strlen(buf), crc); + crc ^= 0xFFFFFFFFu; + CU_ASSERT(crc == 0x18D12335); + + /* 6-byte buffer */ + snprintf(buf, sizeof(buf), "%s", "123456"); + crc = 0xFFFFFFFFu; + crc = spdk_crc32c_update(buf, strlen(buf), crc); + crc ^= 0xFFFFFFFFu; + CU_ASSERT(crc == 0x41357186); + + /* 7-byte buffer */ + snprintf(buf, sizeof(buf), "%s", "1234567"); + crc = 0xFFFFFFFFu; + crc = spdk_crc32c_update(buf, strlen(buf), crc); + crc ^= 0xFFFFFFFFu; + CU_ASSERT(crc == 0x124297EA); + + /* Test a buffer of exactly 8 bytes (one block in the main CRC32-C loop). */ + snprintf(buf, sizeof(buf), "%s", "12345678"); + crc = 0xFFFFFFFFu; + crc = spdk_crc32c_update(buf, strlen(buf), crc); + crc ^= 0xFFFFFFFFu; + CU_ASSERT(crc == 0x6087809A); +} + +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("crc32c", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_crc32c", test_crc32c) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/util/string.c/.gitignore b/src/spdk/test/unit/lib/util/string.c/.gitignore new file mode 100644 index 00000000..5d85d4d9 --- /dev/null +++ b/src/spdk/test/unit/lib/util/string.c/.gitignore @@ -0,0 +1 @@ +string_ut diff --git a/src/spdk/test/unit/lib/util/string.c/Makefile b/src/spdk/test/unit/lib/util/string.c/Makefile new file mode 100644 index 00000000..8ee11909 --- /dev/null +++ b/src/spdk/test/unit/lib/util/string.c/Makefile @@ -0,0 +1,40 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +TEST_FILE = string_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/util/string.c/string_ut.c b/src/spdk/test/unit/lib/util/string.c/string_ut.c new file mode 100644 index 00000000..2ca32cbe --- /dev/null +++ b/src/spdk/test/unit/lib/util/string.c/string_ut.c @@ -0,0 +1,237 @@ +/*- + * 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 "util/string.c" + +static void +test_parse_ip_addr(void) +{ + int rc; + char *host; + char *port; + char ip[255]; + + /* IPv4 */ + snprintf(ip, 255, "%s", "192.168.0.1"); + rc = spdk_parse_ip_addr(ip, &host, &port); + CU_ASSERT_EQUAL(rc, 0); + SPDK_CU_ASSERT_FATAL(host != NULL); + CU_ASSERT(strcmp(host, "192.168.0.1") == 0); + CU_ASSERT_EQUAL(strlen(host), 11); + CU_ASSERT_EQUAL(port, NULL); + + /* IPv4 with port */ + snprintf(ip, 255, "%s", "123.456.789.0:5520"); + rc = spdk_parse_ip_addr(ip, &host, &port); + CU_ASSERT_EQUAL(rc, 0); + SPDK_CU_ASSERT_FATAL(host != NULL); + CU_ASSERT(strcmp(host, "123.456.789.0") == 0); + CU_ASSERT_EQUAL(strlen(host), 13); + SPDK_CU_ASSERT_FATAL(port != NULL); + CU_ASSERT(strcmp(port, "5520") == 0); + CU_ASSERT_EQUAL(strlen(port), 4); + + /* IPv6 */ + snprintf(ip, 255, "%s", "[2001:db8:85a3:8d3:1319:8a2e:370:7348]"); + rc = spdk_parse_ip_addr(ip, &host, &port); + CU_ASSERT_EQUAL(rc, 0); + SPDK_CU_ASSERT_FATAL(host != NULL); + CU_ASSERT(strcmp(host, "2001:db8:85a3:8d3:1319:8a2e:370:7348") == 0); + CU_ASSERT_EQUAL(strlen(host), 36); + CU_ASSERT_EQUAL(port, NULL); + + /* IPv6 with port */ + snprintf(ip, 255, "%s", "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443"); + rc = spdk_parse_ip_addr(ip, &host, &port); + CU_ASSERT_EQUAL(rc, 0); + SPDK_CU_ASSERT_FATAL(host != NULL); + CU_ASSERT(strcmp(host, "2001:db8:85a3:8d3:1319:8a2e:370:7348") == 0); + CU_ASSERT_EQUAL(strlen(host), 36); + SPDK_CU_ASSERT_FATAL(port != NULL); + CU_ASSERT(strcmp(port, "443") == 0); + CU_ASSERT_EQUAL(strlen(port), 3); + + /* IPv6 dangling colon */ + snprintf(ip, 255, "%s", "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:"); + rc = spdk_parse_ip_addr(ip, &host, &port); + CU_ASSERT_EQUAL(rc, 0); + SPDK_CU_ASSERT_FATAL(host != NULL); + CU_ASSERT(strcmp(host, "2001:db8:85a3:8d3:1319:8a2e:370:7348") == 0); + CU_ASSERT_EQUAL(strlen(host), 36); + CU_ASSERT_EQUAL(port, NULL); +} + +static void +test_str_chomp(void) +{ + char s[1024]; + + /* One \n newline */ + snprintf(s, sizeof(s), "%s", "hello world\n"); + CU_ASSERT(spdk_str_chomp(s) == 1); + CU_ASSERT(strcmp(s, "hello world") == 0); + + /* One \r\n newline */ + snprintf(s, sizeof(s), "%s", "hello world\r\n"); + CU_ASSERT(spdk_str_chomp(s) == 2); + CU_ASSERT(strcmp(s, "hello world") == 0); + + /* No newlines */ + snprintf(s, sizeof(s), "%s", "hello world"); + CU_ASSERT(spdk_str_chomp(s) == 0); + CU_ASSERT(strcmp(s, "hello world") == 0); + + /* Two newlines */ + snprintf(s, sizeof(s), "%s", "hello world\n\n"); + CU_ASSERT(spdk_str_chomp(s) == 2); + CU_ASSERT(strcmp(s, "hello world") == 0); + + /* Empty string */ + snprintf(s, sizeof(s), "%s", ""); + CU_ASSERT(spdk_str_chomp(s) == 0); + CU_ASSERT(strcmp(s, "") == 0); + + /* One-character string with only \n */ + snprintf(s, sizeof(s), "%s", "\n"); + CU_ASSERT(spdk_str_chomp(s) == 1); + CU_ASSERT(strcmp(s, "") == 0); + + /* One-character string without a newline */ + snprintf(s, sizeof(s), "%s", "a"); + CU_ASSERT(spdk_str_chomp(s) == 0); + CU_ASSERT(strcmp(s, "a") == 0); +} + +static void +test_parse_capacity(void) +{ + char str[128]; + uint64_t cap; + int rc; + bool has_prefix = true; + + rc = spdk_parse_capacity("472", &cap, &has_prefix); + CU_ASSERT(rc == 0); + CU_ASSERT(cap == 472); + CU_ASSERT(has_prefix == false); + + snprintf(str, sizeof(str), "%"PRIu64, UINT64_MAX); + rc = spdk_parse_capacity(str, &cap, &has_prefix); + CU_ASSERT(rc == 0); + CU_ASSERT(cap == UINT64_MAX); + CU_ASSERT(has_prefix == false); + + rc = spdk_parse_capacity("12k", &cap, &has_prefix); + CU_ASSERT(rc == 0); + CU_ASSERT(cap == 12 * 1024); + CU_ASSERT(has_prefix == true); + + rc = spdk_parse_capacity("12K", &cap, &has_prefix); + CU_ASSERT(rc == 0); + CU_ASSERT(cap == 12 * 1024); + CU_ASSERT(has_prefix == true); + + rc = spdk_parse_capacity("12KB", &cap, &has_prefix); + CU_ASSERT(rc == 0); + CU_ASSERT(cap == 12 * 1024); + CU_ASSERT(has_prefix == true); + + rc = spdk_parse_capacity("100M", &cap, &has_prefix); + CU_ASSERT(rc == 0); + CU_ASSERT(cap == 100 * 1024 * 1024); + CU_ASSERT(has_prefix == true); + + rc = spdk_parse_capacity("128M", &cap, &has_prefix); + CU_ASSERT(rc == 0); + CU_ASSERT(cap == 128 * 1024 * 1024); + CU_ASSERT(has_prefix == true); + + rc = spdk_parse_capacity("4G", &cap, &has_prefix); + CU_ASSERT(rc == 0); + CU_ASSERT(cap == 4ULL * 1024 * 1024 * 1024); + CU_ASSERT(has_prefix == true); + + rc = spdk_parse_capacity("100M 512k", &cap, &has_prefix); + CU_ASSERT(rc == 0); + CU_ASSERT(cap == 100ULL * 1024 * 1024); + + rc = spdk_parse_capacity("12k8K", &cap, &has_prefix); + CU_ASSERT(rc == 0); + CU_ASSERT(cap == 12 * 1024); + CU_ASSERT(has_prefix == true); + + /* Non-number */ + rc = spdk_parse_capacity("G", &cap, &has_prefix); + CU_ASSERT(rc != 0); + + rc = spdk_parse_capacity("darsto", &cap, &has_prefix); + CU_ASSERT(rc != 0); +} + +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("string", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_parse_ip_addr", test_parse_ip_addr) == NULL || + CU_add_test(suite, "test_str_chomp", test_str_chomp) == NULL || + CU_add_test(suite, "test_parse_capacity", test_parse_capacity) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/lib/vhost/Makefile b/src/spdk/test/unit/lib/vhost/Makefile new file mode 100644 index 00000000..0f569f6d --- /dev/null +++ b/src/spdk/test/unit/lib/vhost/Makefile @@ -0,0 +1,44 @@ +# +# 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)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = vhost.c + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/unit/lib/vhost/test_vhost.c b/src/spdk/test/unit/lib/vhost/test_vhost.c new file mode 100644 index 00000000..437e1230 --- /dev/null +++ b/src/spdk/test/unit/lib/vhost/test_vhost.c @@ -0,0 +1,121 @@ +/*- + * BSD LICENSE + * + * Copyright(c) Intel Corporation. All rights reserved. + * 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 "CUnit/Basic.h" +#include "spdk_cunit.h" +#include "spdk_internal/mock.h" +#include "spdk/thread.h" + +#include "unit/lib/json_mock.c" + +struct spdk_conf_section { + struct spdk_conf_section *next; + char *name; + int num; + struct spdk_conf_item *item; +}; + +DEFINE_STUB(spdk_vhost_vq_get_desc, int, (struct spdk_vhost_dev *vdev, + struct spdk_vhost_virtqueue *vq, uint16_t req_idx, struct vring_desc **desc, + struct vring_desc **desc_table, uint32_t *desc_table_size), 0); +DEFINE_STUB(spdk_vhost_vring_desc_is_wr, bool, (struct vring_desc *cur_desc), false); +DEFINE_STUB(spdk_vhost_vring_desc_to_iov, int, (struct spdk_vhost_dev *vdev, struct iovec *iov, + uint16_t *iov_index, const struct vring_desc *desc), 0); +DEFINE_STUB_V(spdk_vhost_vq_used_ring_enqueue, (struct spdk_vhost_dev *vdev, + struct spdk_vhost_virtqueue *vq, uint16_t id, uint32_t len)); +DEFINE_STUB(spdk_vhost_vring_desc_get_next, int, (struct vring_desc **desc, + struct vring_desc *desc_table, uint32_t desc_table_size), 0); +DEFINE_STUB(spdk_vhost_vq_avail_ring_get, uint16_t, (struct spdk_vhost_virtqueue *vq, + uint16_t *reqs, uint16_t reqs_len), 0); +DEFINE_STUB(spdk_vhost_vq_used_signal, int, (struct spdk_vhost_dev *vdev, + struct spdk_vhost_virtqueue *virtqueue), 0); +DEFINE_STUB_V(spdk_vhost_dev_used_signal, (struct spdk_vhost_dev *vdev)); +DEFINE_STUB_V(spdk_vhost_dev_mem_register, (struct spdk_vhost_dev *vdev)); +DEFINE_STUB_P(spdk_vhost_dev_find, struct spdk_vhost_dev, (const char *ctrlr_name), {0}); +DEFINE_STUB_P(spdk_conf_first_section, struct spdk_conf_section, (struct spdk_conf *cp), {0}); +DEFINE_STUB(spdk_conf_section_match_prefix, bool, (const struct spdk_conf_section *sp, + const char *name_prefix), false); +DEFINE_STUB_P(spdk_conf_next_section, struct spdk_conf_section, (struct spdk_conf_section *sp), {0}); +DEFINE_STUB_P(spdk_conf_section_get_name, const char, (const struct spdk_conf_section *sp), {0}); +DEFINE_STUB(spdk_conf_section_get_boolval, bool, (struct spdk_conf_section *sp, const char *key, + bool default_val), false); +DEFINE_STUB_P(spdk_conf_section_get_nmval, char, (struct spdk_conf_section *sp, const char *key, + int idx1, int idx2), {0}); +DEFINE_STUB_V(spdk_vhost_dev_mem_unregister, (struct spdk_vhost_dev *vdev)); +DEFINE_STUB(spdk_vhost_event_send, int, (struct spdk_vhost_dev *vdev, spdk_vhost_event_fn cb_fn, + void *arg, unsigned timeout_sec, const char *errmsg), 0); +DEFINE_STUB(spdk_env_get_socket_id, uint32_t, (uint32_t core), 0); +DEFINE_STUB_V(spdk_vhost_dev_backend_event_done, (void *event_ctx, int response)); +DEFINE_STUB_V(spdk_vhost_lock, (void)); +DEFINE_STUB_V(spdk_vhost_unlock, (void)); +DEFINE_STUB(spdk_env_get_current_core, uint32_t, (void), 0); +DEFINE_STUB_V(spdk_vhost_call_external_event, (const char *ctrlr_name, spdk_vhost_event_fn fn, + void *arg)); +DEFINE_STUB(spdk_vhost_vring_desc_has_next, bool, (struct vring_desc *cur_desc), false); +DEFINE_STUB_VP(spdk_vhost_gpa_to_vva, (struct spdk_vhost_dev *vdev, uint64_t addr, uint64_t len), +{0}); +DEFINE_STUB(spdk_scsi_dev_get_id, int, (const struct spdk_scsi_dev *dev), {0}); + +/* This sets spdk_vhost_dev_unregister to either to fail or success */ +DEFINE_STUB(spdk_vhost_dev_unregister_fail, bool, (void), false); +/* This sets spdk_vhost_dev_register to either to fail or success */ +DEFINE_STUB(spdk_vhost_dev_register_fail, bool, (void), false); + +static struct spdk_vhost_dev *g_spdk_vhost_device; +int +spdk_vhost_dev_register(struct spdk_vhost_dev *vdev, const char *name, const char *mask_str, + const struct spdk_vhost_dev_backend *backend) +{ + if (spdk_vhost_dev_register_fail()) { + return -1; + } + + vdev->backend = backend; + g_spdk_vhost_device = vdev; + vdev->registered = true; + return 0; +} + +int +spdk_vhost_dev_unregister(struct spdk_vhost_dev *vdev) +{ + if (spdk_vhost_dev_unregister_fail()) { + return -1; + } + + free(vdev->name); + g_spdk_vhost_device = NULL; + return 0; +} diff --git a/src/spdk/test/unit/lib/vhost/vhost.c/.gitignore b/src/spdk/test/unit/lib/vhost/vhost.c/.gitignore new file mode 100644 index 00000000..16cead8f --- /dev/null +++ b/src/spdk/test/unit/lib/vhost/vhost.c/.gitignore @@ -0,0 +1 @@ +vhost_ut diff --git a/src/spdk/test/unit/lib/vhost/vhost.c/Makefile b/src/spdk/test/unit/lib/vhost/vhost.c/Makefile new file mode 100644 index 00000000..3c30f5a8 --- /dev/null +++ b/src/spdk/test/unit/lib/vhost/vhost.c/Makefile @@ -0,0 +1,42 @@ +# +# 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)/../../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +CFLAGS += -I$(SPDK_ROOT_DIR)/lib/vhost/rte_vhost +CFLAGS += $(ENV_CFLAGS) +TEST_FILE = vhost_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/vhost/vhost.c/vhost_ut.c b/src/spdk/test/unit/lib/vhost/vhost.c/vhost_ut.c new file mode 100644 index 00000000..49e879ed --- /dev/null +++ b/src/spdk/test/unit/lib/vhost/vhost.c/vhost_ut.c @@ -0,0 +1,364 @@ +/*- + * 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 "CUnit/Basic.h" +#include "spdk_cunit.h" +#include "spdk/thread.h" +#include "spdk_internal/mock.h" +#include "common/lib/test_env.c" +#include "unit/lib/json_mock.c" + +#include "vhost/vhost.c" + +DEFINE_STUB(rte_vhost_driver_unregister, int, (const char *path), 0); +DEFINE_STUB(spdk_event_allocate, struct spdk_event *, + (uint32_t lcore, spdk_event_fn fn, void *arg1, void *arg2), NULL); +DEFINE_STUB(spdk_mem_register, int, (void *vaddr, size_t len), 0); +DEFINE_STUB(spdk_mem_unregister, int, (void *vaddr, size_t len), 0); + +static struct spdk_cpuset *g_app_core_mask; +struct spdk_cpuset *spdk_app_get_core_mask(void) +{ + if (g_app_core_mask == NULL) { + g_app_core_mask = spdk_cpuset_alloc(); + spdk_cpuset_set_cpu(g_app_core_mask, 0, true); + } + return g_app_core_mask; +} + +int +spdk_app_parse_core_mask(const char *mask, struct spdk_cpuset *cpumask) +{ + int ret; + struct spdk_cpuset *validmask; + + ret = spdk_cpuset_parse(cpumask, mask); + if (ret < 0) { + return ret; + } + + validmask = spdk_app_get_core_mask(); + spdk_cpuset_and(cpumask, validmask); + + return 0; +} + +DEFINE_STUB(spdk_env_get_first_core, uint32_t, (void), 0); +DEFINE_STUB(spdk_env_get_next_core, uint32_t, (uint32_t prev_core), 0); +DEFINE_STUB(spdk_env_get_last_core, uint32_t, (void), 0); +DEFINE_STUB_V(spdk_app_stop, (int rc)); +DEFINE_STUB_V(spdk_event_call, (struct spdk_event *event)); +DEFINE_STUB(spdk_poller_register, struct spdk_poller *, (spdk_poller_fn fn, void *arg, + uint64_t period_microseconds), NULL); +DEFINE_STUB_V(spdk_poller_unregister, (struct spdk_poller **ppoller)); +DEFINE_STUB(spdk_iommu_mem_unregister, int, (uint64_t addr, uint64_t len), 0); +DEFINE_STUB(rte_vhost_get_mem_table, int, (int vid, struct rte_vhost_memory **mem), 0); +DEFINE_STUB(rte_vhost_get_negotiated_features, int, (int vid, uint64_t *features), 0); +DEFINE_STUB(rte_vhost_get_vhost_vring, int, + (int vid, uint16_t vring_idx, struct rte_vhost_vring *vring), 0); +DEFINE_STUB(rte_vhost_enable_guest_notification, int, + (int vid, uint16_t queue_id, int enable), 0); +DEFINE_STUB(rte_vhost_get_ifname, int, (int vid, char *buf, size_t len), 0); +DEFINE_STUB(rte_vhost_get_vring_num, uint16_t, (int vid), 0); +DEFINE_STUB(rte_vhost_driver_start, int, (const char *name), 0); +DEFINE_STUB(rte_vhost_driver_callback_register, int, + (const char *path, struct vhost_device_ops const *const ops), 0); +DEFINE_STUB(rte_vhost_driver_disable_features, int, (const char *path, uint64_t features), 0); +DEFINE_STUB(rte_vhost_driver_set_features, int, (const char *path, uint64_t features), 0); +DEFINE_STUB(rte_vhost_driver_register, int, (const char *path, uint64_t flags), 0); +DEFINE_STUB_V(rte_vhost_log_used_vring, (int vid, uint16_t vring_idx, uint64_t offset, + uint64_t len)); +DEFINE_STUB_V(rte_vhost_log_write, (int vid, uint64_t addr, uint64_t len)); +DEFINE_STUB(spdk_vhost_scsi_controller_construct, int, (void), 0); +DEFINE_STUB(spdk_vhost_blk_controller_construct, int, (void), 0); +DEFINE_STUB(spdk_vhost_nvme_admin_passthrough, int, (int vid, void *cmd, void *cqe, void *buf), 0); +DEFINE_STUB(spdk_vhost_nvme_set_cq_call, int, (int vid, uint16_t qid, int fd), 0); +DEFINE_STUB(spdk_vhost_nvme_get_cap, int, (int vid, uint64_t *cap), 0); +DEFINE_STUB(spdk_vhost_nvme_controller_construct, int, (void), 0); +DEFINE_STUB(rte_vhost_set_vhost_vring_last_idx, int, + (int vid, uint16_t vring_idx, uint16_t last_avail_idx, uint16_t last_used_idx), 0); +DEFINE_STUB(spdk_env_get_current_core, uint32_t, (void), 0); + +void * +spdk_call_unaffinitized(void *cb(void *arg), void *arg) +{ + return cb(arg); +} + +static struct spdk_vhost_dev_backend g_vdev_backend; + +static int +test_setup(void) +{ + return 0; +} + +static int +alloc_vdev(struct spdk_vhost_dev **vdev_p, const char *name, const char *cpumask) +{ + struct spdk_vhost_dev *vdev = NULL; + int rc; + + /* spdk_vhost_dev must be allocated on a cache line boundary. */ + rc = posix_memalign((void **)&vdev, 64, sizeof(*vdev)); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(vdev != NULL); + memset(vdev, 0, sizeof(*vdev)); + rc = spdk_vhost_dev_register(vdev, name, cpumask, &g_vdev_backend); + if (rc == 0) { + *vdev_p = vdev; + } else { + free(vdev); + *vdev_p = NULL; + } + + return rc; +} + +static void +start_vdev(struct spdk_vhost_dev *vdev) +{ + vdev->vid = 0; + vdev->lcore = 0; + vdev->mem = calloc(1, sizeof(*vdev->mem) + 2 * sizeof(struct rte_vhost_mem_region)); + SPDK_CU_ASSERT_FATAL(vdev->mem != NULL); + vdev->mem->nregions = 2; + vdev->mem->regions[0].guest_phys_addr = 0; + vdev->mem->regions[0].size = 0x400000; /* 4 MB */ + vdev->mem->regions[0].host_user_addr = 0x1000000; + vdev->mem->regions[1].guest_phys_addr = 0x400000; + vdev->mem->regions[1].size = 0x400000; /* 4 MB */ + vdev->mem->regions[1].host_user_addr = 0x2000000; +} + +static void +stop_vdev(struct spdk_vhost_dev *vdev) +{ + free(vdev->mem); + vdev->mem = NULL; + vdev->vid = -1; +} + +static void +cleanup_vdev(struct spdk_vhost_dev *vdev) +{ + stop_vdev(vdev); + spdk_vhost_dev_unregister(vdev); + free(vdev); +} + +static void +desc_to_iov_test(void) +{ + struct spdk_vhost_dev *vdev; + struct iovec iov[SPDK_VHOST_IOVS_MAX]; + uint16_t iov_index; + struct vring_desc desc; + int rc; + + rc = alloc_vdev(&vdev, "vdev_name_0", "0x1"); + SPDK_CU_ASSERT_FATAL(rc == 0 && vdev); + start_vdev(vdev); + + /* Test simple case where iov falls fully within a 2MB page. */ + desc.addr = 0x110000; + desc.len = 0x1000; + iov_index = 0; + rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc); + CU_ASSERT(rc == 0); + CU_ASSERT(iov_index == 1); + CU_ASSERT(iov[0].iov_base == (void *)0x1110000); + CU_ASSERT(iov[0].iov_len == 0x1000); + /* + * Always memset the iov to ensure each test validates data written by its call + * to the function under test. + */ + memset(iov, 0, sizeof(iov)); + + /* Same test, but ensure it respects the non-zero starting iov_index. */ + iov_index = SPDK_VHOST_IOVS_MAX - 1; + rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc); + CU_ASSERT(rc == 0); + CU_ASSERT(iov_index == SPDK_VHOST_IOVS_MAX); + CU_ASSERT(iov[SPDK_VHOST_IOVS_MAX - 1].iov_base == (void *)0x1110000); + CU_ASSERT(iov[SPDK_VHOST_IOVS_MAX - 1].iov_len == 0x1000); + memset(iov, 0, sizeof(iov)); + + /* Test for failure if iov_index already equals SPDK_VHOST_IOVS_MAX. */ + iov_index = SPDK_VHOST_IOVS_MAX; + rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc); + CU_ASSERT(rc != 0); + memset(iov, 0, sizeof(iov)); + + /* Test case where iov spans a 2MB boundary, but does not span a vhost memory region. */ + desc.addr = 0x1F0000; + desc.len = 0x20000; + iov_index = 0; + rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc); + CU_ASSERT(rc == 0); + CU_ASSERT(iov_index == 1); + CU_ASSERT(iov[0].iov_base == (void *)0x11F0000); + CU_ASSERT(iov[0].iov_len == 0x20000); + memset(iov, 0, sizeof(iov)); + + /* Same test, but ensure it respects the non-zero starting iov_index. */ + iov_index = SPDK_VHOST_IOVS_MAX - 1; + rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc); + CU_ASSERT(rc == 0); + CU_ASSERT(iov_index == SPDK_VHOST_IOVS_MAX); + CU_ASSERT(iov[SPDK_VHOST_IOVS_MAX - 1].iov_base == (void *)0x11F0000); + CU_ASSERT(iov[SPDK_VHOST_IOVS_MAX - 1].iov_len == 0x20000); + memset(iov, 0, sizeof(iov)); + + /* Test case where iov spans a vhost memory region. */ + desc.addr = 0x3F0000; + desc.len = 0x20000; + iov_index = 0; + rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc); + CU_ASSERT(rc == 0); + CU_ASSERT(iov_index == 2); + CU_ASSERT(iov[0].iov_base == (void *)0x13F0000); + CU_ASSERT(iov[0].iov_len == 0x10000); + CU_ASSERT(iov[1].iov_base == (void *)0x2000000); + CU_ASSERT(iov[1].iov_len == 0x10000); + memset(iov, 0, sizeof(iov)); + + cleanup_vdev(vdev); + + CU_ASSERT(true); +} + +static void +create_controller_test(void) +{ + struct spdk_vhost_dev *vdev, *vdev2; + int ret; + char long_name[PATH_MAX]; + + /* NOTE: spdk_app_get_core_mask stub always sets coremask 0x01 */ + + /* Create device with no name */ + ret = alloc_vdev(&vdev, NULL, "0x1"); + CU_ASSERT(ret != 0); + + /* Create device with incorrect cpumask */ + ret = alloc_vdev(&vdev, "vdev_name_0", "0x2"); + CU_ASSERT(ret != 0); + + /* Create device with too long name and path */ + memset(long_name, 'x', sizeof(long_name)); + long_name[PATH_MAX - 1] = 0; + snprintf(dev_dirname, sizeof(dev_dirname), "some_path/"); + ret = alloc_vdev(&vdev, long_name, "0x1"); + CU_ASSERT(ret != 0); + dev_dirname[0] = 0; + + /* Create device when device name is already taken */ + ret = alloc_vdev(&vdev, "vdev_name_0", "0x1"); + SPDK_CU_ASSERT_FATAL(ret == 0 && vdev); + ret = alloc_vdev(&vdev2, "vdev_name_0", "0x1"); + CU_ASSERT(ret != 0); + cleanup_vdev(vdev); +} + +static void +dev_find_by_vid_test(void) +{ + struct spdk_vhost_dev *vdev, *tmp; + int rc; + + rc = alloc_vdev(&vdev, "vdev_name_0", "0x1"); + SPDK_CU_ASSERT_FATAL(rc == 0 && vdev); + + tmp = spdk_vhost_dev_find_by_vid(vdev->vid); + CU_ASSERT(tmp == vdev); + + /* Search for a device with incorrect vid */ + tmp = spdk_vhost_dev_find_by_vid(vdev->vid + 0xFF); + CU_ASSERT(tmp == NULL); + + cleanup_vdev(vdev); +} + +static void +remove_controller_test(void) +{ + struct spdk_vhost_dev *vdev; + int ret; + + ret = alloc_vdev(&vdev, "vdev_name_0", "0x1"); + SPDK_CU_ASSERT_FATAL(ret == 0 && vdev); + + /* Remove device when controller is in use */ + start_vdev(vdev); + ret = spdk_vhost_dev_unregister(vdev); + CU_ASSERT(ret != 0); + + cleanup_vdev(vdev); +} + +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("vhost_suite", test_setup, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "desc_to_iov", desc_to_iov_test) == NULL || + CU_add_test(suite, "create_controller", create_controller_test) == NULL || + CU_add_test(suite, "dev_find_by_vid", dev_find_by_vid_test) == NULL || + CU_add_test(suite, "remove_controller", remove_controller_test) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/src/spdk/test/unit/unittest.sh b/src/spdk/test/unit/unittest.sh new file mode 100755 index 00000000..6e79b381 --- /dev/null +++ b/src/spdk/test/unit/unittest.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash +# +# Environment variables: +# $valgrind Specify the valgrind command line, if not +# then a default command line is used + +set -xe + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $(dirname $0)/../..) + +cd "$rootdir" + + +# if ASAN is enabled, use it. If not use valgrind if installed but allow +# the env variable to override the default shown below. +if [ -z ${valgrind+x} ]; then + if grep -q '#undef SPDK_CONFIG_ASAN' $rootdir/include/spdk/config.h && hash valgrind; then + valgrind='valgrind --leak-check=full --error-exitcode=2' + else + valgrind='' + fi +fi + +# setup local unit test coverage if cov is available +if hash lcov && grep -q '#define SPDK_CONFIG_COVERAGE 1' $rootdir/include/spdk/config.h; then + cov_avail="yes" +else + cov_avail="no" +fi +if [ "$cov_avail" = "yes" ]; then + # set unit test output dir if not specified in env var + if [ -z ${UT_COVERAGE+x} ]; then + UT_COVERAGE="ut_coverage" + fi + mkdir -p $UT_COVERAGE + export LCOV_OPTS=" + --rc lcov_branch_coverage=1 + --rc lcov_function_coverage=1 + --rc genhtml_branch_coverage=1 + --rc genhtml_function_coverage=1 + --rc genhtml_legend=1 + --rc geninfo_all_blocks=1 + " + export LCOV="lcov $LCOV_OPTS --no-external" + # zero out coverage data + $LCOV -q -c -i -d . -t "Baseline" -o $UT_COVERAGE/ut_cov_base.info +fi +$valgrind $testdir/include/spdk/histogram_data.h/histogram_ut + +$valgrind $testdir/lib/bdev/bdev.c/bdev_ut +$valgrind $testdir/lib/bdev/bdev_raid.c/bdev_raid_ut +$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 + +if grep -q '#define SPDK_CONFIG_CRYPTO 1' $rootdir/include/spdk/config.h; then + $valgrind $testdir/lib/bdev/crypto.c/crypto_ut +fi + +if grep -q '#define SPDK_CONFIG_PMDK 1' $rootdir/include/spdk/config.h; then + $valgrind $testdir/lib/bdev/pmem/bdev_pmem_ut +fi + +$valgrind $testdir/lib/bdev/mt/bdev.c/bdev_ut + +$valgrind $testdir/lib/blob/blob.c/blob_ut +$valgrind $testdir/lib/blobfs/tree.c/tree_ut + +$valgrind $testdir/lib/blobfs/blobfs_async_ut/blobfs_async_ut +# blobfs_sync_ut hangs when run under valgrind, so don't use $valgrind +$testdir/lib/blobfs/blobfs_sync_ut/blobfs_sync_ut + +$valgrind $testdir/lib/event/subsystem.c/subsystem_ut +$valgrind $testdir/lib/event/app.c/app_ut + +$valgrind $testdir/lib/sock/sock.c/sock_ut + +$valgrind $testdir/lib/nvme/nvme.c/nvme_ut +$valgrind $testdir/lib/nvme/nvme_ctrlr.c/nvme_ctrlr_ut +$valgrind $testdir/lib/nvme/nvme_ctrlr_cmd.c/nvme_ctrlr_cmd_ut +$valgrind $testdir/lib/nvme/nvme_ctrlr_ocssd_cmd.c/nvme_ctrlr_ocssd_cmd_ut +$valgrind $testdir/lib/nvme/nvme_ns.c/nvme_ns_ut +$valgrind $testdir/lib/nvme/nvme_ns_cmd.c/nvme_ns_cmd_ut +$valgrind $testdir/lib/nvme/nvme_ns_ocssd_cmd.c/nvme_ns_ocssd_cmd_ut +$valgrind $testdir/lib/nvme/nvme_qpair.c/nvme_qpair_ut +$valgrind $testdir/lib/nvme/nvme_pcie.c/nvme_pcie_ut +$valgrind $testdir/lib/nvme/nvme_quirks.c/nvme_quirks_ut +if grep -q '#define SPDK_CONFIG_RDMA 1' $rootdir/config.h; then + $valgrind $testdir/lib/nvme/nvme_rdma.c/nvme_rdma_ut +fi + +$valgrind $testdir/lib/ioat/ioat.c/ioat_ut + +$valgrind $testdir/lib/json/json_parse.c/json_parse_ut +$valgrind $testdir/lib/json/json_util.c/json_util_ut +$valgrind $testdir/lib/json/json_write.c/json_write_ut + +$valgrind $testdir/lib/jsonrpc/jsonrpc_server.c/jsonrpc_server_ut + +$valgrind $testdir/lib/log/log.c/log_ut + +$valgrind $testdir/lib/nvmf/ctrlr.c/ctrlr_ut +$valgrind $testdir/lib/nvmf/ctrlr_bdev.c/ctrlr_bdev_ut +$valgrind $testdir/lib/nvmf/ctrlr_discovery.c/ctrlr_discovery_ut +$valgrind $testdir/lib/nvmf/request.c/request_ut +$valgrind $testdir/lib/nvmf/subsystem.c/subsystem_ut + +$valgrind $testdir/lib/scsi/dev.c/dev_ut +$valgrind $testdir/lib/scsi/lun.c/lun_ut +$valgrind $testdir/lib/scsi/scsi.c/scsi_ut +$valgrind $testdir/lib/scsi/scsi_bdev.c/scsi_bdev_ut + +$valgrind $testdir/lib/lvol/lvol.c/lvol_ut + +$valgrind $testdir/lib/iscsi/conn.c/conn_ut +$valgrind $testdir/lib/iscsi/param.c/param_ut +$valgrind $testdir/lib/iscsi/tgt_node.c/tgt_node_ut $testdir/lib/iscsi/tgt_node.c/tgt_node.conf +$valgrind $testdir/lib/iscsi/iscsi.c/iscsi_ut +$valgrind $testdir/lib/iscsi/init_grp.c/init_grp_ut $testdir/lib/iscsi/init_grp.c/init_grp.conf +$valgrind $testdir/lib/iscsi/portal_grp.c/portal_grp_ut $testdir/lib/iscsi/portal_grp.c/portal_grp.conf + +$valgrind $testdir/lib/thread/thread.c/thread_ut + +$valgrind $testdir/lib/util/base64.c/base64_ut +$valgrind $testdir/lib/util/bit_array.c/bit_array_ut +$valgrind $testdir/lib/util/crc16.c/crc16_ut +$valgrind $testdir/lib/util/crc32_ieee.c/crc32_ieee_ut +$valgrind $testdir/lib/util/crc32c.c/crc32c_ut +$valgrind $testdir/lib/util/string.c/string_ut + +if [ $(uname -s) = Linux ]; then +$valgrind $testdir/lib/vhost/vhost.c/vhost_ut +fi + +# local unit test coverage +if [ "$cov_avail" = "yes" ]; then + $LCOV -q -d . -c -t "$(hostname)" -o $UT_COVERAGE/ut_cov_test.info + $LCOV -q -a $UT_COVERAGE/ut_cov_base.info -a $UT_COVERAGE/ut_cov_test.info -o $UT_COVERAGE/ut_cov_total.info + $LCOV -q -a $UT_COVERAGE/ut_cov_total.info -o $UT_COVERAGE/ut_cov_unit.info + $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/app/*" -o $UT_COVERAGE/ut_cov_unit.info + $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/dpdk/*" -o $UT_COVERAGE/ut_cov_unit.info + $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/examples/*" -o $UT_COVERAGE/ut_cov_unit.info + $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/include/*" -o $UT_COVERAGE/ut_cov_unit.info + $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/lib/vhost/rte_vhost/*" -o $UT_COVERAGE/ut_cov_unit.info + $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/test/*" -o $UT_COVERAGE/ut_cov_unit.info + rm -f $UT_COVERAGE/ut_cov_base.info $UT_COVERAGE/ut_cov_test.info + genhtml $UT_COVERAGE/ut_cov_unit.info --output-directory $UT_COVERAGE + # git -C option not used for compatibility reasons + cd $rootdir + git clean -f "*.gcda" + cd - +fi + +set +x + +echo +echo +echo "=====================" +echo "All unit tests passed" +echo "=====================" +if [ "$cov_avail" = "yes" ]; then + echo "Note: coverage report is here: $rootdir/$UT_COVERAGE" +else + echo "WARN: lcov not installed or SPDK built without coverage!" +fi +if grep -q '#undef SPDK_CONFIG_ASAN' $rootdir/include/spdk/config.h && [ "$valgrind" = "" ]; then + echo "WARN: neither valgrind nor ASAN is enabled!" +fi + +echo +echo diff --git a/src/spdk/test/vhost/common/autotest.config b/src/spdk/test/vhost/common/autotest.config new file mode 100644 index 00000000..96b0d08b --- /dev/null +++ b/src/spdk/test/vhost/common/autotest.config @@ -0,0 +1,38 @@ +vhost_0_reactor_mask="[0]" +vhost_0_master_core=0 + +VM_0_qemu_mask=1-2 +VM_0_qemu_numa_node=0 + +VM_1_qemu_mask=3-4 +VM_1_qemu_numa_node=0 + +VM_2_qemu_mask=5-6 +VM_2_qemu_numa_node=0 + +VM_3_qemu_mask=7-8 +VM_3_qemu_numa_node=0 + +VM_4_qemu_mask=9-10 +VM_4_qemu_numa_node=0 + +VM_5_qemu_mask=11-12 +VM_5_qemu_numa_node=0 + +VM_6_qemu_mask=13-14 +VM_6_qemu_numa_node=1 + +VM_7_qemu_mask=15-16 +VM_7_qemu_numa_node=1 + +VM_8_qemu_mask=17-18 +VM_8_qemu_numa_node=1 + +VM_9_qemu_mask=19-20 +VM_9_qemu_numa_node=1 + +VM_10_qemu_mask=21-22 +VM_10_qemu_numa_node=1 + +VM_11_qemu_mask=23-24 +VM_11_qemu_numa_node=1 diff --git a/src/spdk/test/vhost/common/common.sh b/src/spdk/test/vhost/common/common.sh new file mode 100644 index 00000000..19c4be62 --- /dev/null +++ b/src/spdk/test/vhost/common/common.sh @@ -0,0 +1,1109 @@ +set -e + +: ${SPDK_VHOST_VERBOSE=false} +: ${QEMU_PREFIX="/usr/local/qemu/spdk-2.12"} + +BASE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})) + +# Default running dir -> spdk/.. +[[ -z "$TEST_DIR" ]] && TEST_DIR=$BASE_DIR/../../../../ + +TEST_DIR="$(mkdir -p $TEST_DIR && cd $TEST_DIR && echo $PWD)" +SPDK_BUILD_DIR=$BASE_DIR/../../../ + +SPDK_VHOST_SCSI_TEST_DIR=$TEST_DIR/vhost + +function message() +{ + if ! $SPDK_VHOST_VERBOSE; then + local verbose_out="" + elif [[ ${FUNCNAME[2]} == "source" ]]; then + local verbose_out=" (file $(basename ${BASH_SOURCE[1]}):${BASH_LINENO[1]})" + else + local verbose_out=" (function ${FUNCNAME[2]}:${BASH_LINENO[1]})" + fi + + local msg_type="$1" + shift + echo -e "${msg_type}${verbose_out}: $@" +} + +function fail() +{ + echo "===========" >&2 + message "FAIL" "$@" >&2 + echo "===========" >&2 + exit 1 +} + +function error() +{ + echo "===========" >&2 + message "ERROR" "$@" >&2 + echo "===========" >&2 + # Don't 'return 1' since the stack trace will be incomplete (why?) missing upper command. + false +} + +function warning() +{ + message "WARN" "$@" >&2 +} + +function notice() +{ + message "INFO" "$@" +} + + +# SSH key file +: ${SPDK_VHOST_SSH_KEY_FILE="$(readlink -e $HOME/.ssh/spdk_vhost_id_rsa)"} +if [[ ! -r "$SPDK_VHOST_SSH_KEY_FILE" ]]; then + error "Could not find SSH key file $SPDK_VHOST_SSH_KEY_FILE" + exit 1 +fi +echo "Using SSH key file $SPDK_VHOST_SSH_KEY_FILE" + +VM_BASE_DIR="$TEST_DIR/vms" + + +mkdir -p $TEST_DIR + +# +# Source config describing QEMU and VHOST cores and NUMA +# +source $(readlink -f $(dirname ${BASH_SOURCE[0]}))/autotest.config + +# Trace flag is optional, if it wasn't set earlier - disable it after sourcing +# autotest_common.sh +if [[ $- =~ x ]]; then + source $SPDK_BUILD_DIR/test/common/autotest_common.sh +else + source $SPDK_BUILD_DIR/test/common/autotest_common.sh + set +x +fi + +function get_vhost_dir() +{ + if [[ ! -z "$1" ]]; then + assert_number "$1" + local vhost_num=$1 + else + local vhost_num=0 + fi + + echo "$SPDK_VHOST_SCSI_TEST_DIR${vhost_num}" +} + +function spdk_vhost_list_all() +{ + shopt -s nullglob + local vhost_list="$(echo $SPDK_VHOST_SCSI_TEST_DIR[0-9]*)" + shopt -u nullglob + + if [[ ! -z "$vhost_list" ]]; then + vhost_list="$(basename --multiple $vhost_list)" + echo "${vhost_list//vhost/}" + fi +} + +function spdk_vhost_run() +{ + local param + local vhost_num=0 + local vhost_conf_path="" + local memory=1024 + + for param in "$@"; do + case $param in + --vhost-num=*) + vhost_num="${param#*=}" + assert_number "$vhost_num" + ;; + --conf-path=*) local vhost_conf_path="${param#*=}" ;; + --json-path=*) local vhost_json_path="${param#*=}" ;; + --memory=*) local memory=${param#*=} ;; + --no-pci*) local no_pci="-u" ;; + *) + error "Invalid parameter '$param'" + return 1 + ;; + esac + done + + local vhost_dir="$(get_vhost_dir $vhost_num)" + local vhost_app="$SPDK_BUILD_DIR/app/vhost/vhost" + local vhost_log_file="$vhost_dir/vhost.log" + local vhost_pid_file="$vhost_dir/vhost.pid" + local vhost_socket="$vhost_dir/usvhost" + local vhost_conf_template="$vhost_conf_path/vhost.conf.in" + local vhost_conf_file="$vhost_conf_path/vhost.conf" + notice "starting vhost app in background" + [[ -r "$vhost_pid_file" ]] && spdk_vhost_kill $vhost_num + [[ -d $vhost_dir ]] && rm -f $vhost_dir/* + mkdir -p $vhost_dir + + if [[ ! -x $vhost_app ]]; then + error "application not found: $vhost_app" + return 1 + fi + + local reactor_mask="vhost_${vhost_num}_reactor_mask" + reactor_mask="${!reactor_mask}" + + local master_core="vhost_${vhost_num}_master_core" + master_core="${!master_core}" + + if [[ -z "$reactor_mask" ]] || [[ -z "$master_core" ]]; then + error "Parameters vhost_${vhost_num}_reactor_mask or vhost_${vhost_num}_master_core not found in autotest.config file" + return 1 + fi + + local cmd="$vhost_app -m $reactor_mask -p $master_core -s $memory -r $vhost_dir/rpc.sock $no_pci" + if [[ -n "$vhost_conf_path" ]]; then + cp $vhost_conf_template $vhost_conf_file + $SPDK_BUILD_DIR/scripts/gen_nvme.sh >> $vhost_conf_file + cmd="$vhost_app -m $reactor_mask -p $master_core -c $vhost_conf_file -s $memory -r $vhost_dir/rpc.sock $no_pci" + fi + + notice "Loging to: $vhost_log_file" + notice "Socket: $vhost_socket" + notice "Command: $cmd" + + timing_enter vhost_start + cd $vhost_dir; $cmd & + vhost_pid=$! + echo $vhost_pid > $vhost_pid_file + + notice "waiting for app to run..." + waitforlisten "$vhost_pid" "$vhost_dir/rpc.sock" + #do not generate nvmes if pci access is disabled + if [[ -z "$vhost_conf_path" ]] && [[ -z "$no_pci" ]]; then + $SPDK_BUILD_DIR/scripts/gen_nvme.sh "--json" | $SPDK_BUILD_DIR/scripts/rpc.py\ + -s $vhost_dir/rpc.sock load_subsystem_config + fi + + if [[ -n "$vhost_json_path" ]]; then + $SPDK_BUILD_DIR/scripts/rpc.py -s $vhost_dir/rpc.sock load_config < "$vhost_json_path/conf.json" + fi + + notice "vhost started - pid=$vhost_pid" + timing_exit vhost_start + + rm -f $vhost_conf_file +} + +function spdk_vhost_kill() +{ + local rc=0 + local vhost_num=0 + if [[ ! -z "$1" ]]; then + vhost_num=$1 + assert_number "$vhost_num" + fi + + local vhost_pid_file="$(get_vhost_dir $vhost_num)/vhost.pid" + + if [[ ! -r $vhost_pid_file ]]; then + warning "no vhost pid file found" + return 0 + fi + + timing_enter vhost_kill + local vhost_pid="$(cat $vhost_pid_file)" + notice "killing vhost (PID $vhost_pid) app" + + if /bin/kill -INT $vhost_pid >/dev/null; then + notice "sent SIGINT to vhost app - waiting 60 seconds to exit" + for ((i=0; i<60; i++)); do + if /bin/kill -0 $vhost_pid; then + echo "." + sleep 1 + else + break + fi + done + if /bin/kill -0 $vhost_pid; then + error "ERROR: vhost was NOT killed - sending SIGABRT" + /bin/kill -ABRT $vhost_pid + rm $vhost_pid_file + rc=1 + else + while kill -0 $vhost_pid; do + echo "." + done + fi + elif /bin/kill -0 $vhost_pid; then + error "vhost NOT killed - you need to kill it manually" + rc=1 + else + notice "vhost was no running" + fi + + timing_exit vhost_kill + if [[ $rc == 0 ]]; then + rm $vhost_pid_file + fi + + return $rc +} + +### +# Mgmt functions +### + +function assert_number() +{ + [[ "$1" =~ [0-9]+ ]] && return 0 + + error "Invalid or missing paramter: need number but got '$1'" + return 1; +} + +# Helper to validate VM number +# param $1 VM number +# +function vm_num_is_valid() +{ + [[ "$1" =~ ^[0-9]+$ ]] && return 0 + + error "Invalid or missing paramter: vm number '$1'" + return 1; +} + + +# Print network socket for given VM number +# param $1 virtual machine number +# +function vm_ssh_socket() +{ + vm_num_is_valid $1 || return 1 + local vm_dir="$VM_BASE_DIR/$1" + + cat $vm_dir/ssh_socket +} + +function vm_fio_socket() +{ + vm_num_is_valid $1 || return 1 + local vm_dir="$VM_BASE_DIR/$1" + + cat $vm_dir/fio_socket +} + +function vm_create_ssh_config() +{ + local ssh_config="$VM_BASE_DIR/ssh_config" + if [[ ! -f $ssh_config ]]; then + ( + echo "Host *" + echo " ControlPersist=10m" + echo " ConnectTimeout=1" + echo " Compression=no" + echo " ControlMaster=auto" + echo " UserKnownHostsFile=/dev/null" + echo " StrictHostKeyChecking=no" + echo " User root" + echo " ControlPath=/tmp/%r@%h:%p.ssh" + echo "" + ) > $ssh_config + # Control path created at /tmp because of live migration test case 3. + # In case of using sshfs share for the test - control path cannot be + # on share because remote server will fail on ssh commands. + fi +} + +# Execute ssh command on given VM +# param $1 virtual machine number +# +function vm_ssh() +{ + vm_num_is_valid $1 || return 1 + vm_create_ssh_config + local ssh_config="$VM_BASE_DIR/ssh_config" + + local ssh_cmd="ssh -i $SPDK_VHOST_SSH_KEY_FILE -F $ssh_config \ + -p $(vm_ssh_socket $1) $VM_SSH_OPTIONS 127.0.0.1" + + shift + $ssh_cmd "$@" +} + +# Execute scp command on given VM +# param $1 virtual machine number +# +function vm_scp() +{ + vm_num_is_valid $1 || return 1 + vm_create_ssh_config + local ssh_config="$VM_BASE_DIR/ssh_config" + + local scp_cmd="scp -i $SPDK_VHOST_SSH_KEY_FILE -F $ssh_config \ + -P $(vm_ssh_socket $1) " + + shift + $scp_cmd "$@" +} + + +# check if specified VM is running +# param $1 VM num +function vm_is_running() +{ + vm_num_is_valid $1 || return 1 + local vm_dir="$VM_BASE_DIR/$1" + + if [[ ! -r $vm_dir/qemu.pid ]]; then + return 1 + fi + + local vm_pid="$(cat $vm_dir/qemu.pid)" + + if /bin/kill -0 $vm_pid; then + return 0 + else + if [[ $EUID -ne 0 ]]; then + warning "not root - assuming VM running since can't be checked" + return 0 + fi + + # not running - remove pid file + rm $vm_dir/qemu.pid + return 1 + fi +} + +# check if specified VM is running +# param $1 VM num +function vm_os_booted() +{ + vm_num_is_valid $1 || return 1 + local vm_dir="$VM_BASE_DIR/$1" + + if [[ ! -r $vm_dir/qemu.pid ]]; then + error "VM $1 is not running" + return 1 + fi + + if ! VM_SSH_OPTIONS="-o ControlMaster=no" vm_ssh $1 "true" 2>/dev/null; then + # Shutdown existing master. Ignore errors as it might not exist. + VM_SSH_OPTIONS="-O exit" vm_ssh $1 "true" 2>/dev/null + return 1 + fi + + return 0 +} + + +# Shutdown given VM +# param $1 virtual machine number +# return non-zero in case of error. +function vm_shutdown() +{ + vm_num_is_valid $1 || return 1 + local vm_dir="$VM_BASE_DIR/$1" + if [[ ! -d "$vm_dir" ]]; then + error "VM$1 ($vm_dir) not exist - setup it first" + return 1 + fi + + if ! vm_is_running $1; then + notice "VM$1 ($vm_dir) is not running" + return 0 + fi + + # Temporarily disabling exit flag for next ssh command, since it will + # "fail" due to shutdown + notice "Shutting down virtual machine $vm_dir" + set +e + vm_ssh $1 "nohup sh -c 'shutdown -h -P now'" || true + notice "VM$1 is shutting down - wait a while to complete" + set -e +} + +# Kill given VM +# param $1 virtual machine number +# +function vm_kill() +{ + vm_num_is_valid $1 || return 1 + local vm_dir="$VM_BASE_DIR/$1" + + if [[ ! -r $vm_dir/qemu.pid ]]; then + return 0 + fi + + local vm_pid="$(cat $vm_dir/qemu.pid)" + + notice "Killing virtual machine $vm_dir (pid=$vm_pid)" + # First kill should fail, second one must fail + if /bin/kill $vm_pid; then + notice "process $vm_pid killed" + rm $vm_dir/qemu.pid + elif vm_is_running $1; then + error "Process $vm_pid NOT killed" + return 1 + fi +} + +# List all VM numbers in VM_BASE_DIR +# +function vm_list_all() +{ + local vms="$(shopt -s nullglob; echo $VM_BASE_DIR/[0-9]*)" + if [[ ! -z "$vms" ]]; then + basename --multiple $vms + fi +} + +# Kills all VM in $VM_BASE_DIR +# +function vm_kill_all() +{ + local vm + for vm in $(vm_list_all); do + vm_kill $vm + done +} + +# Shutdown all VM in $VM_BASE_DIR +# +function vm_shutdown_all() +{ + local shell_restore_x="$( [[ "$-" =~ x ]] && echo 'set -x' )" + # XXX: temporally disable to debug shutdown issue + # set +x + + local vms=$(vm_list_all) + local vm + + for vm in $vms; do + vm_shutdown $vm + done + + notice "Waiting for VMs to shutdown..." + local timeo=30 + while [[ $timeo -gt 0 ]]; do + local all_vms_down=1 + for vm in $vms; do + if vm_is_running $vm; then + all_vms_down=0 + break + fi + done + + if [[ $all_vms_down == 1 ]]; then + notice "All VMs successfully shut down" + $shell_restore_x + return 0 + fi + + ((timeo-=1)) + sleep 1 + done + + $shell_restore_x + error "Timeout waiting for some VMs to shutdown" + return 1 +} + +function vm_setup() +{ + local shell_restore_x="$( [[ "$-" =~ x ]] && echo 'set -x' )" + local OPTIND optchar vm_num + + local os="" + local os_mode="" + local qemu_args="" + local disk_type_g=NOT_DEFINED + local read_only="false" + local disks="" + local raw_cache="" + local vm_incoming="" + local vm_migrate_to="" + local force_vm="" + local guest_memory=1024 + local queue_number="" + local vhost_dir="$(get_vhost_dir)" + while getopts ':-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + os=*) local os="${OPTARG#*=}" ;; + os-mode=*) local os_mode="${OPTARG#*=}" ;; + qemu-args=*) local qemu_args="${qemu_args} ${OPTARG#*=}" ;; + disk-type=*) local disk_type_g="${OPTARG#*=}" ;; + read-only=*) local read_only="${OPTARG#*=}" ;; + disks=*) local disks="${OPTARG#*=}" ;; + raw-cache=*) local raw_cache=",cache${OPTARG#*=}" ;; + force=*) local force_vm=${OPTARG#*=} ;; + memory=*) local guest_memory=${OPTARG#*=} ;; + queue_num=*) local queue_number=${OPTARG#*=} ;; + incoming=*) local vm_incoming="${OPTARG#*=}" ;; + migrate-to=*) local vm_migrate_to="${OPTARG#*=}" ;; + vhost-num=*) local vhost_dir="$(get_vhost_dir ${OPTARG#*=})" ;; + spdk-boot=*) local boot_from="${OPTARG#*=}" ;; + *) + error "unknown argument $OPTARG" + return 1 + esac + ;; + *) + error "vm_create Unknown param $OPTARG" + return 1 + ;; + esac + done + + # Find next directory we can use + if [[ ! -z $force_vm ]]; then + vm_num=$force_vm + + vm_num_is_valid $vm_num || return 1 + local vm_dir="$VM_BASE_DIR/$vm_num" + [[ -d $vm_dir ]] && warning "removing existing VM in '$vm_dir'" + else + local vm_dir="" + + set +x + for (( i=0; i<=256; i++)); do + local vm_dir="$VM_BASE_DIR/$i" + [[ ! -d $vm_dir ]] && break + done + $shell_restore_x + + vm_num=$i + fi + + if [[ $i -eq 256 ]]; then + error "no free VM found. do some cleanup (256 VMs created, are you insane?)" + return 1 + fi + + if [[ ! -z "$vm_migrate_to" && ! -z "$vm_incoming" ]]; then + error "'--incoming' and '--migrate-to' cannot be used together" + return 1 + elif [[ ! -z "$vm_incoming" ]]; then + if [[ ! -z "$os_mode" || ! -z "$os_img" ]]; then + error "'--incoming' can't be used together with '--os' nor '--os-mode'" + return 1 + fi + + os_mode="original" + os="$VM_BASE_DIR/$vm_incoming/os.qcow2" + elif [[ ! -z "$vm_migrate_to" ]]; then + [[ "$os_mode" != "backing" ]] && warning "Using 'backing' mode for OS since '--migrate-to' is used" + os_mode=backing + fi + + notice "Creating new VM in $vm_dir" + mkdir -p $vm_dir + + if [[ "$os_mode" == "backing" ]]; then + notice "Creating backing file for OS image file: $os" + if ! $QEMU_PREFIX/bin/qemu-img create -f qcow2 -b $os $vm_dir/os.qcow2; then + error "Failed to create OS backing file in '$vm_dir/os.qcow2' using '$os'" + return 1 + fi + + local os=$vm_dir/os.qcow2 + elif [[ "$os_mode" == "original" ]]; then + warning "Using original OS image file: $os" + elif [[ "$os_mode" != "snapshot" ]]; then + if [[ -z "$os_mode" ]]; then + notice "No '--os-mode' parameter provided - using 'snapshot'" + os_mode="snapshot" + else + error "Invalid '--os-mode=$os_mode'" + return 1 + fi + fi + + # WARNING: + # each cmd+= must contain ' ${eol}' at the end + # + local eol="\\\\\n " + local qemu_mask_param="VM_${vm_num}_qemu_mask" + local qemu_numa_node_param="VM_${vm_num}_qemu_numa_node" + + if [[ -z "${!qemu_mask_param}" ]] || [[ -z "${!qemu_numa_node_param}" ]]; then + error "Parameters ${qemu_mask_param} or ${qemu_numa_node_param} not found in autotest.config file" + return 1 + fi + + local task_mask=${!qemu_mask_param} + + notice "TASK MASK: $task_mask" + local cmd="taskset -a -c $task_mask $QEMU_PREFIX/bin/qemu-system-x86_64 ${eol}" + local vm_socket_offset=$(( 10000 + 100 * vm_num )) + + local ssh_socket=$(( vm_socket_offset + 0 )) + local fio_socket=$(( vm_socket_offset + 1 )) + local monitor_port=$(( vm_socket_offset + 2 )) + local migration_port=$(( vm_socket_offset + 3 )) + local gdbserver_socket=$(( vm_socket_offset + 4 )) + local vnc_socket=$(( 100 + vm_num )) + local qemu_pid_file="$vm_dir/qemu.pid" + local cpu_num=0 + + set +x + # cpu list for taskset can be comma separated or range + # or both at the same time, so first split on commas + cpu_list=$(echo $task_mask | tr "," "\n") + queue_number=0 + for c in $cpu_list; do + # if range is detected - count how many cpus + if [[ $c =~ [0-9]+-[0-9]+ ]]; then + val=$(($c-1)) + val=${val#-} + else + val=1 + fi + cpu_num=$((cpu_num+val)) + queue_number=$((queue_number+val)) + done + + if [ -z $queue_number ]; then + queue_number=$cpu_num + fi + + $shell_restore_x + + local node_num=${!qemu_numa_node_param} + local boot_disk_present=false + notice "NUMA NODE: $node_num" + cmd+="-m $guest_memory --enable-kvm -cpu host -smp $cpu_num -vga std -vnc :$vnc_socket -daemonize ${eol}" + cmd+="-object memory-backend-file,id=mem,size=${guest_memory}M,mem-path=/dev/hugepages,share=on,prealloc=yes,host-nodes=$node_num,policy=bind ${eol}" + [[ $os_mode == snapshot ]] && cmd+="-snapshot ${eol}" + [[ ! -z "$vm_incoming" ]] && cmd+=" -incoming tcp:0:$migration_port ${eol}" + cmd+="-monitor telnet:127.0.0.1:$monitor_port,server,nowait ${eol}" + cmd+="-numa node,memdev=mem ${eol}" + cmd+="-pidfile $qemu_pid_file ${eol}" + cmd+="-serial file:$vm_dir/serial.log ${eol}" + cmd+="-D $vm_dir/qemu.log ${eol}" + cmd+="-net user,hostfwd=tcp::$ssh_socket-:22,hostfwd=tcp::$fio_socket-:8765 ${eol}" + cmd+="-net nic ${eol}" + if [[ -z "$boot_from" ]]; then + cmd+="-drive file=$os,if=none,id=os_disk ${eol}" + cmd+="-device ide-hd,drive=os_disk,bootindex=0 ${eol}" + fi + + if ( [[ $disks == '' ]] && [[ $disk_type_g == virtio* ]] ); then + disks=1 + fi + + for disk in ${disks//:/ }; do + if [[ $disk = *","* ]]; then + disk_type=${disk#*,} + disk=${disk%,*} + else + disk_type=$disk_type_g + fi + + case $disk_type in + virtio) + local raw_name="RAWSCSI" + local raw_disk=$vm_dir/test.img + + if [[ ! -z $disk ]]; then + [[ ! -b $disk ]] && touch $disk + local raw_disk=$(readlink -f $disk) + fi + + # Create disk file if it not exist or it is smaller than 1G + if ( [[ -f $raw_disk ]] && [[ $(stat --printf="%s" $raw_disk) -lt $((1024 * 1024 * 1024)) ]] ) || \ + [[ ! -e $raw_disk ]]; then + if [[ $raw_disk =~ /dev/.* ]]; then + error \ + "ERROR: Virtio disk point to missing device ($raw_disk) -\n" \ + " this is probably not what you want." + return 1 + fi + + notice "Creating Virtio disc $raw_disk" + dd if=/dev/zero of=$raw_disk bs=1024k count=1024 + else + notice "Using existing image $raw_disk" + fi + + cmd+="-device virtio-scsi-pci,num_queues=$queue_number ${eol}" + cmd+="-device scsi-hd,drive=hd$i,vendor=$raw_name ${eol}" + cmd+="-drive if=none,id=hd$i,file=$raw_disk,format=raw$raw_cache ${eol}" + ;; + spdk_vhost_scsi) + notice "using socket $vhost_dir/naa.$disk.$vm_num" + cmd+="-chardev socket,id=char_$disk,path=$vhost_dir/naa.$disk.$vm_num ${eol}" + cmd+="-device vhost-user-scsi-pci,id=scsi_$disk,num_queues=$queue_number,chardev=char_$disk" + if [[ "$disk" == "$boot_from" ]]; then + cmd+=",bootindex=0" + boot_disk_present=true + fi + cmd+=" ${eol}" + ;; + spdk_vhost_blk) + notice "using socket $vhost_dir/naa.$disk.$vm_num" + cmd+="-chardev socket,id=char_$disk,path=$vhost_dir/naa.$disk.$vm_num ${eol}" + cmd+="-device vhost-user-blk-pci,num-queues=$queue_number,chardev=char_$disk" + if [[ "$disk" == "$boot_from" ]]; then + cmd+=",bootindex=0" + boot_disk_present=true + fi + cmd+=" ${eol}" + ;; + kernel_vhost) + if [[ -z $disk ]]; then + error "need WWN for $disk_type" + return 1 + elif [[ ! $disk =~ ^[[:alpha:]]{3}[.][[:xdigit:]]+$ ]]; then + error "$disk_type - disk(wnn)=$disk does not look like WNN number" + return 1 + fi + notice "Using kernel vhost disk wwn=$disk" + cmd+=" -device vhost-scsi-pci,wwpn=$disk,num_queues=$queue_number ${eol}" + ;; + *) + error "unknown mode '$disk_type', use: virtio, spdk_vhost_scsi, spdk_vhost_blk or kernel_vhost" + return 1 + esac + done + + if [[ -n $boot_from ]] && [[ $boot_disk_present == false ]]; then + error "Boot from $boot_from is selected but device is not present" + return 1 + fi + + [[ ! -z $qemu_args ]] && cmd+=" $qemu_args ${eol}" + # remove last $eol + cmd="${cmd%\\\\\\n }" + + notice "Saving to $vm_dir/run.sh" + ( + echo '#!/bin/bash' + echo 'if [[ $EUID -ne 0 ]]; then ' + echo ' echo "Go away user come back as root"' + echo ' exit 1' + echo 'fi'; + echo + echo -e "qemu_cmd=\"$cmd\""; + echo + echo "echo 'Running VM in $vm_dir'" + echo "rm -f $qemu_pid_file" + echo '$qemu_cmd' + echo "echo 'Waiting for QEMU pid file'" + echo "sleep 1" + echo "[[ ! -f $qemu_pid_file ]] && sleep 1" + echo "[[ ! -f $qemu_pid_file ]] && echo 'ERROR: no qemu pid file found' && exit 1" + echo + echo "chmod +r $vm_dir/*" + echo + echo "echo '=== qemu.log ==='" + echo "cat $vm_dir/qemu.log" + echo "echo '=== qemu.log ==='" + echo '# EOF' + ) > $vm_dir/run.sh + chmod +x $vm_dir/run.sh + + # Save generated sockets redirection + echo $ssh_socket > $vm_dir/ssh_socket + echo $fio_socket > $vm_dir/fio_socket + echo $monitor_port > $vm_dir/monitor_port + + rm -f $vm_dir/migration_port + [[ -z $vm_incoming ]] || echo $migration_port > $vm_dir/migration_port + + echo $gdbserver_socket > $vm_dir/gdbserver_socket + echo $vnc_socket >> $vm_dir/vnc_socket + + [[ -z $vm_incoming ]] || ln -fs $VM_BASE_DIR/$vm_incoming $vm_dir/vm_incoming + [[ -z $vm_migrate_to ]] || ln -fs $VM_BASE_DIR/$vm_migrate_to $vm_dir/vm_migrate_to +} + +function vm_run() +{ + local OPTIND optchar vm + local run_all=false + local vms_to_run="" + + while getopts 'a-:' optchar; do + case "$optchar" in + a) run_all=true ;; + *) + error "Unknown param $OPTARG" + return 1 + ;; + esac + done + + if $run_all; then + vms_to_run="$(vm_list_all)" + else + shift $((OPTIND-1)) + for vm in $@; do + vm_num_is_valid $1 || return 1 + if [[ ! -x $VM_BASE_DIR/$vm/run.sh ]]; then + error "VM$vm not defined - setup it first" + return 1 + fi + vms_to_run+=" $vm" + done + fi + + for vm in $vms_to_run; do + if vm_is_running $vm; then + warning "VM$vm ($VM_BASE_DIR/$vm) already running" + continue + fi + + notice "running $VM_BASE_DIR/$vm/run.sh" + if ! $VM_BASE_DIR/$vm/run.sh; then + error "FAILED to run vm $vm" + return 1 + fi + done +} + +# Wait for all created VMs to boot. +# param $1 max wait time +function vm_wait_for_boot() +{ + assert_number $1 + + local shell_restore_x="$( [[ "$-" =~ x ]] && echo 'set -x' )" + set +x + + local all_booted=false + local timeout_time=$1 + [[ $timeout_time -lt 10 ]] && timeout_time=10 + local timeout_time=$(date -d "+$timeout_time seconds" +%s) + + notice "Waiting for VMs to boot" + shift + if [[ "$@" == "" ]]; then + local vms_to_check="$VM_BASE_DIR/[0-9]*" + else + local vms_to_check="" + for vm in $@; do + vms_to_check+=" $VM_BASE_DIR/$vm" + done + fi + + for vm in $vms_to_check; do + local vm_num=$(basename $vm) + local i=0 + notice "waiting for VM$vm_num ($vm)" + while ! vm_os_booted $vm_num; do + if ! vm_is_running $vm_num; then + + warning "VM $vm_num is not running" + warning "================" + warning "QEMU LOG:" + if [[ -r $vm/qemu.log ]]; then + cat $vm/qemu.log + else + warning "LOG not found" + fi + + warning "VM LOG:" + if [[ -r $vm/serial.log ]]; then + cat $vm/serial.log + else + warning "LOG not found" + fi + warning "================" + $shell_restore_x + return 1 + fi + + if [[ $(date +%s) -gt $timeout_time ]]; then + warning "timeout waiting for machines to boot" + $shell_restore_x + return 1 + fi + if (( i > 30 )); then + local i=0 + echo + fi + echo -n "." + sleep 1 + done + echo "" + notice "VM$vm_num ready" + #Change Timeout for stopping services to prevent lengthy powerdowns + vm_ssh $vm_num "echo 'DefaultTimeoutStopSec=10' >> /etc/systemd/system.conf; systemctl daemon-reexec" + done + + notice "all VMs ready" + $shell_restore_x + return 0 +} + +function vm_start_fio_server() +{ + local OPTIND optchar + local readonly='' + while getopts ':-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + fio-bin=*) local fio_bin="${OPTARG#*=}" ;; + readonly) local readonly="--readonly" ;; + *) error "Invalid argument '$OPTARG'" && return 1;; + esac + ;; + *) error "Invalid argument '$OPTARG'" && return 1;; + esac + done + + shift $(( OPTIND - 1 )) + for vm_num in $@; do + notice "Starting fio server on VM$vm_num" + if [[ $fio_bin != "" ]]; then + cat $fio_bin | vm_ssh $vm_num 'cat > /root/fio; chmod +x /root/fio' + vm_ssh $vm_num /root/fio $readonly --eta=never --server --daemonize=/root/fio.pid + else + vm_ssh $vm_num fio $readonly --eta=never --server --daemonize=/root/fio.pid + fi + done +} + +function vm_check_scsi_location() +{ + # Script to find wanted disc + local script='shopt -s nullglob; \ + for entry in /sys/block/sd*; do \ + disk_type="$(cat $entry/device/vendor)"; \ + if [[ $disk_type == INTEL* ]] || [[ $disk_type == RAWSCSI* ]] || [[ $disk_type == LIO-ORG* ]]; then \ + fname=$(basename $entry); \ + echo -n " $fname"; \ + fi; \ + done' + + SCSI_DISK="$(echo "$script" | vm_ssh $1 bash -s)" + + if [[ -z "$SCSI_DISK" ]]; then + error "no test disk found!" + return 1 + fi +} + +# Script to perform scsi device reset on all disks in VM +# param $1 VM num +# param $2..$n Disks to perform reset on +function vm_reset_scsi_devices() +{ + for disk in "${@:2}"; do + notice "VM$1 Performing device reset on disk $disk" + vm_ssh $1 sg_reset /dev/$disk -vNd + done +} + +function vm_check_blk_location() +{ + local script='shopt -s nullglob; cd /sys/block; echo vd*' + SCSI_DISK="$(echo "$script" | vm_ssh $1 bash -s)" + + if [[ -z "$SCSI_DISK" ]]; then + error "no blk test disk found!" + return 1 + fi +} + +function run_fio() +{ + local arg + local job_file="" + local fio_bin="" + local vms=() + local out="" + local fio_disks="" + local vm + local run_server_mode=true + + for arg in $@; do + case "$arg" in + --job-file=*) local job_file="${arg#*=}" ;; + --fio-bin=*) local fio_bin="${arg#*=}" ;; + --vm=*) vms+=( "${arg#*=}" ) ;; + --out=*) + local out="${arg#*=}" + mkdir -p $out + ;; + --local) run_server_mode=false ;; + --json) json="--json" ;; + *) + error "Invalid argument '$arg'" + return 1 + ;; + esac + done + + if [[ ! -z "$fio_bin" && ! -r "$fio_bin" ]]; then + error "FIO binary '$fio_bin' does not exist" + return 1 + fi + + if [[ ! -r "$job_file" ]]; then + error "Fio job '$job_file' does not exist" + return 1 + fi + + local job_fname=$(basename "$job_file") + # prepare job file for each VM + for vm in ${vms[@]}; do + local vm_num=${vm%%:*} + local vmdisks=${vm#*:} + + sed "s@filename=@filename=$vmdisks@" $job_file | vm_ssh $vm_num "cat > /root/$job_fname" + fio_disks+="127.0.0.1:$(vm_fio_socket $vm_num):$vmdisks," + + vm_ssh $vm_num cat /root/$job_fname + if ! $run_server_mode; then + if [[ ! -z "$fio_bin" ]]; then + cat $fio_bin | vm_ssh $vm_num 'cat > /root/fio; chmod +x /root/fio' + fi + + notice "Running local fio on VM $vm_num" + vm_ssh $vm_num "nohup /root/fio /root/$job_fname 1>/root/$job_fname.out 2>/root/$job_fname.out </dev/null & echo \$! > /root/fio.pid" + fi + done + + if ! $run_server_mode; then + # Give FIO time to run + sleep 0.5 + return 0 + fi + + $SPDK_BUILD_DIR/test/vhost/common/run_fio.py --job-file=/root/$job_fname \ + $([[ ! -z "$fio_bin" ]] && echo "--fio-bin=$fio_bin") \ + --out=$out $json ${fio_disks%,} +} + +# Shutdown or kill any running VM and SPDK APP. +# +function at_app_exit() +{ + local vhost_num + + notice "APP EXITING" + notice "killing all VMs" + vm_kill_all + # Kill vhost application + notice "killing vhost app" + + for vhost_num in $(spdk_vhost_list_all); do + spdk_vhost_kill $vhost_num + done + + notice "EXIT DONE" +} + +function error_exit() +{ + trap - ERR + print_backtrace + set +e + error "Error on $1 $2" + + at_app_exit + exit 1 +} diff --git a/src/spdk/test/vhost/common/fio_jobs/default_initiator.job b/src/spdk/test/vhost/common/fio_jobs/default_initiator.job new file mode 100644 index 00000000..43c1404b --- /dev/null +++ b/src/spdk/test/vhost/common/fio_jobs/default_initiator.job @@ -0,0 +1,9 @@ +[global] +thread=1 +group_reporting=1 +direct=1 +time_based=1 +do_verify=1 +verify=md5 +verify_backlog=1024 +fsync_on_close=1 diff --git a/src/spdk/test/vhost/common/fio_jobs/default_integrity.job b/src/spdk/test/vhost/common/fio_jobs/default_integrity.job new file mode 100644 index 00000000..06398b50 --- /dev/null +++ b/src/spdk/test/vhost/common/fio_jobs/default_integrity.job @@ -0,0 +1,19 @@ +[global] +blocksize_range=4k-512k +iodepth=512 +iodepth_batch=128 +iodepth_low=256 +ioengine=libaio +size=1G +io_size=4G +filename= +group_reporting +thread +numjobs=1 +direct=1 +rw=randwrite +do_verify=1 +verify=md5 +verify_backlog=1024 +fsync_on_close=1 +[nvme-host] diff --git a/src/spdk/test/vhost/common/fio_jobs/default_integrity_nightly.job b/src/spdk/test/vhost/common/fio_jobs/default_integrity_nightly.job new file mode 100644 index 00000000..09740178 --- /dev/null +++ b/src/spdk/test/vhost/common/fio_jobs/default_integrity_nightly.job @@ -0,0 +1,23 @@ +[global] +ioengine=libaio +runtime=10 +filename= +group_reporting +thread +numjobs=1 +direct=1 +do_verify=1 +verify=md5 +verify_backlog=1024 + +[randwrite] +stonewall +rw=randwrite +bs=512k +iodepth=256 + +[randrw] +stonewall +rw=randrw +bs=128k +iodepth=64 diff --git a/src/spdk/test/vhost/common/fio_jobs/default_performance.job b/src/spdk/test/vhost/common/fio_jobs/default_performance.job new file mode 100644 index 00000000..a51cb5ed --- /dev/null +++ b/src/spdk/test/vhost/common/fio_jobs/default_performance.job @@ -0,0 +1,16 @@ +[global] +blocksize_range=4k-512k +iodepth=512 +iodepth_batch=128 +iodepth_low=256 +ioengine=libaio +size=10G +filename= +ramp_time=10 +group_reporting +thread +numjobs=1 +direct=1 +rw=randread +fsync_on_close=1 +[nvme-host] diff --git a/src/spdk/test/vhost/common/run_fio.py b/src/spdk/test/vhost/common/run_fio.py new file mode 100755 index 00000000..0760b018 --- /dev/null +++ b/src/spdk/test/vhost/common/run_fio.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 + +import os +import sys +import getopt +import subprocess +import signal +import re + +fio_bin = "fio" + + +def show_help(): + print("""Usage: {} run_fio.py [options] [args] + Description: + Run FIO job file 'fio.job' on remote machines. + NOTE: The job file must exist on remote machines on '/root/' directory. + Args: + [VMs] (ex. vm1_IP:vm1_port:vm1_disk1:vm_disk2,vm2_IP:vm2_port:vm2_disk1,etc...) + Options: + -h, --help Show this message. + -j, --job-file Paths to file with FIO job configuration on remote host. + -f, --fio-bin Location of FIO binary on local host (Default "fio") + -o, --out Directory used to save generated job files and + files with test results + -J, --json Use JSON format for output + -p, --perf-vmex Enable aggregating statistic for VMEXITS for VMs + """.format(os.path.split(sys.executable)[-1])) + + +def exec_cmd(cmd, blocking): + # Print result to STDOUT for now, we don't have json support yet. + p = subprocess.Popen(cmd.split(" "), stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, stdin=subprocess.PIPE) + if blocking is True: + out, _ = p.communicate() + return p.returncode, out.decode() + return p + + +def save_file(path, mode, contents): + with open(path, mode) as fh: + fh.write(contents) + fh.close() + + +def run_fio(vms, fio_cfg_fname, out_path, perf_vmex=False, json=False): + global fio_bin + job_name = os.path.splitext(os.path.basename(fio_cfg_fname))[0] + + # Build command for FIO + fio_cmd = " ".join([fio_bin, "--eta=never"]) + if json: + fio_cmd = " ".join([fio_bin, "--output-format=json"]) + for vm in vms: + # vm[0] = IP address, vm[1] = Port number + fio_cmd = " ".join([fio_cmd, + "--client={vm_ip},{vm_port}".format(vm_ip=vm[0], vm_port=vm[1]), + "--remote-config {cfg}".format(cfg=fio_cfg_fname)]) + print(fio_cmd) + + if perf_vmex: + perf_dir = os.path.join(out_path, "perf_stats") + try: + os.mkdir(perf_dir) + except OSError: + pass + + # Start gathering perf statistics for host and VM guests + perf_rec_file = os.path.join(perf_dir, "perf.data.kvm") + perf_run_cmd = "perf kvm --host --guest " + \ + "-o {0} stat record -a".format(perf_rec_file) + print(perf_run_cmd) + perf_p = exec_cmd(perf_run_cmd, blocking=False) + + # Run FIO test on VMs + rc, out = exec_cmd(fio_cmd, blocking=True) + + # if for some reason output contains lines with "eta" - remove them + out = re.sub(r'.+\[eta\s+\d{2}m:\d{2}s\]', '', out) + + print(out) + + if rc != 0: + print("ERROR! While executing FIO jobs - RC: {rc}".format(rc=rc, out=out)) + sys.exit(rc) + else: + save_file(os.path.join(out_path, ".".join([job_name, "log"])), "w", out) + + if perf_vmex: + # Stop gathering perf statistics and prepare some result files + perf_p.send_signal(signal.SIGINT) + perf_p.wait() + + perf_stat_cmd = "perf kvm --host -i {perf_rec} stat report --event vmexit"\ + .format(perf_rec=perf_rec_file) + + rc, out = exec_cmd(" ".join([perf_stat_cmd, "--event vmexit"]), + blocking=True) + print("VMexit host stats:") + print("{perf_out}".format(perf_out=out)) + save_file(os.path.join(perf_dir, "vmexit_stats_" + job_name), + "w", "{perf_out}".format(perf_out=out)) + try: + os.remove(perf_rec_file) + except OSError: + pass + + +def main(): + global fio_bin + + abspath = os.path.abspath(__file__) + dname = os.path.dirname(abspath) + + vms = [] + fio_cfg = None + out_dir = None + perf_vmex = False + json = False + + try: + opts, args = getopt.getopt(sys.argv[1:], "hJj:f:o:p", + ["help", "job-file=", "fio-bin=", + "out=", "perf-vmex", "json"]) + except getopt.GetoptError: + show_help() + sys.exit(1) + + if len(args) < 1: + show_help() + sys.exit(1) + + for o, a in opts: + if o in ("-j", "--job-file"): + fio_cfg = a + elif o in ("-h", "--help"): + show_help() + sys.exit(1) + elif o in ("-p", "--perf-vmex"): + perf_vmex = True + elif o in ("-o", "--out"): + out_dir = a + elif o in ("-f", "--fio-bin"): + fio_bin = a + elif o in ("-J", "--json"): + json = True + + if fio_cfg is None: + print("ERROR! No FIO job provided!") + sys.exit(1) + + if out_dir is None or not os.path.exists(out_dir): + print("ERROR! Folder {out_dir} does not exist ".format(out_dir=out_dir)) + sys.exit(1) + + # Get IP, port and fio 'filename' information from positional args + for arg in args[0].split(","): + _ = arg.split(":") + ip, port, filenames = _[0], _[1], ":".join(_[2:]) + vms.append((ip, port, filenames)) + + print("Running job file: {0}".format(fio_cfg)) + run_fio(vms, fio_cfg, out_dir, perf_vmex, json) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/spdk/test/vhost/common/run_vhost.sh b/src/spdk/test/vhost/common/run_vhost.sh new file mode 100755 index 00000000..bd6c496a --- /dev/null +++ b/src/spdk/test/vhost/common/run_vhost.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $BASE_DIR/../common && pwd)" +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)" + +vhost_num="" + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for running vhost app." + echo "Usage: $(basename $1) [-x] [-h|--help] [--clean-build] [--work-dir=PATH]" + echo "-h, --help print help and exit" + echo "-x Set -x for script debug" + echo " --work-dir=PATH Where to find source/project. [default=$TEST_DIR]" + echo " --conf-dir=PATH Path to directory with configuration for vhost" + echo " --vhost-num=NUM Optional: vhost instance NUM to start. Default: 0" + + exit 0 +} + +run_in_background=false +while getopts 'xh-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 ;; + work-dir=*) TEST_DIR="${OPTARG#*=}" ;; + conf-dir=*) CONF_DIR="${OPTARG#*=}" ;; + vhost-num=*) vhost_num="${OPTARG}" ;; + *) usage $0 echo "Invalid argument '$OPTARG'" ;; + esac + ;; + h) usage $0 ;; + x) set -x ;; + *) usage $0 "Invalid argument '$optchar'" ;; + esac +done + +if [[ $EUID -ne 0 ]]; then + fail "Go away user come back as root" +fi + +notice "$0" +notice "" + +. $COMMON_DIR/common.sh + +# Starting vhost with valid options +spdk_vhost_run $vhost_num --conf-path=$CONF_DIR diff --git a/src/spdk/test/vhost/common/vm_run.sh b/src/spdk/test/vhost/common/vm_run.sh new file mode 100755 index 00000000..03938f8c --- /dev/null +++ b/src/spdk/test/vhost/common/vm_run.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $BASE_DIR/../common && pwd)" +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)" + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for enabling VMs" + echo "Usage: $(basename $1) [OPTIONS] VM..." + echo + echo "-h, --help print help and exit" + echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: ./..]" + echo "-a Run all VMs in WORK_DIR" + echo "-x set -x for script debug" + exit 0 +} +run_all=false +while getopts 'xah-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 ;; + work-dir=*) TEST_DIR="${OPTARG#*=}" ;; + *) usage $0 "Invalid argument '$OPTARG'" ;; + esac + ;; + h) usage $0 ;; + a) run_all=true ;; + x) set -x ;; + *) usage $0 "Invalid argument '$OPTARG'" + esac +done + +. $COMMON_DIR/common.sh + +if [[ $EUID -ne 0 ]]; then + fail "Go away user come back as root" +fi + +if $run_all; then + vm_run -a +else + shift $((OPTIND-1)) + notice "running VMs: $@" + vm_run "$@" +fi diff --git a/src/spdk/test/vhost/common/vm_setup.sh b/src/spdk/test/vhost/common/vm_setup.sh new file mode 100755 index 00000000..7e3599fd --- /dev/null +++ b/src/spdk/test/vhost/common/vm_setup.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $BASE_DIR/../common && pwd)" +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)" + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for setting up VMs for tests" + echo "Usage: $(basename $1) [OPTIONS] VM_NUM" + echo + echo "-h, --help Print help and exit" + echo " --work-dir=WORK_DIR Where to find build file. Must exit. (default: $TEST_DIR)" + echo " --force=VM_NUM Force VM_NUM reconfiguration if already exist" + echo " --disk-type=TYPE Perform specified test:" + echo " virtio - test host virtio-scsi-pci using file as disk image" + echo " kernel_vhost - use kernel driver vhost-scsi" + echo " spdk_vhost_scsi - use spdk vhost scsi" + echo " spdk_vhost_blk - use spdk vhost block" + echo " --read-only=true|false Enable/Disable read only for vhost_blk tests" + echo " --raw-cache=CACHE Use CACHE for virtio test: " + echo " writethrough, writeback, none, unsafe or directsyns" + echo " --disk=PATH[,disk_type] Disk to use in test. test specific meaning:" + echo " virtio - disk path (file or block device ex: /dev/nvme0n1)" + echo " kernel_vhost - the WWN number to be used" + echo " spdk_vhost_[scsi|blk] - the socket path." + echo " optional disk_type - set disk type for disk (overwrites test-type)" + echo " e.g. /dev/nvme0n1,spdk_vhost_scsi" + echo " --os=OS_QCOW2 Custom OS qcow2 image file" + echo " --os-mode=MODE MODE how to use provided image: default: backing" + echo " backing - create new image but use provided backing file" + echo " copy - copy provided image and use a copy" + echo " orginal - use file directly. Will modify the provided file" + echo " --incoming=VM_NUM Use VM_NUM as source migration VM." + echo " --migrate-to=VM_NUM Use VM_NUM as target migration VM." + echo " --vhost-num=NUM Optional: vhost instance NUM to be used by this VM. Default: 0" + echo "-x Turn on script debug (set -x)" + echo "-v Be more verbose" + exit 0 +} + +setup_params=() +for param in "$@"; do + case "$param" in + --help|-h) usage $0 ;; + --work-dir=*) + TEST_DIR="${param#*=}" + continue + ;; + --raw-cache=*) ;; + --disk-type=*) ;; + --disks=*) ;; + --os=*) ;; + --os-mode=*) ;; + --force=*) ;; + --incoming=*) ;; + --migrate-to=*) ;; + --read-only=*) ;; + -x) + set -x + continue + ;; + -v) + SPDK_VHOST_VERBOSE=true + continue + ;; + *) usage $0 "Invalid argument '$param'" ;; + esac + + setup_params+=( "$param" ) +done + +. $COMMON_DIR/common.sh + +vm_setup ${setup_params[@]} + +trap -- ERR diff --git a/src/spdk/test/vhost/common/vm_shutdown.sh b/src/spdk/test/vhost/common/vm_shutdown.sh new file mode 100755 index 00000000..1de1170f --- /dev/null +++ b/src/spdk/test/vhost/common/vm_shutdown.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $BASE_DIR/../common && pwd)" +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)" + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for shutting down VMs" + echo "Usage: $(basename $1) [OPTIONS] [VMs]" + echo + echo "-h, --help print help and exit" + echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: ./..]" + echo "-a kill/shutdown all running VMs" + echo "-k kill instead of shutdown" + exit 0 +} +optspec='akh-:' +do_kill=false +all=false + +while getopts "$optspec" optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 ;; + work-dir=*) TEST_DIR="${OPTARG#*=}" ;; + *) usage $0 "Invalid argument '$OPTARG'" ;; + esac + ;; + h) usage $0 ;; + k) do_kill=true ;; + a) all=true ;; + *) usage $0 "Invalid argument '$OPTARG'" + esac +done + +. $COMMON_DIR/common.sh + +if $do_kill && [[ $EUID -ne 0 ]]; then + echo "Go away user come back as root" + exit 1 +fi + +if $all; then + if do_kill; then + notice "killing all VMs" + vm_kill_all + else + notice "shutting down all VMs" + vm_shutdown_all + fi +else + shift $((OPTIND-1)) + + if do_kill; then + notice "INFO: killing VMs: $@" + for vm in $@; do + vm_kill $vm + done + else + notice "shutting down all VMs" + vm_shutdown_all + fi +fi diff --git a/src/spdk/test/vhost/common/vm_ssh.sh b/src/spdk/test/vhost/common/vm_ssh.sh new file mode 100755 index 00000000..abdc3322 --- /dev/null +++ b/src/spdk/test/vhost/common/vm_ssh.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $BASE_DIR/../common && pwd)" +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)" + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for connecting to or executing command on selected VM" + echo "Usage: $(basename $1) [OPTIONS] VM_NUMBER" + echo + echo "-h, --help print help and exit" + echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: $TEST_DIR]" + echo "-w Don't wait for vm to boot" + echo "-x set -x for script debug" + exit 0 +} + +boot_wait=true +while getopts 'xwh-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 ;; + work-dir=*) TEST_DIR="${OPTARG#*=}" ;; + *) usage $0 "Invalid argument '$OPTARG'" ;; + esac ;; + h) usage $0 ;; + w) boot_wait=false ;; + x) set -x ;; + *) usage $0 "Invalid argument '$OPTARG'" ;; + esac +done + +. $COMMON_DIR/common.sh + +shift $((OPTIND-1)) +vm_num="$1" +shift + + +if ! vm_num_is_valid $vm_num; then + usage $0 "Invalid VM num $vm_num" + exit 1 +fi + +if $boot_wait; then + while ! vm_os_booted $vm_num; do + if ! vm_is_running $vm_num; then + fail "VM$vm_num is not running" + fi + notice "waiting for VM$vm_num to boot" + sleep 1 + done +fi + +vm_ssh $vm_num "$@" diff --git a/src/spdk/test/vhost/fiotest/autotest.sh b/src/spdk/test/vhost/fiotest/autotest.sh new file mode 100755 index 00000000..466ac141 --- /dev/null +++ b/src/spdk/test/vhost/fiotest/autotest.sh @@ -0,0 +1,247 @@ +#!/usr/bin/env bash +set -e +AUTOTEST_BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $AUTOTEST_BASE_DIR/../common && pwd)" +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $AUTOTEST_BASE_DIR/../../../../ && pwd)" + +dry_run=false +no_shutdown=false +fio_bin="" +remote_fio_bin="" +fio_jobs="" +test_type=spdk_vhost_scsi +reuse_vms=false +vms=() +used_vms="" +x="" + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for doing automated test" + echo "Usage: $(basename $1) [OPTIONS]" + echo + echo "-h, --help print help and exit" + echo " --test-type=TYPE Perform specified test:" + echo " virtio - test host virtio-scsi-pci using file as disk image" + echo " kernel_vhost - use kernel driver vhost-scsi" + echo " spdk_vhost_scsi - use spdk vhost scsi" + echo " spdk_vhost_blk - use spdk vhost block" + echo "-x set -x for script debug" + echo " --fio-bin=FIO Use specific fio binary (will be uploaded to VM)" + echo " --fio-job= Fio config to use for test." + echo " All VMs will run the same fio job when FIO executes." + echo " (no unique jobs for specific VMs)" + echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: $TEST_DIR]" + echo " --dry-run Don't perform any tests, run only and wait for enter to terminate" + echo " --no-shutdown Don't shutdown at the end but leave envirionment working" + echo " --vm=NUM[,OS][,DISKS] VM configuration. This parameter might be used more than once:" + echo " NUM - VM number (mandatory)" + echo " OS - VM os disk path (optional)" + echo " DISKS - VM os test disks/devices path (virtio - optional, kernel_vhost - mandatory)" + exit 0 +} + +#default raw file is NVMe drive + +while getopts 'xh-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 ;; + work-dir=*) TEST_DIR="${OPTARG#*=}" ;; + fio-bin=*) fio_bin="--fio-bin=${OPTARG#*=}" ;; + fio-job=*) fio_job="${OPTARG#*=}" ;; + dry-run) dry_run=true ;; + no-shutdown) no_shutdown=true ;; + test-type=*) test_type="${OPTARG#*=}" ;; + vm=*) vms+=("${OPTARG#*=}") ;; + *) usage $0 "Invalid argument '$OPTARG'" ;; + esac + ;; + h) usage $0 ;; + x) set -x + x="-x" ;; + *) usage $0 "Invalid argument '$OPTARG'" + esac +done +shift $(( OPTIND - 1 )) + +if [[ ! -r "$fio_job" ]]; then + fail "no fio job file specified" +fi + +. $COMMON_DIR/common.sh + +trap 'error_exit "${FUNCNAME}" "${LINENO}"' ERR + +vm_kill_all + +if [[ $test_type =~ "spdk_vhost" ]]; then + notice "===============" + notice "" + notice "running SPDK" + notice "" + spdk_vhost_run --json-path=$AUTOTEST_BASE_DIR + notice "" +fi + +notice "===============" +notice "" +notice "Setting up VM" +notice "" + +rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock" + +for vm_conf in ${vms[@]}; do + IFS=',' read -ra conf <<< "$vm_conf" + if [[ x"${conf[0]}" == x"" ]] || ! assert_number ${conf[0]}; then + fail "invalid VM configuration syntax $vm_conf" + fi + + # Sanity check if VM is not defined twice + for vm_num in $used_vms; do + if [[ $vm_num -eq ${conf[0]} ]]; then + fail "VM$vm_num defined more than twice ( $(printf "'%s' " "${vms[@]}"))!" + fi + done + + used_vms+=" ${conf[0]}" + + if [[ $test_type =~ "spdk_vhost" ]]; then + + notice "Adding device via RPC ..." + + while IFS=':' read -ra disks; do + for disk in "${disks[@]}"; do + if [[ "$test_type" == "spdk_vhost_blk" ]]; then + disk=${disk%%_*} + notice "Creating vhost block controller naa.$disk.${conf[0]} with device $disk" + $rpc_py construct_vhost_blk_controller naa.$disk.${conf[0]} $disk + else + notice "Creating controller naa.$disk.${conf[0]}" + $rpc_py construct_vhost_scsi_controller naa.$disk.${conf[0]} + + notice "Adding device (0) to naa.$disk.${conf[0]}" + $rpc_py add_vhost_scsi_lun naa.$disk.${conf[0]} 0 $disk + fi + done + done <<< "${conf[2]}" + unset IFS; + $rpc_py get_vhost_controllers + fi + + setup_cmd="vm_setup --force=${conf[0]} --disk-type=$test_type" + [[ x"${conf[1]}" != x"" ]] && setup_cmd+=" --os=${conf[1]}" + [[ x"${conf[2]}" != x"" ]] && setup_cmd+=" --disks=${conf[2]}" + + $setup_cmd +done + +# Run everything +vm_run $used_vms +vm_wait_for_boot 600 $used_vms + +if [[ $test_type == "spdk_vhost_scsi" ]]; then + for vm_conf in ${vms[@]}; do + IFS=',' read -ra conf <<< "$vm_conf" + while IFS=':' read -ra disks; do + for disk in "${disks[@]}"; do + notice "Hotdetach test. Trying to remove existing device from a controller naa.$disk.${conf[0]}" + $rpc_py remove_vhost_scsi_target naa.$disk.${conf[0]} 0 + + sleep 0.1 + + notice "Hotattach test. Re-adding device 0 to naa.$disk.${conf[0]}" + $rpc_py add_vhost_scsi_lun naa.$disk.${conf[0]} 0 $disk + done + done <<< "${conf[2]}" + unset IFS; + done +fi + +sleep 0.1 + +notice "===============" +notice "" +notice "Testing..." + +notice "Running fio jobs ..." + +# Check if all VM have disk in tha same location +DISK="" + +fio_disks="" +for vm_num in $used_vms; do + vm_dir=$VM_BASE_DIR/$vm_num + + qemu_mask_param="VM_${vm_num}_qemu_mask" + + host_name="VM-$vm_num" + notice "Setting up hostname: $host_name" + vm_ssh $vm_num "hostname $host_name" + vm_start_fio_server $fio_bin $readonly $vm_num + + if [[ "$test_type" == "spdk_vhost_scsi" ]]; then + vm_check_scsi_location $vm_num + #vm_reset_scsi_devices $vm_num $SCSI_DISK + elif [[ "$test_type" == "spdk_vhost_blk" ]]; then + vm_check_blk_location $vm_num + fi + + fio_disks+=" --vm=${vm_num}$(printf ':/dev/%s' $SCSI_DISK)" +done + +if $dry_run; then + read -p "Enter to kill evething" xx + sleep 3 + at_app_exit + exit 0 +fi + +run_fio $fio_bin --job-file="$fio_job" --out="$TEST_DIR/fio_results" $fio_disks + +if [[ "$test_type" == "spdk_vhost_scsi" ]]; then + for vm_num in $used_vms; do + vm_reset_scsi_devices $vm_num $SCSI_DISK + done +fi + +if ! $no_shutdown; then + notice "===============" + notice "APP EXITING" + notice "killing all VMs" + vm_shutdown_all + notice "waiting 2 seconds to let all VMs die" + sleep 2 + if [[ $test_type =~ "spdk_vhost" ]]; then + notice "Removing vhost devices & controllers via RPC ..." + for vm_conf in ${vms[@]}; do + IFS=',' read -ra conf <<< "$vm_conf" + + while IFS=':' read -ra disks; do + for disk in "${disks[@]}"; do + disk=${disk%%_*} + notice "Removing all vhost devices from controller naa.$disk.${conf[0]}" + if [[ "$test_type" == "spdk_vhost_scsi" ]]; then + $rpc_py remove_vhost_scsi_target naa.$disk.${conf[0]} 0 + fi + + $rpc_py remove_vhost_controller naa.$disk.${conf[0]} + done + done <<< "${conf[2]}" + done + fi + notice "Testing done -> shutting down" + notice "killing vhost app" + spdk_vhost_kill + + notice "EXIT DONE" + notice "===============" +else + notice "===============" + notice "" + notice "Leaving environment working!" + notice "" + notice "===============" +fi diff --git a/src/spdk/test/vhost/fiotest/conf.json b/src/spdk/test/vhost/fiotest/conf.json new file mode 100644 index 00000000..7a1594b2 --- /dev/null +++ b/src/spdk/test/vhost/fiotest/conf.json @@ -0,0 +1,80 @@ +{ + "subsystems": [ + { + "subsystem": "copy", + "config": null + }, + { + "subsystem": "interface", + "config": null + }, + { + "subsystem": "net_framework", + "config": null + }, + { + "subsystem": "bdev", + "config": [ + { + "params": { + "base_bdev": "Nvme0n1", + "split_size_mb": 0, + "split_count": 4 + }, + "method": "construct_split_vbdev" + }, + { + "params": { + "block_size": 4096, + "num_blocks": 32768 + }, + "method": "construct_malloc_bdev" + }, + { + "params": { + "block_size": 4096, + "num_blocks": 32768 + }, + "method": "construct_malloc_bdev" + } + ] + }, + { + "subsystem": "nbd", + "config": [] + }, + { + "subsystem": "scsi", + "config": null + }, + { + "subsystem": "vhost", + "config": [ + { + "params": { + "cpumask": "0x1", + "ctrlr": "vhost.0" + }, + "method": "construct_vhost_scsi_controller" + }, + { + "params": { + "scsi_target_num": 0, + "bdev_name": "Malloc0", + "ctrlr": "vhost.0" + }, + "method": "add_vhost_scsi_lun" + }, + { + "params": { + "dev_name": "Malloc1", + "readonly": true, + "ctrlr": "vhost.1", + "cpumask": "0x1" + }, + "method": "construct_vhost_blk_controller" + } + ] + } + ] +} diff --git a/src/spdk/test/vhost/hotplug/blk_hotremove.sh b/src/spdk/test/vhost/hotplug/blk_hotremove.sh new file mode 100644 index 00000000..a350d90d --- /dev/null +++ b/src/spdk/test/vhost/hotplug/blk_hotremove.sh @@ -0,0 +1,236 @@ +# Vhost blk hot remove tests +# +# Objective +# The purpose of these tests is to verify that SPDK vhost remains stable during +# hot-remove operations performed on SCSI and BLK controllers devices. +# Hot-remove is a scenario where a NVMe device is removed when already in use. +# +# Test cases description +# 1. FIO I/O traffic is run during hot-remove operations. +# By default FIO uses default_integrity*.job config files located in +# test/vhost/hotplug/fio_jobs directory. +# 2. FIO mode of operation is random write (randwrite) with verification enabled +# which results in also performing read operations. +# 3. In test cases fio status is checked after every run if any errors occurred. + +function prepare_fio_cmd_tc1() { + print_test_fio_header + + run_fio="$fio_bin --eta=never " + for vm_num in $1; do + cp $fio_job $tmp_detach_job + vm_dir=$VM_BASE_DIR/$vm_num + vm_check_blk_location $vm_num + for disk in $SCSI_DISK; do + echo "[nvme-host$disk]" >> $tmp_detach_job + echo "filename=/dev/$disk" >> $tmp_detach_job + done + vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/default_integrity_2discs.job + run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity_2discs.job " + rm $tmp_detach_job + done +} + +function remove_vhost_controllers() { + $rpc_py remove_vhost_controller naa.Nvme0n1p0.0 + $rpc_py remove_vhost_controller naa.Nvme0n1p1.0 + $rpc_py remove_vhost_controller naa.Nvme0n1p2.1 + $rpc_py remove_vhost_controller naa.Nvme0n1p3.1 +} + +# Vhost blk hot remove test cases +# +# Test Case 1 +function blk_hotremove_tc1() { + echo "Blk hotremove test case 1" + traddr="" + # 1. Run the command to hot remove NVMe disk. + get_traddr "Nvme0" + delete_nvme "Nvme0" + # 2. If vhost had crashed then tests would stop running + sleep 1 + add_nvme "HotInNvme0" "$traddr" + sleep 1 +} + +# Test Case 2 +function blk_hotremove_tc2() { + echo "Blk hotremove test case 2" + # 1. Use rpc command to create blk controllers. + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p0.0 HotInNvme0n1p0 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p1.0 Nvme1n1p0 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p2.1 Nvme1n1p1 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p3.1 Nvme1n1p2 + # 2. Run two VMs and attach every VM to two blk controllers. + vm_run_with_arg "0 1" + vms_prepare "0" + + traddr="" + get_traddr "Nvme0" + prepare_fio_cmd_tc1 "0" + # 3. Run FIO I/O traffic with verification enabled on NVMe disk. + $run_fio & + local last_pid=$! + sleep 3 + # 4. Run the command to hot remove NVMe disk. + delete_nvme "HotInNvme0" + local retcode=0 + wait_for_finish $last_pid || retcode=$? + # 5. Check that fio job run on hot-removed device stopped. + # Expected: Fio should return error message and return code != 0. + check_fio_retcode "Blk hotremove test case 2: Iteration 1." 1 $retcode + + # 6. Reboot VM + reboot_all_and_prepare "0" + # 7. Run FIO I/O traffic with verification enabled on NVMe disk. + $run_fio & + local retcode=0 + wait_for_finish $! || retcode=$? + # 8. Check that fio job run on hot-removed device stopped. + # Expected: Fio should return error message and return code != 0. + check_fio_retcode "Blk hotremove test case 2: Iteration 2." 1 $retcode + vm_shutdown_all + remove_vhost_controllers + add_nvme "HotInNvme1" "$traddr" + sleep 1 +} + +# ## Test Case 3 +function blk_hotremove_tc3() { + echo "Blk hotremove test case 3" + # 1. Use rpc command to create blk controllers. + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p0.0 HotInNvme1n1p0 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p1.0 Nvme1n1p0 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p2.1 HotInNvme1n1p1 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p3.1 Nvme1n1p1 + # 2. Run two VMs and attach every VM to two blk controllers. + vm_run_with_arg "0 1" + vms_prepare "0 1" + + traddr="" + get_traddr "Nvme0" + prepare_fio_cmd_tc1 "0" + # 3. Run FIO I/O traffic with verification enabled on first NVMe disk. + $run_fio & + local last_pid=$! + sleep 3 + # 4. Run the command to hot remove of first NVMe disk. + delete_nvme "HotInNvme1" + local retcode=0 + wait_for_finish $last_pid || retcode=$? + # 6. Check that fio job run on hot-removed device stopped. + # Expected: Fio should return error message and return code != 0. + check_fio_retcode "Blk hotremove test case 3: Iteration 1." 1 $retcode + + # 7. Reboot VM + reboot_all_and_prepare "0" + local retcode=0 + # 8. Run FIO I/O traffic with verification enabled on removed NVMe disk. + $run_fio & + wait_for_finish $! || retcode=$? + # 9. Check that fio job run on hot-removed device stopped. + # Expected: Fio should return error message and return code != 0. + check_fio_retcode "Blk hotremove test case 3: Iteration 2." 1 $retcode + vm_shutdown_all + remove_vhost_controllers + add_nvme "HotInNvme2" "$traddr" + sleep 1 +} + +# Test Case 4 +function blk_hotremove_tc4() { + echo "Blk hotremove test case 4" + # 1. Use rpc command to create blk controllers. + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p0.0 HotInNvme2n1p0 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p1.0 Nvme1n1p0 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p2.1 HotInNvme2n1p1 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p3.1 Nvme1n1p1 + # 2. Run two VM, attached to blk controllers. + vm_run_with_arg "0 1" + vms_prepare "0 1" + + prepare_fio_cmd_tc1 "0" + # 3. Run FIO I/O traffic on first VM with verification enabled on both NVMe disks. + $run_fio & + local last_pid_vm0=$! + + prepare_fio_cmd_tc1 "1" + # 4. Run FIO I/O traffic on second VM with verification enabled on both NVMe disks. + $run_fio & + local last_pid_vm1=$! + + sleep 3 + prepare_fio_cmd_tc1 "0 1" + # 5. Run the command to hot remove of first NVMe disk. + delete_nvme "HotInNvme2" + local retcode_vm0=0 + local retcode_vm1=0 + wait_for_finish $last_pid_vm0 || retcode_vm0=$? + wait_for_finish $last_pid_vm1 || retcode_vm1=$? + # 6. Check that fio job run on hot-removed device stopped. + # Expected: Fio should return error message and return code != 0. + check_fio_retcode "Blk hotremove test case 4: Iteration 1." 1 $retcode_vm0 + check_fio_retcode "Blk hotremove test case 4: Iteration 2." 1 $retcode_vm1 + + # 7. Reboot all VMs. + reboot_all_and_prepare "0 1" + # 8. Run FIO I/O traffic with verification enabled on removed NVMe disk. + $run_fio & + local retcode=0 + wait_for_finish $! || retcode=$? + # 9. Check that fio job run on hot-removed device stopped. + # Expected: Fio should return error message and return code != 0. + check_fio_retcode "Blk hotremove test case 4: Iteration 3." 1 $retcode + + vm_shutdown_all + remove_vhost_controllers + add_nvme "HotInNvme3" "$traddr" + sleep 1 +} + +# Test Case 5 +function blk_hotremove_tc5() { + echo "Blk hotremove test case 5" + # 1. Use rpc command to create blk controllers. + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p0.0 HotInNvme3n1p0 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p1.0 Nvme1n1p0 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p2.1 Nvme1n1p1 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p3.1 Nvme1n1p2 + # 2. Run two VM, attached to blk controllers. + vm_run_with_arg "0 1" + vms_prepare "0 1" + + prepare_fio_cmd_tc1 "0" + # 3. Run FIO I/O traffic on first VM with verification enabled on both NVMe disks. + $run_fio & + local last_pid=$! + sleep 3 + # 4. Run the command to hot remove of first NVMe disk. + delete_nvme "HotInNvme3" + local retcode=0 + wait_for_finish $last_pid || retcode=$? + # 5. Check that fio job run on hot-removed device stopped. + # Expected: Fio should return error message and return code != 0. + check_fio_retcode "Blk hotremove test case 5: Iteration 1." 1 $retcode + + # 6. Reboot VM. + reboot_all_and_prepare "0" + local retcode=0 + # 7. Run FIO I/O traffic with verification enabled on removed NVMe disk. + $run_fio & + wait_for_finish $! || retcode=$? + # 8. Check that fio job run on hot-removed device stopped. + # Expected: Fio should return error message and return code != 0. + check_fio_retcode "Blk hotremove test case 5: Iteration 2." 1 $retcode + vm_shutdown_all + remove_vhost_controllers + add_nvme "HotInNvme4" "$traddr" + sleep 1 +} + +vms_setup +blk_hotremove_tc1 +blk_hotremove_tc2 +blk_hotremove_tc3 +blk_hotremove_tc4 +blk_hotremove_tc5 diff --git a/src/spdk/test/vhost/hotplug/common.sh b/src/spdk/test/vhost/hotplug/common.sh new file mode 100644 index 00000000..a94b06cf --- /dev/null +++ b/src/spdk/test/vhost/hotplug/common.sh @@ -0,0 +1,230 @@ +dry_run=false +no_shutdown=false +fio_bin="fio" +fio_jobs="$BASE_DIR/fio_jobs/" +test_type=spdk_vhost_scsi +reuse_vms=false +vms=() +used_vms="" +disk_split="" +x="" +scsi_hot_remove_test=0 +blk_hot_remove_test=0 + + +function usage() { + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for doing automated hotattach/hotdetach test" + echo "Usage: $(basename $1) [OPTIONS]" + echo + echo "-h, --help print help and exit" + echo " --test-type=TYPE Perform specified test:" + echo " virtio - test host virtio-scsi-pci using file as disk image" + echo " kernel_vhost - use kernel driver vhost-scsi" + echo " spdk_vhost_scsi - use spdk vhost scsi" + echo " spdk_vhost_blk - use spdk vhost block" + echo "-x set -x for script debug" + echo " --fio-bin=FIO Use specific fio binary (will be uploaded to VM)" + echo " --fio-jobs= Fio configs to use for tests. Can point to a directory or" + echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: $TEST_DIR]" + echo " --vm=NUM[,OS][,DISKS] VM configuration. This parameter might be used more than once:" + echo " NUM - VM number (mandatory)" + echo " OS - VM os disk path (optional)" + echo " DISKS - VM os test disks/devices path (virtio - optional, kernel_vhost - mandatory)" + echo " --scsi-hotremove-test Run scsi hotremove tests" + exit 0 +} + +while getopts 'xh-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 ;; + work-dir=*) TEST_DIR="${OPTARG#*=}" ;; + fio-bin=*) fio_bin="${OPTARG#*=}" ;; + fio-jobs=*) fio_jobs="${OPTARG#*=}" ;; + test-type=*) test_type="${OPTARG#*=}" ;; + vm=*) vms+=("${OPTARG#*=}") ;; + scsi-hotremove-test) scsi_hot_remove_test=1 ;; + blk-hotremove-test) blk_hot_remove_test=1 ;; + *) usage $0 "Invalid argument '$OPTARG'" ;; + esac + ;; + h) usage $0 ;; + x) set -x + x="-x" ;; + *) usage $0 "Invalid argument '$OPTARG'" + esac +done +shift $(( OPTIND - 1 )) + +fio_job=$BASE_DIR/fio_jobs/default_integrity.job +tmp_attach_job=$BASE_DIR/fio_jobs/fio_attach.job.tmp +tmp_detach_job=$BASE_DIR/fio_jobs/fio_detach.job.tmp +. $BASE_DIR/../common/common.sh + +rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock" + +function print_test_fio_header() { + notice "===============" + notice "" + notice "Testing..." + + notice "Running fio jobs ..." + if [ $# -gt 0 ]; then + echo $1 + fi +} + +function run_vhost() { + notice "===============" + notice "" + notice "running SPDK" + notice "" + spdk_vhost_run --conf-path=$BASE_DIR + notice "" +} + +function vms_setup() { + for vm_conf in ${vms[@]}; do + IFS=',' read -ra conf <<< "$vm_conf" + if [[ x"${conf[0]}" == x"" ]] || ! assert_number ${conf[0]}; then + fail "invalid VM configuration syntax $vm_conf" + fi + + # Sanity check if VM is not defined twice + for vm_num in $used_vms; do + if [[ $vm_num -eq ${conf[0]} ]]; then + fail "VM$vm_num defined more than twice ( $(printf "'%s' " "${vms[@]}"))!" + fi + done + + used_vms+=" ${conf[0]}" + + setup_cmd="vm_setup --disk-type=$test_type --force=${conf[0]}" + [[ x"${conf[1]}" != x"" ]] && setup_cmd+=" --os=${conf[1]}" + [[ x"${conf[2]}" != x"" ]] && setup_cmd+=" --disks=${conf[2]}" + $setup_cmd + done +} + +function vm_run_with_arg() { + vm_run $@ + vm_wait_for_boot 600 $@ +} + +function vms_setup_and_run() { + vms_setup + vm_run_with_arg $@ +} + +function vms_prepare() { + for vm_num in $1; do + vm_dir=$VM_BASE_DIR/$vm_num + + qemu_mask_param="VM_${vm_num}_qemu_mask" + + host_name="VM-${vm_num}-${!qemu_mask_param}" + notice "Setting up hostname: $host_name" + vm_ssh $vm_num "hostname $host_name" + vm_start_fio_server --fio-bin=$fio_bin $readonly $vm_num + done +} + +function vms_reboot_all() { + notice "Rebooting all vms " + for vm_num in $1; do + vm_ssh $vm_num "reboot" || true + while vm_os_booted $vm_num; do + sleep 0.5 + done + done + + vm_wait_for_boot 300 $1 +} + +function check_fio_retcode() { + local fio_retcode=$3 + echo $1 + local retcode_expected=$2 + if [ $retcode_expected == 0 ]; then + if [ $fio_retcode != 0 ]; then + error " Fio test ended with error." + else + notice " Fio test ended with success." + fi + else + if [ $fio_retcode != 0 ]; then + notice " Fio test ended with expected error." + else + error " Fio test ended with unexpected success." + fi + fi +} + +function wait_for_finish() { + local wait_for_pid=$1 + local sequence=${2:-30} + for i in `seq 1 $sequence`; do + if kill -0 $wait_for_pid; then + sleep 0.5 + continue + else + break + fi + done + if kill -0 $wait_for_pid; then + error "Timeout for fio command" + fi + + wait $wait_for_pid +} + + +function reboot_all_and_prepare() { + vms_reboot_all "$1" + vms_prepare "$1" +} + +function post_test_case() { + vm_shutdown_all + spdk_vhost_kill +} + +function on_error_exit() { + set +e + echo "Error on $1 - $2" + post_test_case + print_backtrace + exit 1 +} + +function check_disks() { + if [ "$1" == "$2" ]; then + echo "Disk has not been deleted" + exit 1 + fi +} + +function get_traddr() { + local nvme_name=$1 + local nvme="$( $SPDK_BUILD_DIR/scripts/gen_nvme.sh )" + while read -r line; do + if [[ $line == *"TransportID"* ]] && [[ $line == *$nvme_name* ]]; then + local word_array=($line) + for word in "${word_array[@]}"; do + if [[ $word == *"traddr"* ]]; then + traddr=$( echo $word | sed 's/traddr://' | sed 's/"//' ) + fi + done + fi + done <<< "$nvme" +} + +function delete_nvme() { + $rpc_py delete_nvme_controller $1 +} + +function add_nvme() { + $rpc_py construct_nvme_bdev -b $1 -t PCIe -a $2 +} diff --git a/src/spdk/test/vhost/hotplug/fio_jobs/default_integrity.job b/src/spdk/test/vhost/hotplug/fio_jobs/default_integrity.job new file mode 100644 index 00000000..136fe902 --- /dev/null +++ b/src/spdk/test/vhost/hotplug/fio_jobs/default_integrity.job @@ -0,0 +1,16 @@ +[global] +blocksize=4k +iodepth=512 +iodepth_batch=128 +iodepth_low=256 +ioengine=libaio +group_reporting +thread +numjobs=1 +direct=1 +rw=randwrite +do_verify=1 +verify=md5 +verify_backlog=1024 +time_based=1 +runtime=10 diff --git a/src/spdk/test/vhost/hotplug/scsi_hotattach.sh b/src/spdk/test/vhost/hotplug/scsi_hotattach.sh new file mode 100755 index 00000000..e9d851f6 --- /dev/null +++ b/src/spdk/test/vhost/hotplug/scsi_hotattach.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +set -e +BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)" + +. $BASE_DIR/common.sh + +function prepare_fio_cmd_tc1() { + print_test_fio_header + + run_fio="$fio_bin --eta=never " + for vm_num in $1; do + cp $fio_job $tmp_attach_job + vm_dir=$VM_BASE_DIR/$vm_num + vm_check_scsi_location $vm_num + for disk in $SCSI_DISK; do + echo "[nvme-host$disk]" >> $tmp_attach_job + echo "filename=/dev/$disk" >> $tmp_attach_job + done + vm_scp $vm_num $tmp_attach_job 127.0.0.1:/root/default_integrity_discs.job + run_fio+="--client=127.0.0.1,$(vm_fio_socket ${vm_num}) --remote-config /root/default_integrity_discs.job " + rm $tmp_attach_job + done +} + +# Check if fio test passes on device attached to first controller. +function hotattach_tc1() { + notice "Hotattach test case 1" + + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 0 Nvme0n1p0 + + sleep 3 + prepare_fio_cmd_tc1 "0" + $run_fio + check_fio_retcode "Hotattach test case 1: Iteration 1." 0 $? +} + +# Run fio test for previously attached device. +# During test attach another device to first controller and check fio status. +function hotattach_tc2() { + notice "Hotattach test case 2" + prepare_fio_cmd_tc1 "0" + + $run_fio & + last_pid=$! + sleep 3 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 1 Nvme0n1p1 + wait $last_pid + check_fio_retcode "Hotattach test case 2: Iteration 1." 0 $? +} + +# Run fio test for previously attached devices. +# During test attach another device to second controller and check fio status. +function hotattach_tc3() { + notice "Hotattach test case 3" + prepare_fio_cmd_tc1 "0" + + $run_fio & + last_pid=$! + sleep 3 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p1.0 0 Nvme0n1p2 + wait $last_pid + check_fio_retcode "Hotattach test case 3: Iteration 1." 0 $? +} + +# Run fio test for previously attached devices. +# During test attach another device to third controller(VM2) and check fio status. +# At the end after rebooting VMs run fio test for all devices and check fio status. +function hotattach_tc4() { + notice "Hotattach test case 4" + + prepare_fio_cmd_tc1 "0" + + $run_fio & + last_pid=$! + sleep 3 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p2.1 0 Nvme0n1p3 + wait $last_pid + check_fio_retcode "Hotattach test case 4: Iteration 1." 0 $? + + prepare_fio_cmd_tc1 "0 1" + $run_fio + check_fio_retcode "Hotattach test case 4: Iteration 2." 0 $? + + reboot_all_and_prepare "0 1" + + prepare_fio_cmd_tc1 "0 1" + $run_fio + check_fio_retcode "Hotattach test case 4: Iteration 3." 0 $? +} + +function cleanup_after_tests() { + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p0.0 0 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p0.0 1 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p1.0 0 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p2.1 0 + $rpc_py delete_nvme_controller Nvme0 +} + +hotattach_tc1 +hotattach_tc2 +hotattach_tc3 +hotattach_tc4 +cleanup_after_tests diff --git a/src/spdk/test/vhost/hotplug/scsi_hotdetach.sh b/src/spdk/test/vhost/hotplug/scsi_hotdetach.sh new file mode 100755 index 00000000..45c948d9 --- /dev/null +++ b/src/spdk/test/vhost/hotplug/scsi_hotdetach.sh @@ -0,0 +1,241 @@ +#!/usr/bin/env bash + +set -e +BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)" + +. $BASE_DIR/common.sh + +function get_first_disk() { + vm_check_scsi_location $1 + disk_array=( $SCSI_DISK ) + eval "$2=${disk_array[0]}" +} + +function check_disks() { + if [ "$1" == "$2" ]; then + fail "Disk has not been deleted" + fi +} + +function prepare_fio_cmd_tc1_iter1() { + print_test_fio_header + + run_fio="$fio_bin --eta=never " + for vm_num in $1; do + cp $fio_job $tmp_detach_job + vm_dir=$VM_BASE_DIR/$vm_num + vm_check_scsi_location $vm_num + for disk in $SCSI_DISK; do + echo "[nvme-host$disk]" >> $tmp_detach_job + echo "filename=/dev/$disk" >> $tmp_detach_job + done + vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/default_integrity_4discs.job + run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity_4discs.job " + rm $tmp_detach_job + done +} + +function prepare_fio_cmd_tc1_iter2() { + print_test_fio_header + + for vm_num in 2; do + cp $fio_job $tmp_detach_job + vm_dir=$VM_BASE_DIR/$vm_num + vm_check_scsi_location $vm_num + for disk in $SCSI_DISK; do + echo "[nvme-host$disk]" >> $tmp_detach_job + echo "filename=/dev/$disk" >> $tmp_detach_job + done + vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/default_integrity_3discs.job + rm $tmp_detach_job + done + run_fio="$fio_bin --eta=never " + for vm_num in $used_vms; do + if [ $vm_num == 2 ]; then + run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity_3discs.job " + continue + fi + run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity_4discs.job " + done +} + +function prepare_fio_cmd_tc2_iter1() { + print_test_fio_header + + run_fio="$fio_bin --eta=never " + for vm_num in $1; do + cp $fio_job $tmp_detach_job + vm_dir=$VM_BASE_DIR/$vm_num + vm_check_scsi_location $vm_num + disk_array=($SCSI_DISK) + disk=${disk_array[0]} + echo "[nvme-host$disk]" >> $tmp_detach_job + echo "filename=/dev/$disk" >> $tmp_detach_job + vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/default_integrity.job + run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity.job " + rm $tmp_detach_job + done +} + +function prepare_fio_cmd_tc2_iter2() { + print_test_fio_header + + run_fio="$fio_bin --eta=never " + for vm_num in $1; do + cp $fio_job $tmp_detach_job + if [ $vm_num == 2 ]; then + vm_job_name=default_integrity_3discs.job + else + vm_job_name=default_integrity_4discs.job + fi + vm_dir=$VM_BASE_DIR/$vm_num + vm_check_scsi_location $vm_num + for disk in $SCSI_DISK; do + echo "[nvme-host$disk]" >> $tmp_detach_job + echo "filename=/dev/$disk" >> $tmp_detach_job + done + vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/$vm_job_name + run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/${vm_job_name} " + rm $tmp_detach_job + done +} + + +function prepare_fio_cmd_tc3_iter1() { + print_test_fio_header + + run_fio="$fio_bin --eta=never " + for vm_num in $1; do + cp $fio_job $tmp_detach_job + if [ $vm_num == 2 ]; then + vm_job_name=default_integrity_3discs.job + else + vm_job_name=default_integrity_4discs.job + fi + vm_dir=$VM_BASE_DIR/$vm_num + vm_check_scsi_location $vm_num + j=1 + for disk in $SCSI_DISK; do + if [ $vm_num == 2 ]; then + if [ $j == 1 ]; then + (( j++ )) + continue + fi + fi + echo "[nvme-host$disk]" >> $tmp_detach_job + echo "filename=/dev/$disk" >> $tmp_detach_job + (( j++ )) + done + vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/$vm_job_name + run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/$vm_job_name " + rm $tmp_detach_job + done +} + +# During fio test for all devices remove first device from fifth controller and check if fio fails. +# Also check if disc has been removed from VM. +function hotdetach_tc1() { + notice "Hotdetach test case 1" + first_disk="" + get_first_disk "2" first_disk + prepare_fio_cmd_tc1_iter1 "2 3" + $run_fio & + last_pid=$! + sleep 3 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 0 + set +xe + wait $last_pid + check_fio_retcode "Hotdetach test case 1: Iteration 1." 1 $? + set -xe + second_disk="" + get_first_disk "2" second_disk + check_disks $first_disk $second_disk + clear_after_tests +} + +# During fio test for device from third VM remove first device from fifth controller and check if fio fails. +# Also check if disc has been removed from VM. +function hotdetach_tc2() { + notice "Hotdetach test case 2" + sleep 2 + first_disk="" + get_first_disk "2" first_disk + prepare_fio_cmd_tc2_iter1 "2" + $run_fio & + last_pid=$! + sleep 3 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 0 + set +xe + wait $last_pid + check_fio_retcode "Hotdetach test case 2: Iteration 1." 1 $? + set -xe + second_disk="" + get_first_disk "2" second_disk + check_disks $first_disk $second_disk + clear_after_tests +} + +# Run fio test for all devices except one, then remove this device and check if fio passes. +# Also check if disc has been removed from VM. +function hotdetach_tc3() { + notice "Hotdetach test case 3" + sleep 2 + first_disk="" + get_first_disk "2" first_disk + prepare_fio_cmd_tc3_iter1 "2 3" + $run_fio & + last_pid=$! + sleep 3 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 0 + wait $last_pid + check_fio_retcode "Hotdetach test case 3: Iteration 1." 0 $? + second_disk="" + get_first_disk "2" second_disk + check_disks $first_disk $second_disk + clear_after_tests +} + +# Run fio test for all devices except one and run separate fio test for this device. +# Check if first fio test passes and second fio test fails. +# Also check if disc has been removed from VM. +# After reboot run fio test for remaining devices and check if fio passes. +function hotdetach_tc4() { + notice "Hotdetach test case 4" + sleep 2 + first_disk="" + get_first_disk "2" first_disk + prepare_fio_cmd_tc2_iter1 "2" + $run_fio & + first_fio_pid=$! + prepare_fio_cmd_tc3_iter1 "2 3" + $run_fio & + second_fio_pid=$! + sleep 3 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 0 + set +xe + wait $first_fio_pid + check_fio_retcode "Hotdetach test case 4: Iteration 1." 1 $? + set -xe + wait $second_fio_pid + check_fio_retcode "Hotdetach test case 4: Iteration 2." 0 $? + second_disk="" + get_first_disk "2" second_disk + check_disks $first_disk $second_disk + + reboot_all_and_prepare "2 3" + sleep 2 + prepare_fio_cmd_tc2_iter2 "2 3" + $run_fio + check_fio_retcode "Hotdetach test case 4: Iteration 3." 0 $? + clear_after_tests +} + +function clear_after_tests() { + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p4.2 0 Nvme0n1p8 +} + +hotdetach_tc1 +hotdetach_tc2 +hotdetach_tc3 +hotdetach_tc4 diff --git a/src/spdk/test/vhost/hotplug/scsi_hotplug.sh b/src/spdk/test/vhost/hotplug/scsi_hotplug.sh new file mode 100755 index 00000000..ab429c1e --- /dev/null +++ b/src/spdk/test/vhost/hotplug/scsi_hotplug.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -e +BASE_DIR=$(readlink -f $(dirname $0)) +. $BASE_DIR/common.sh + +if [[ $scsi_hot_remove_test == 1 ]] && [[ $blk_hot_remove_test == 1 ]]; then + notice "Vhost-scsi and vhost-blk hotremove tests cannot be run together" +fi + +# Add split section into vhost config +function gen_config() { + cp $BASE_DIR/vhost.conf.base $BASE_DIR/vhost.conf.in + cat << END_OF_CONFIG >> $BASE_DIR/vhost.conf.in +[Split] + Split Nvme0n1 16 + Split Nvme1n1 20 + Split HotInNvme0n1 2 + Split HotInNvme1n1 2 + Split HotInNvme2n1 2 + Split HotInNvme3n1 2 +END_OF_CONFIG +} + +# Run spdk by calling run_vhost from hotplug/common.sh. +# Then prepare vhost with rpc calls and setup and run 4 VMs. +function pre_hot_attach_detach_test_case() { + used_vms="" + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p0.0 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p1.0 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p2.1 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p3.1 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p4.2 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p5.2 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p6.3 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p7.3 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p4.2 0 Nvme0n1p8 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p4.2 1 Nvme0n1p9 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p5.2 0 Nvme0n1p10 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p5.2 1 Nvme0n1p11 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p6.3 0 Nvme0n1p12 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p6.3 1 Nvme0n1p13 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p7.3 0 Nvme0n1p14 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p7.3 1 Nvme0n1p15 + vms_setup_and_run "0 1 2 3" + vms_prepare "0 1 2 3" +} + +function clear_vhost_config() { + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 0 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 1 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p5.2 0 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p5.2 1 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p6.3 0 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p6.3 1 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p7.3 0 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p7.3 1 + $rpc_py remove_vhost_controller naa.Nvme0n1p0.0 + $rpc_py remove_vhost_controller naa.Nvme0n1p1.0 + $rpc_py remove_vhost_controller naa.Nvme0n1p2.1 + $rpc_py remove_vhost_controller naa.Nvme0n1p3.1 + $rpc_py remove_vhost_controller naa.Nvme0n1p4.2 + $rpc_py remove_vhost_controller naa.Nvme0n1p5.2 + $rpc_py remove_vhost_controller naa.Nvme0n1p6.3 + $rpc_py remove_vhost_controller naa.Nvme0n1p7.3 +} + +trap 'error_exit "${FUNCNAME}" "${LINENO}"' ERR +gen_config +# Hotremove/hotattach/hotdetach test cases prerequisites +# 1. Run vhost with 2 NVMe disks. +run_vhost +rm $BASE_DIR/vhost.conf.in +if [[ $scsi_hot_remove_test == 0 ]] && [[ $blk_hot_remove_test == 0 ]]; then + pre_hot_attach_detach_test_case + $BASE_DIR/scsi_hotattach.sh --fio-bin=$fio_bin & + first_script=$! + $BASE_DIR/scsi_hotdetach.sh --fio-bin=$fio_bin & + second_script=$! + wait $first_script + wait $second_script + vm_shutdown_all + clear_vhost_config +fi +if [[ $scsi_hot_remove_test == 1 ]]; then + source $BASE_DIR/scsi_hotremove.sh +fi +if [[ $blk_hot_remove_test == 1 ]]; then + source $BASE_DIR/blk_hotremove.sh +fi +post_test_case diff --git a/src/spdk/test/vhost/hotplug/scsi_hotremove.sh b/src/spdk/test/vhost/hotplug/scsi_hotremove.sh new file mode 100644 index 00000000..829eb3f6 --- /dev/null +++ b/src/spdk/test/vhost/hotplug/scsi_hotremove.sh @@ -0,0 +1,232 @@ +set -xe + +# Vhost SCSI hotremove tests +# +# # Objective +# The purpose of these tests is to verify that SPDK vhost remains stable during +# hot-remove operations performed on SCSI controllers devices. +# Hot-remove is a scenario where a NVMe device is removed when already in use. +# Tests consist of 4 test cases. +# +# # Test cases description +# 1. FIO I/O traffic is run during hot-remove operations. +# By default FIO uses default_integrity*.job config files located in +# test/vhost/hotplug/fio_jobs directory. +# 2. FIO mode of operation is random write (randwrite) with verification enabled +# which results in also performing read operations. + +function prepare_fio_cmd_tc1() { + print_test_fio_header + + run_fio="$fio_bin --eta=never " + for vm_num in $1; do + cp $fio_job $tmp_detach_job + vm_dir=$VM_BASE_DIR/$vm_num + vm_check_scsi_location $vm_num + for disk in $SCSI_DISK; do + echo "[nvme-host$disk]" >> $tmp_detach_job + echo "filename=/dev/$disk" >> $tmp_detach_job + echo "size=100%" >> $tmp_detach_job + done + vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/default_integrity_2discs.job + run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity_2discs.job " + rm $tmp_detach_job + done +} + +# Vhost SCSI hot-remove test cases. + +# Test Case 1 +function scsi_hotremove_tc1() { + echo "Scsi hotremove test case 1" + traddr="" + get_traddr "Nvme0" + # 1. Run the command to hot remove NVMe disk. + delete_nvme "Nvme0" + # 2. If vhost had crashed then tests would stop running + sleep 1 + add_nvme "HotInNvme0" "$traddr" +} + +# Test Case 2 +function scsi_hotremove_tc2() { + echo "Scsi hotremove test case 2" + # 1. Attach split NVMe bdevs to scsi controller. + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 0 HotInNvme0n1p0 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p1.0 0 Nvme1n1p0 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p2.1 0 HotInNvme0n1p1 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p3.1 0 Nvme1n1p1 + + # 2. Run two VMs, attached to scsi controllers. + vms_setup + vm_run_with_arg 0 1 + vms_prepare "0 1" + + vm_check_scsi_location "0" + local disks="$SCSI_DISK" + + traddr="" + get_traddr "Nvme0" + prepare_fio_cmd_tc1 "0 1" + # 3. Run FIO I/O traffic with verification enabled on on both NVMe disks in VM. + $run_fio & + local last_pid=$! + sleep 3 + # 4. Run the command to hot remove NVMe disk. + delete_nvme "HotInNvme0" + + # 5. Check that fio job run on hot-remove device stopped on VM. + # Expected: Fio should return error message and return code != 0. + wait_for_finish $last_pid || retcode=$? + check_fio_retcode "Scsi hotremove test case 2: Iteration 1." 1 $retcode + + # 6. Check if removed devices are gone from VM. + vm_check_scsi_location "0" + local new_disks="$SCSI_DISK" + check_disks "$disks" "$new_disks" + # 7. Reboot both VMs. + reboot_all_and_prepare "0 1" + # 8. Run FIO I/O traffic with verification enabled on on both VMs. + local retcode=0 + $run_fio & + wait_for_finish $! || retcode=$? + # 9. Check that fio job run on hot-remove device stopped on both VMs. + # Expected: Fio should return error message and return code != 0. + check_fio_retcode "Scsi hotremove test case 2: Iteration 2." 1 $retcode + vm_shutdown_all + add_nvme "HotInNvme1" "$traddr" + sleep 1 +} + +# Test Case 3 +function scsi_hotremove_tc3() { + echo "Scsi hotremove test case 3" + # 1. Attach added NVMe bdev to scsi controller. + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 0 HotInNvme1n1p0 + # 2. Run two VM, attached to scsi controllers. + vm_run_with_arg 0 1 + vms_prepare "0 1" + vm_check_scsi_location "0" + local disks="$SCSI_DISK" + traddr="" + get_traddr "Nvme0" + # 3. Run FIO I/O traffic with verification enabled on on both NVMe disks in VMs. + prepare_fio_cmd_tc1 "0" + $run_fio & + local last_pid=$! + sleep 3 + # 4. Run the command to hot remove NVMe disk. + delete_nvme "HotInNvme1" + # 5. Check that fio job run on hot-remove device stopped on first VM. + # Expected: Fio should return error message and return code != 0. + wait_for_finish $last_pid || retcode=$? + check_fio_retcode "Scsi hotremove test case 3: Iteration 1." 1 $retcode + # 6. Check if removed devices are gone from lsblk. + vm_check_scsi_location "0" + local new_disks="$SCSI_DISK" + check_disks "$disks" "$new_disks" + # 7. Reboot both VMs. + reboot_all_and_prepare "0 1" + # 8. Run FIO I/O traffic with verification enabled on on both VMs. + local retcode=0 + $run_fio & + wait_for_finish $! || retcode=$? + # 9. Check that fio job run on hot-remove device stopped on both VMs. + # Expected: Fio should return error message and return code != 0. + check_fio_retcode "Scsi hotremove test case 3: Iteration 2." 1 $retcode + vm_shutdown_all + add_nvme "HotInNvme2" "$traddr" + sleep 1 +} + +# Test Case 4 +function scsi_hotremove_tc4() { + echo "Scsi hotremove test case 4" + # 1. Attach NVMe bdevs to scsi controllers. + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 0 HotInNvme2n1p0 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p2.1 0 HotInNvme2n1p1 + # 2. Run two VMs, attach to scsi controller. + vm_run_with_arg 0 1 + vms_prepare "0 1" + + # 3. Run FIO I/O traffic with verification enabled on first VM. + vm_check_scsi_location "0" + local disks_vm0="$SCSI_DISK" + # 4. Run FIO I/O traffic with verification enabled on second VM. + prepare_fio_cmd_tc1 "0" + $run_fio & + last_pid_vm0=$! + + vm_check_scsi_location "1" + local disks_vm1="$SCSI_DISK" + prepare_fio_cmd_tc1 "1" + $run_fio & + local last_pid_vm1=$! + prepare_fio_cmd_tc1 "0 1" + sleep 3 + # 5. Run the command to hot remove NVMe disk. + traddr="" + get_traddr "Nvme0" + delete_nvme "HotInNvme2" + # 6. Check that fio job run on hot-removed devices stopped. + # Expected: Fio should return error message and return code != 0. + local retcode_vm0=0 + wait_for_finish $last_pid_vm0 || retcode_vm0=$? + local retcode_vm1=0 + wait_for_finish $last_pid_vm1 || retcode_vm1=$? + check_fio_retcode "Scsi hotremove test case 4: Iteration 1." 1 $retcode_vm0 + check_fio_retcode "Scsi hotremove test case 4: Iteration 2." 1 $retcode_vm1 + + # 7. Check if removed devices are gone from lsblk. + vm_check_scsi_location "0" + local new_disks_vm0="$SCSI_DISK" + check_disks "$disks_vm0" "$new_disks_vm0" + vm_check_scsi_location "1" + local new_disks_vm1="$SCSI_DISK" + check_disks "$disks_vm1" "$new_disks_vm1" + + # 8. Reboot both VMs. + reboot_all_and_prepare "0 1" + # 9. Run FIO I/O traffic with verification enabled on on not-removed NVMe disk. + local retcode=0 + $run_fio & + wait_for_finish $! || retcode=$? + # 10. Check that fio job run on hot-removed device stopped. + # Expect: Fio should return error message and return code != 0. + check_fio_retcode "Scsi hotremove test case 4: Iteration 3." 1 $retcode + prepare_fio_cmd_tc1 "0 1" + # 11. Run FIO I/O traffic with verification enabled on on not-removed NVMe disk. + local retcode=0 + $run_fio & + wait_for_finish $! || retcode=$? + # 12. Check finished status FIO. Write and read in the not-removed. + # NVMe disk should be successful. + # Expected: Fio should return return code == 0. + check_fio_retcode "Scsi hotremove test case 4: Iteration 4." 0 $retcode + vm_shutdown_all + add_nvme "HotInNvme3" "$traddr" + sleep 1 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p1.0 0 + $rpc_py remove_vhost_scsi_target naa.Nvme0n1p3.1 0 +} + +function pre_scsi_hotremove_test_case() { + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p0.0 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p1.0 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p2.1 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p3.1 +} + +function post_scsi_hotremove_test_case() { + $rpc_py remove_vhost_controller naa.Nvme0n1p0.0 + $rpc_py remove_vhost_controller naa.Nvme0n1p1.0 + $rpc_py remove_vhost_controller naa.Nvme0n1p2.1 + $rpc_py remove_vhost_controller naa.Nvme0n1p3.1 +} + +pre_scsi_hotremove_test_case +scsi_hotremove_tc1 +scsi_hotremove_tc2 +scsi_hotremove_tc3 +scsi_hotremove_tc4 +post_scsi_hotremove_test_case diff --git a/src/spdk/test/vhost/hotplug/test_plan.md b/src/spdk/test/vhost/hotplug/test_plan.md new file mode 100644 index 00000000..0cbc5042 --- /dev/null +++ b/src/spdk/test/vhost/hotplug/test_plan.md @@ -0,0 +1,86 @@ +#Vhost hotattach and hotdetach test plan + +## Objective +The purpose of these tests is to verify that SPDK vhost remains stable during +hot-attach and hot-detach operations performed on SCSI controllers devices. +Hot-attach is a scenario where a device is added to controller already in use by +guest VM, while in hot-detach device is removed from controller when already in use. + +## Test Cases Description +1. FIO I/O traffic is run during hot-attach and detach operations. +By default FIO uses default_integrity*.job config files located in +test/vhost/hotfeatures/fio_jobs directory. +2. FIO mode of operation in random write (randwrite) with verification enabled +which results in also performing read operations. +3. Test case descriptions below contain manual steps for testing. +Automated tests are located in test/vhost/hotfeatures. + +### Hotattach, Hotdetach Test Cases prerequisites +1. Run vhost with 8 empty controllers. Prepare 16 nvme disks. +If you don't have 16 disks use split. +2. In test cases fio status is checked after every run if there are any errors. + +### Hotattach Test Cases prerequisites +1. Run vms, first with ctrlr-1 and ctrlr-2 and second one with ctrlr-3 and ctrlr-4. + +## Test Case 1 +1. Attach NVMe to Ctrlr 1 +2. Run fio integrity on attached device + +## Test Case 2 +1. Run fio integrity on attached device from test case 1 +2. During fio attach another NVMe to Ctrlr 1 +3. Run fio integrity on both devices + +## Test Case 3 +1. Run fio integrity on attached devices from previous test cases +2. During fio attach NVMe to Ctrl2 +3. Run fio integrity on all devices + +## Test Case 4 +2. Run fio integrity on attached device from previous test cases +3. During fio attach NVMe to Ctrl3/VM2 +4. Run fio integrity on all devices +5. Reboot VMs +6. Run fio integrity again on all devices + + +### Hotdetach Test Cases prerequisites +1. Run vms, first with ctrlr-5 and ctrlr-6 and second with ctrlr-7 and ctrlr-8. + +## Test Case 1 +1. Run fio on all devices +2. Detatch NVMe from Ctrl5 during fio +3. Check vhost or VMs did not crash +4. Check that detatched device is gone from VM +5. Check that fio job run on detached device stopped and failed + +## Test Case 2 +1. Attach NVMe to Ctrlr 5 +2. Run fio on 1 device from Ctrl 5 +3. Detatch NVMe from Ctrl5 during fio traffic +4. Check vhost or VMs did not crash +5. Check that fio job run on detached device stopped and failed +6. Check that detatched device is gone from VM + +## Test Case 3 +1. Attach NVMe to Ctrlr 5 +2. Run fio with integrity on all devices, except one +3. Detatch NVMe without traffic during fio running on other devices +4. Check vhost or VMs did not crash +5. Check that fio jobs did not fail +6. Check that detatched device is gone from VM + +## Test Case 4 +1. Attach NVMe to Ctrlr 5 +2. Run fio on 1 device from Ctrl 5 +3. Run separate fio with integrity on all other devices (all VMs) +4. Detatch NVMe from Ctrl1 during fio traffic +5. Check vhost or VMs did not crash +6. Check that fio job run on detached device stopped and failed +7. Check that other fio jobs did not fail +8. Check that detatched device is gone from VM +9. Reboot VMs +10. Check that detatched device is gone from VM +11. Check that all other devices are in place +12. Run fio integrity on all remianing devices diff --git a/src/spdk/test/vhost/hotplug/vhost.conf.base b/src/spdk/test/vhost/hotplug/vhost.conf.base new file mode 100644 index 00000000..4fa801d9 --- /dev/null +++ b/src/spdk/test/vhost/hotplug/vhost.conf.base @@ -0,0 +1,4 @@ +[Global] + +[Nvme] + HotplugEnable Yes diff --git a/src/spdk/test/vhost/initiator/autotest.config b/src/spdk/test/vhost/initiator/autotest.config new file mode 100644 index 00000000..61a1a242 --- /dev/null +++ b/src/spdk/test/vhost/initiator/autotest.config @@ -0,0 +1,5 @@ +vhost_0_reactor_mask=["0"] +vhost_0_master_core=0 + +VM_0_qemu_mask=1-10 +VM_0_qemu_numa_node=0 diff --git a/src/spdk/test/vhost/initiator/bdev.conf b/src/spdk/test/vhost/initiator/bdev.conf new file mode 100644 index 00000000..7ea01a82 --- /dev/null +++ b/src/spdk/test/vhost/initiator/bdev.conf @@ -0,0 +1,21 @@ +[VirtioUser0] + Path naa.Nvme0n1_scsi0.0 + Queues 8 + +[VirtioUser1] + Path naa.Malloc0.0 + Queues 8 + +[VirtioUser2] + Path naa.Malloc1.0 + Queues 8 + +[VirtioUser3] + Path naa.Nvme0n1_blk0.0 + Type Blk + Queues 8 + +[VirtioUser4] + Path naa.Nvme0n1_blk1.0 + Type Blk + Queues 8 diff --git a/src/spdk/test/vhost/initiator/bdev.fio b/src/spdk/test/vhost/initiator/bdev.fio new file mode 100644 index 00000000..40520228 --- /dev/null +++ b/src/spdk/test/vhost/initiator/bdev.fio @@ -0,0 +1,51 @@ +[global] +thread=1 +group_reporting=1 +direct=1 +norandommap=1 +time_based=1 +do_verify=1 +verify=md5 +verify_backlog=1024 +iodepth=128 +bs=4K +runtime=10 +size=13% + +[job_randwrite] +rw=randwrite +name=randwrite + +[job_randrw] +offset=13% +rw=randrw +name=randrw + +[job_write] +offset=26% +rw=write +name=write + +[job_rw] +offset=39% +rw=rw +name=rw + +[job_unmap_trim_sequential] +offset=52% +rw=trim +trim_verify_zero=1 +name=unmap_trim_sequential + +[job_unmap_trim_random] +offset=65% +rw=randtrim +trim_verify_zero=1 +name=unmap_trim_random + +[job_unmap_write] +stonewall +offset=52% +size=26% +rw=randwrite +name=unmap_write diff --git a/src/spdk/test/vhost/initiator/bdev_pci.conf b/src/spdk/test/vhost/initiator/bdev_pci.conf new file mode 100644 index 00000000..0e47e88a --- /dev/null +++ b/src/spdk/test/vhost/initiator/bdev_pci.conf @@ -0,0 +1,2 @@ +[VirtioPci] + Enable Yes diff --git a/src/spdk/test/vhost/initiator/blockdev.sh b/src/spdk/test/vhost/initiator/blockdev.sh new file mode 100755 index 00000000..b5ec3015 --- /dev/null +++ b/src/spdk/test/vhost/initiator/blockdev.sh @@ -0,0 +1,200 @@ +#!/usr/bin/env bash + +set -e +INITIATOR_DIR=$(readlink -f $(dirname $0)) +[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $INITIATOR_DIR/../common && pwd)" +ROOT_DIR=$(readlink -f $INITIATOR_DIR/../../..) + +PLUGIN_DIR=$ROOT_DIR/examples/bdev/fio_plugin +FIO_PATH="/usr/src/fio" +virtio_bdevs="" +virtio_with_unmap="" +os_image="/home/sys_sgsw/vhost_vm_image.qcow2" +#different linux distributions have different versions of targetcli that have different names for ramdisk option +targetcli_rd_name="" +kernel_vhost_disk="naa.5012345678901234" + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Script for running vhost initiator tests." + echo "Usage: $(basename $1) [-h|--help] [--fiobin=PATH]" + echo "-h, --help Print help and exit" + echo " --vm_image=PATH Path to VM image used in these tests [default=$os_image]" + echo " --fiopath=PATH Path to fio directory on host [default=$FIO_PATH]" +} + +while getopts 'h-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 && exit 0 ;; + fiopath=*) FIO_PATH="${OPTARG#*=}" ;; + vm_image=*) os_image="${OPTARG#*=}" ;; + *) usage $0 echo "Invalid argument '$OPTARG'" && exit 1 ;; + esac + ;; + h) usage $0 && exit 0 ;; + *) usage $0 "Invalid argument '$optchar'" && exit 1 ;; + esac +done + +source $COMMON_DIR/common.sh +source $INITIATOR_DIR/autotest.config +PLUGIN_DIR=$ROOT_DIR/examples/bdev/fio_plugin +RPC_PY="$ROOT_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock" + +if [ ! -x $FIO_PATH ]; then + error "Invalid path of fio binary" +fi + +if [[ $EUID -ne 0 ]]; then + echo "INFO: Go away user come back as root" + exit 1 +fi + +if targetcli ls backstores | grep ramdisk ; then + targetcli_rd_name="ramdisk" +elif targetcli ls backstores | grep rd_mcp ; then + targetcli_rd_name="rd_mcp" +else + error "targetcli: cannot create a ramdisk.\ + Neither backstores/ramdisk nor backstores/rd_mcp is available" +fi + +function remove_kernel_vhost() +{ + targetcli "/vhost delete $kernel_vhost_disk" + targetcli "/backstores/$targetcli_rd_name delete ramdisk" +} + +trap 'rm -f *.state $ROOT_DIR/spdk.tar.gz $ROOT_DIR/fio.tar.gz $(get_vhost_dir)/Virtio0;\ + error_exit "${FUNCNAME}""${LINENO}"' ERR SIGTERM SIGABRT +function run_spdk_fio() { + LD_PRELOAD=$PLUGIN_DIR/fio_plugin $FIO_PATH/fio --ioengine=spdk_bdev\ + "$@" --spdk_mem=1024 --spdk_single_seg=1 +} + +function create_bdev_config() +{ + local vbdevs + + if [ -z "$($RPC_PY get_bdevs | jq '.[] | select(.name=="Nvme0n1")')" ]; then + error "Nvme0n1 bdev not found!" + fi + + $RPC_PY construct_split_vbdev Nvme0n1 6 + + $RPC_PY construct_vhost_scsi_controller naa.Nvme0n1_scsi0.0 + $RPC_PY add_vhost_scsi_lun naa.Nvme0n1_scsi0.0 0 Nvme0n1p0 + $RPC_PY add_vhost_scsi_lun naa.Nvme0n1_scsi0.0 1 Nvme0n1p1 + $RPC_PY add_vhost_scsi_lun naa.Nvme0n1_scsi0.0 2 Nvme0n1p2 + $RPC_PY add_vhost_scsi_lun naa.Nvme0n1_scsi0.0 3 Nvme0n1p3 + + $RPC_PY construct_vhost_blk_controller naa.Nvme0n1_blk0.0 Nvme0n1p4 + $RPC_PY construct_vhost_blk_controller naa.Nvme0n1_blk1.0 Nvme0n1p5 + + $RPC_PY construct_malloc_bdev 128 512 --name Malloc0 + $RPC_PY construct_vhost_scsi_controller naa.Malloc0.0 + $RPC_PY add_vhost_scsi_lun naa.Malloc0.0 0 Malloc0 + + $RPC_PY construct_malloc_bdev 128 4096 --name Malloc1 + $RPC_PY construct_vhost_scsi_controller naa.Malloc1.0 + $RPC_PY add_vhost_scsi_lun naa.Malloc1.0 0 Malloc1 + + vbdevs=$(discover_bdevs $ROOT_DIR $INITIATOR_DIR/bdev.conf) + virtio_bdevs=$(jq -r '[.[].name] | join(":")' <<< $vbdevs) + virtio_with_unmap=$(jq -r '[.[] | select(.supported_io_types.unmap==true).name] + | join(":")' <<< $vbdevs) +} + +timing_enter spdk_vhost_run +spdk_vhost_run +timing_exit spdk_vhost_run + +timing_enter create_bdev_config +create_bdev_config +timing_exit create_bdev_config + +timing_enter run_spdk_fio +run_spdk_fio $INITIATOR_DIR/bdev.fio --filename=$virtio_bdevs --section=job_randwrite --section=job_randrw \ + --section=job_write --section=job_rw --spdk_conf=$INITIATOR_DIR/bdev.conf +report_test_completion "vhost_run_spdk_fio" +timing_exit run_spdk_fio + +timing_enter run_spdk_fio_unmap +run_spdk_fio $INITIATOR_DIR/bdev.fio --filename=$virtio_with_unmap --spdk_conf=$INITIATOR_DIR/bdev.conf \ + --spdk_conf=$INITIATOR_DIR/bdev.conf +timing_exit run_spdk_fio_unmap + +timing_enter create_kernel_vhost +targetcli "/backstores/$targetcli_rd_name create name=ramdisk size=1GB" +targetcli "/vhost create $kernel_vhost_disk" +targetcli "/vhost/$kernel_vhost_disk/tpg1/luns create /backstores/$targetcli_rd_name/ramdisk" +timing_exit create_kernel_vhost + +timing_enter setup_vm +vm_no="0" +vm_setup --disk-type=spdk_vhost_scsi --force=$vm_no --os=$os_image \ + --disks="Nvme0n1_scsi0:Malloc0:Malloc1:$kernel_vhost_disk,kernel_vhost:Virtio0,virtio:\ + Nvme0n1_blk0,spdk_vhost_blk:Nvme0n1_blk1,spdk_vhost_blk" \ + --queue_num=8 --memory=6144 +vm_run $vm_no + +timing_enter vm_wait_for_boot +vm_wait_for_boot 600 $vm_no +timing_exit vm_wait_for_boot + +timing_enter vm_scp_spdk +touch $ROOT_DIR/spdk.tar.gz +tar --exclude="spdk.tar.gz" --exclude="*.o" --exclude="*.d" --exclude=".git" -C $ROOT_DIR -zcf $ROOT_DIR/spdk.tar.gz . +vm_scp $vm_no $ROOT_DIR/spdk.tar.gz "127.0.0.1:/root" +vm_ssh $vm_no "mkdir -p /root/spdk; tar -zxf /root/spdk.tar.gz -C /root/spdk --strip-components=1" + +touch $ROOT_DIR/fio.tar.gz +tar --exclude="fio.tar.gz" --exclude="*.o" --exclude="*.d" --exclude=".git" -C $FIO_PATH -zcf $ROOT_DIR/fio.tar.gz . +vm_scp $vm_no $ROOT_DIR/fio.tar.gz "127.0.0.1:/root" +vm_ssh $vm_no "rm -rf /root/fio_src; mkdir -p /root/fio_src; tar -zxf /root/fio.tar.gz -C /root/fio_src --strip-components=1" +timing_exit vm_scp_spdk + +timing_enter vm_build_spdk +nproc=$(vm_ssh $vm_no "nproc") +vm_ssh $vm_no " cd /root/fio_src ; make clean ; make -j${nproc} ; make install" +vm_ssh $vm_no " cd spdk ; ./configure --with-fio=/root/fio_src ; make clean ; make -j${nproc}" +timing_exit vm_build_spdk + +vm_ssh $vm_no "/root/spdk/scripts/setup.sh" +vbdevs=$(vm_ssh $vm_no ". /root/spdk/test/common/autotest_common.sh && discover_bdevs /root/spdk \ + /root/spdk/test/vhost/initiator/bdev_pci.conf") +virtio_bdevs=$(jq -r '[.[].name] | join(":")' <<< $vbdevs) +virtio_with_unmap=$(jq -r '[.[] | select(.supported_io_types.unmap==true).name] + | join(":")' <<< $vbdevs) +timing_exit setup_vm + +timing_enter run_spdk_fio_pci +vm_ssh $vm_no "LD_PRELOAD=/root/spdk/examples/bdev/fio_plugin/fio_plugin /root/fio_src/fio --ioengine=spdk_bdev \ + /root/spdk/test/vhost/initiator/bdev.fio --filename=$virtio_bdevs --section=job_randwrite \ + --section=job_randrw --section=job_write --section=job_rw \ + --spdk_conf=/root/spdk/test/vhost/initiator/bdev_pci.conf --spdk_mem=1024 --spdk_single_seg=1" +timing_exit run_spdk_fio_pci + +timing_enter run_spdk_fio_pci_unmap +vm_ssh $vm_no "LD_PRELOAD=/root/spdk/examples/bdev/fio_plugin/fio_plugin /root/fio_src/fio --ioengine=spdk_bdev \ + /root/spdk/test/vhost/initiator/bdev.fio --filename=$virtio_with_unmap \ + --spdk_conf=/root/spdk/test/vhost/initiator/bdev_pci.conf --spdk_mem=1024 --spdk_single_seg=1" +timing_exit run_spdk_fio_pci_unmap + +timing_enter vm_shutdown_all +vm_shutdown_all +timing_exit vm_shutdown_all + +rm -f *.state $ROOT_DIR/spdk.tar.gz $ROOT_DIR/fio.tar.gz $(get_vhost_dir)/Virtio0 +timing_enter remove_kernel_vhost +remove_kernel_vhost +timing_exit remove_kernel_vhost + +$RPC_PY delete_nvme_controller Nvme0 + +timing_enter spdk_vhost_kill +spdk_vhost_kill +timing_exit spdk_vhost_kill diff --git a/src/spdk/test/vhost/initiator/json_config.sh b/src/spdk/test/vhost/initiator/json_config.sh new file mode 100755 index 00000000..86078c9a --- /dev/null +++ b/src/spdk/test/vhost/initiator/json_config.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -ex +INITIATOR_JSON_DIR=$(readlink -f $(dirname $0)) +. $INITIATOR_JSON_DIR/../../json_config/common.sh + +# Load spdk_tgt with controllers used by virtio initiator +# Test also virtio_pci bdevs +function construct_vhost_devices() { + $rpc_py construct_split_vbdev Nvme0n1 4 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p0.0 + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p1.1 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 0 Nvme0n1p0 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1p1.1 0 Nvme0n1p1 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p2.0 Nvme0n1p2 + $rpc_py construct_vhost_blk_controller naa.Nvme0n1p3.1 Nvme0n1p3 + pci_scsi=$(lspci -nn -D | grep '1af4:1004' | head -1 | awk '{print $1;}') + pci_blk=$(lspci -nn -D | grep '1af4:1001' | head -1 | awk '{print $1;}') + if [ ! -z $pci_scsi ]; then + $rpc_py construct_virtio_dev -t pci -a $pci_scsi -d scsi Virtio0 + fi + if [ ! -z $pci_blk ]; then + $rpc_py construct_virtio_dev -t pci -a $pci_blk -d blk Virtio1 + fi +} + +# Load virtio initiator with bdevs +function connect_to_vhost_devices_from_initiator() { + $rpc_py construct_virtio_dev -t user -a naa.Nvme0n1p0.0 -d scsi Nvme0n1p0 + $rpc_py construct_virtio_dev -t user -a naa.Nvme0n1p2.0 -d blk Nvme0n1p2 +} + +function disconnect_and_clear_vhost_devices() { + $clear_config_py clear_config +} + +function test_subsystems() { + run_spdk_tgt + rootdir=$(readlink -f $INITIATOR_JSON_DIR/../../..) + + rpc_py="$spdk_rpc_py" + clear_config_py="$spdk_clear_config_py" + load_nvme + + construct_vhost_devices + test_json_config + run_initiator + rpc_py="$initiator_rpc_py" + clear_config_py="$initiator_clear_config_py" + $rpc_py start_subsystem_init + connect_to_vhost_devices_from_initiator + test_json_config + disconnect_and_clear_vhost_devices + test_global_params "virtio_initiator" + clear_config_py="$spdk_clear_config_py" + $clear_config_py clear_config + kill_targets +} + +trap 'on_error_exit "${FUNCNAME}" "${LINENO}"' ERR +timing_enter json_config_virtio_initiator + +test_subsystems +timing_exit json_config_virtio_initiator +report_test_completion json_config_virtio_initiator diff --git a/src/spdk/test/vhost/integrity/integrity_start.sh b/src/spdk/test/vhost/integrity/integrity_start.sh new file mode 100755 index 00000000..a9899e9f --- /dev/null +++ b/src/spdk/test/vhost/integrity/integrity_start.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +set -e + +INTEGRITY_BASE_DIR=$(readlink -f $(dirname $0)) +ctrl_type="spdk_vhost_scsi" +vm_fs="ext4" + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for doing automated test" + echo "Usage: $(basename $1) [OPTIONS]" + echo + echo "-h, --help Print help and exit" + echo " --work-dir=WORK_DIR Workspace for the test to run" + echo " --ctrl-type=TYPE Controller type to use for test:" + echo " spdk_vhost_scsi - use spdk vhost scsi" + echo " --fs=FS_LIST Filesystems to use for test in VM:" + echo " Example: --fs=\"ext4 ntfs ext2\"" + echo " Default: ext4" + echo " spdk_vhost_blk - use spdk vhost block" + echo "-x set -x for script debug" + exit 0 +} + +function clean_lvol_cfg() +{ + notice "Removing lvol bdev and lvol store" + $rpc_py destroy_lvol_bdev lvol_store/lvol_bdev + $rpc_py destroy_lvol_store -l lvol_store +} + +while getopts 'xh-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 ;; + ctrl-type=*) ctrl_type="${OPTARG#*=}" ;; + fs=*) vm_fs="${OPTARG#*=}" ;; + *) usage $0 "Invalid argument '$OPTARG'" ;; + esac + ;; + h) usage $0 ;; + x) set -x + x="-x" ;; + *) usage $0 "Invalid argument '$OPTARG'" + esac +done + +. $(readlink -e "$(dirname $0)/../common/common.sh") || exit 1 +rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock" + +trap 'error_exit "${FUNCNAME}" "${LINENO}"' SIGTERM SIGABRT ERR + +# Try to kill if any VM remains from previous runs +vm_kill_all + +notice "Starting SPDK vhost" +spdk_vhost_run +notice "..." + +# Set up lvols and vhost controllers +trap 'clean_lvol_cfg; error_exit "${FUNCNAME}" "${LINENO}"' SIGTERM SIGABRT ERR +notice "Constructing lvol store and lvol bdev on top of Nvme0n1" +lvs_uuid=$($rpc_py construct_lvol_store Nvme0n1 lvol_store) +$rpc_py construct_lvol_bdev lvol_bdev 10000 -l lvol_store + +if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then + $rpc_py construct_vhost_scsi_controller naa.Nvme0n1.0 + $rpc_py add_vhost_scsi_lun naa.Nvme0n1.0 0 lvol_store/lvol_bdev +elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then + $rpc_py construct_vhost_blk_controller naa.Nvme0n1.0 lvol_store/lvol_bdev +fi + +# Set up and run VM +setup_cmd="vm_setup --disk-type=$ctrl_type --force=0" +setup_cmd+=" --os=/home/sys_sgsw/vhost_vm_image.qcow2" +setup_cmd+=" --disks=Nvme0n1" +$setup_cmd + +# Run VM +vm_run 0 +vm_wait_for_boot 600 0 + +# Run tests on VM +vm_scp 0 $INTEGRITY_BASE_DIR/integrity_vm.sh root@127.0.0.1:/root/integrity_vm.sh +vm_ssh 0 "~/integrity_vm.sh $ctrl_type \"$vm_fs\"" + +notice "Shutting down virtual machine..." +vm_shutdown_all + +clean_lvol_cfg + +$rpc_py delete_nvme_controller Nvme0 + +notice "Shutting down SPDK vhost app..." +spdk_vhost_kill diff --git a/src/spdk/test/vhost/integrity/integrity_vm.sh b/src/spdk/test/vhost/integrity/integrity_vm.sh new file mode 100755 index 00000000..ccb01cea --- /dev/null +++ b/src/spdk/test/vhost/integrity/integrity_vm.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -xe + +basedir=$(readlink -f $(dirname $0)) +MAKE="make -j$(( $(nproc) * 2 ))" + +if [[ $1 == "spdk_vhost_scsi" ]]; then + devs="" + for entry in /sys/block/sd*; do + if grep -Eq '(INTEL|RAWSCSI|LIO-ORG)' $entry/device/vendor; then + devs+="$(basename $entry) " + fi + done +elif [[ $1 == "spdk_vhost_blk" ]]; then + devs=$(cd /sys/block; echo vd*) +fi + +fs=$2 + +trap "exit 1" SIGINT SIGTERM EXIT + +for fs in $fs; do + for dev in $devs; do + parted_cmd="parted -s /dev/${dev}" + + echo "INFO: Creating partition table on disk using: $parted_cmd mklabel gpt" + $parted_cmd mklabel gpt + $parted_cmd mkpart primary 2048s 100% + sleep 2 + + mkfs_cmd="mkfs.$fs" + if [[ $fs == "ntfs" ]]; then + mkfs_cmd+=" -f" + fi + mkfs_cmd+=" /dev/${dev}1" + echo "INFO: Creating filesystem using: $mkfs_cmd" + wipefs -a /dev/${dev}1 + $mkfs_cmd + + mkdir -p /mnt/${dev}dir + mount -o sync /dev/${dev}1 /mnt/${dev}dir + + fio --name="integrity" --bsrange=4k-512k --iodepth=128 --numjobs=1 --direct=1 \ + --thread=1 --group_reporting=1 --rw=randrw --rwmixread=70 \ + --filename=/mnt/${dev}dir/test_file --verify=md5 --do_verify=1 \ + --verify_backlog=1024 --fsync_on_close=1 --runtime=20 --time_based=1 --size=512m + + # Print out space consumed on target device + df -h /dev/$dev + done + + for dev in $devs; do + umount /mnt/${dev}dir + rm -rf /mnt/${dev}dir + + stats=( $(cat /sys/block/$dev/stat) ) + echo "" + echo "$dev stats" + printf "READ IO cnt: % 8u merges: % 8u sectors: % 8u ticks: % 8u\n" \ + ${stats[0]} ${stats[1]} ${stats[2]} ${stats[3]} + printf "WRITE IO cnt: % 8u merges: % 8u sectors: % 8u ticks: % 8u\n" \ + ${stats[4]} ${stats[5]} ${stats[6]} ${stats[7]} + printf "in flight: % 8u io ticks: % 8u time in queue: % 8u\n" \ + ${stats[8]} ${stats[9]} ${stats[10]} + echo "" + done +done + +trap - SIGINT SIGTERM EXIT diff --git a/src/spdk/test/vhost/json_config/json_config.sh b/src/spdk/test/vhost/json_config/json_config.sh new file mode 100755 index 00000000..d5683f1d --- /dev/null +++ b/src/spdk/test/vhost/json_config/json_config.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -ex +VHOST_JSON_DIR=$(readlink -f $(dirname $0)) +. $VHOST_JSON_DIR/../../json_config/common.sh + +function test_subsystems() { + run_spdk_tgt + + rpc_py="$spdk_rpc_py" + clear_config_py="$spdk_clear_config_py" + load_nvme + + upload_vhost + test_json_config + $clear_config_py clear_config + + kill_targets +} + +trap 'on_error_exit "${FUNCNAME}" "${LINENO}"' ERR +timing_enter json_config_vhost + +test_subsystems +timing_exit json_config_vhost +report_test_completion json_config_vhost diff --git a/src/spdk/test/vhost/lvol/autotest.config b/src/spdk/test/vhost/lvol/autotest.config new file mode 100644 index 00000000..9b653cd7 --- /dev/null +++ b/src/spdk/test/vhost/lvol/autotest.config @@ -0,0 +1,74 @@ +vhost_0_reactor_mask="[0-31]" +vhost_0_master_core=0 + +VM_0_qemu_mask=1 +VM_0_qemu_numa_node=0 + +VM_1_qemu_mask=2 +VM_1_qemu_numa_node=0 + +VM_2_qemu_mask=3 +VM_2_qemu_numa_node=0 + +VM_3_qemu_mask=4 +VM_3_qemu_numa_node=0 + +VM_4_qemu_mask=5 +VM_4_qemu_numa_node=0 + +VM_5_qemu_mask=6 +VM_5_qemu_numa_node=0 + +VM_6_qemu_mask=7 +VM_6_qemu_numa_node=0 + +VM_7_qemu_mask=8 +VM_7_qemu_numa_node=0 + +VM_8_qemu_mask=9 +VM_8_qemu_numa_node=0 + +VM_9_qemu_mask=10 +VM_9_qemu_numa_node=0 + +VM_10_qemu_mask=11 +VM_10_qemu_numa_node=0 + +VM_11_qemu_mask=12 +VM_11_qemu_numa_node=0 + +VM_12_qemu_mask=13 +VM_12_qemu_numa_node=1 + +VM_13_qemu_mask=14 +VM_13_qemu_numa_node=1 + +VM_14_qemu_mask=15 +VM_14_qemu_numa_node=1 + +VM_15_qemu_mask=16 +VM_15_qemu_numa_node=1 + +VM_16_qemu_mask=17 +VM_16_qemu_numa_node=1 + +VM_17_qemu_mask=18 +VM_17_qemu_numa_node=1 + +VM_18_qemu_mask=19 +VM_18_qemu_numa_node=1 + +VM_19_qemu_mask=20 +VM_19_qemu_numa_node=1 + +VM_20_qemu_mask=21 +VM_20_qemu_numa_node=1 + +VM_21_qemu_mask=22 +VM_21_qemu_numa_node=1 + +VM_22_qemu_mask=23 +VM_22_qemu_numa_node=1 + +VM_23_qemu_mask=24 +VM_23_qemu_numa_node=1 diff --git a/src/spdk/test/vhost/lvol/lvol_test.sh b/src/spdk/test/vhost/lvol/lvol_test.sh new file mode 100755 index 00000000..5190b5f2 --- /dev/null +++ b/src/spdk/test/vhost/lvol/lvol_test.sh @@ -0,0 +1,286 @@ +#!/usr/bin/env bash +set -e + +rootdir=$(readlink -f $(dirname $0))/../../.. +source "$rootdir/scripts/common.sh" + +LVOL_TEST_DIR=$(readlink -f $(dirname $0)) +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $LVOL_TEST_DIR/../../../../ && pwd)" +[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $LVOL_TEST_DIR/../common && pwd)" + +. $COMMON_DIR/common.sh +rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock" + +vm_count=1 +max_disks="" +ctrl_type="spdk_vhost_scsi" +use_fs=false +nested_lvol=false +distribute_cores=false + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for doing automated test" + echo "Usage: $(basename $1) [OPTIONS]" + echo + echo "-h, --help Print help and exit" + echo " --fio-bin=PATH Path to FIO binary.;" + echo " --vm-count=INT Virtual machines to use in test;" + echo " Each VM will get one lvol bdev on each NVMe." + echo " Default: 1" + echo " --max-disks=INT Maximum number of NVMe drives to use in test." + echo " Default: will use all available NVMes." + echo " --ctrl-type=TYPE Controller type to use for test:" + echo " spdk_vhost_scsi - use spdk vhost scsi" + echo " spdk_vhost_blk - use spdk vhost block" + echo " --nested-lvol If enabled will create additional lvol bdev" + echo " on each NVMe for use as base device for next" + echo " lvol store and lvol bdevs." + echo " (NVMe->lvol_store->lvol_bdev->lvol_store->lvol_bdev)" + echo " Default: False" + echo " --thin-provisioning Create lvol bdevs thin provisioned instead of" + echo " allocating space up front" + echo " --distribute-cores Use custom config file and run vhost controllers" + echo " on different CPU cores instead of single core." + echo " Default: False" + echo "-x set -x for script debug" + echo " --multi-os Run tests on different os types in VMs" + echo " Default: False" + exit 0 +} + +function clean_lvol_cfg() +{ + notice "Removing nested lvol bdevs" + for lvol_bdev in "${nest_lvol_bdevs[@]}"; do + $rpc_py destroy_lvol_bdev $lvol_bdev + notice "nested lvol bdev $lvol_bdev removed" + done + + notice "Removing nested lvol stores" + for lvol_store in "${nest_lvol_stores[@]}"; do + $rpc_py destroy_lvol_store -u $lvol_store + notice "nested lvol store $lvol_store removed" + done + + notice "Removing lvol bdevs" + for lvol_bdev in "${lvol_bdevs[@]}"; do + $rpc_py destroy_lvol_bdev $lvol_bdev + notice "lvol bdev $lvol_bdev removed" + done + + notice "Removing lvol stores" + for lvol_store in "${lvol_stores[@]}"; do + $rpc_py destroy_lvol_store -u $lvol_store + notice "lvol store $lvol_store removed" + done +} + +while getopts 'xh-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 ;; + fio-bin=*) fio_bin="--fio-bin=${OPTARG#*=}" ;; + vm-count=*) vm_count="${OPTARG#*=}" ;; + max-disks=*) max_disks="${OPTARG#*=}" ;; + ctrl-type=*) ctrl_type="${OPTARG#*=}" ;; + nested-lvol) nested_lvol=true ;; + distribute-cores) distribute_cores=true ;; + thin-provisioning) thin=" -t " ;; + multi-os) multi_os=true ;; + *) usage $0 "Invalid argument '$OPTARG'" ;; + esac + ;; + h) usage $0 ;; + x) set -x + x="-x" ;; + *) usage $0 "Invalid argument '$OPTARG'" + esac +done + +notice "Get NVMe disks:" +nvmes=($(iter_pci_class_code 01 08 02)) + +if [[ -z $max_disks ]]; then + max_disks=${#nvmes[@]} +fi + +if [[ ${#nvmes[@]} -lt max_disks ]]; then + fail "Number of NVMe drives (${#nvmes[@]}) is lower than number of requested disks for test ($max_disks)" +fi + +if $distribute_cores; then + # FIXME: this need to be handled entirely in common.sh + source $LVOL_TEST_DIR/autotest.config +fi + +trap 'error_exit "${FUNCNAME}" "${LINENO}"' SIGTERM SIGABRT ERR + +vm_kill_all + +notice "running SPDK vhost" +spdk_vhost_run +notice "..." + +trap 'clean_lvol_cfg; error_exit "${FUNCNAME}" "${LINENO}"' SIGTERM SIGABRT ERR + +lvol_stores=() +lvol_bdevs=() +nest_lvol_stores=() +nest_lvol_bdevs=() +used_vms="" + +# On each NVMe create one lvol store +for (( i=0; i<$max_disks; i++ ));do + + # Create base lvol store on NVMe + notice "Creating lvol store on device Nvme${i}n1" + ls_guid=$($rpc_py construct_lvol_store Nvme${i}n1 lvs_$i -c 4194304) + lvol_stores+=("$ls_guid") + + if $nested_lvol; then + free_mb=$(get_lvs_free_mb "$ls_guid") + size=$((free_mb / (vm_count+1) )) + + notice "Creating lvol bdev on lvol store: $ls_guid" + lb_name=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_nest $size $thin) + + notice "Creating nested lvol store on lvol bdev: $lb_name" + nest_ls_guid=$($rpc_py construct_lvol_store $lb_name lvs_n_$i -c 4194304) + nest_lvol_stores+=("$nest_ls_guid") + + for (( j=0; j<$vm_count; j++)); do + notice "Creating nested lvol bdev for VM $i on lvol store $nest_ls_guid" + free_mb=$(get_lvs_free_mb "$nest_ls_guid") + nest_size=$((free_mb / (vm_count-j) )) + lb_name=$($rpc_py construct_lvol_bdev -u $nest_ls_guid lbd_vm_$j $nest_size $thin) + nest_lvol_bdevs+=("$lb_name") + done + fi + + # Create base lvol bdevs + for (( j=0; j<$vm_count; j++)); do + notice "Creating lvol bdev for VM $i on lvol store $ls_guid" + free_mb=$(get_lvs_free_mb "$ls_guid") + size=$((free_mb / (vm_count-j) )) + lb_name=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_vm_$j $size $thin) + lvol_bdevs+=("$lb_name") + done +done + +bdev_info=$($rpc_py get_bdevs) +notice "Configuration after initial set-up:" +$rpc_py get_lvol_stores +echo "$bdev_info" + +# Set up VMs +for (( i=0; i<$vm_count; i++)); do + vm="vm_$i" + + # Get all lvol bdevs associated with this VM number + bdevs=$(jq -r "map(select(.aliases[] | contains(\"$vm\")) | \ + .aliases[]) | join(\" \")" <<< "$bdev_info") + bdevs=($bdevs) + + setup_cmd="vm_setup --disk-type=$ctrl_type --force=$i" + if [[ $i%2 -ne 0 ]] && [[ $multi_os ]]; then + setup_cmd+=" --os=/home/sys_sgsw/spdk_vhost_CentOS_vm_image.qcow2" + else + setup_cmd+=" --os=/home/sys_sgsw/vhost_vm_image.qcow2" + fi + + # Create single SCSI controller or multiple BLK controllers for this VM + if $distribute_cores; then + mask="VM_${i}_qemu_mask" + mask_arg="--cpumask ${!mask}" + fi + + if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then + $rpc_py construct_vhost_scsi_controller naa.0.$i $mask_arg + for (( j=0; j<${#bdevs[@]}; j++)); do + $rpc_py add_vhost_scsi_lun naa.0.$i $j ${bdevs[$j]} + done + setup_cmd+=" --disks=0" + elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then + disk="" + for (( j=0; j<${#bdevs[@]}; j++)); do + $rpc_py construct_vhost_blk_controller naa.$j.$i ${bdevs[$j]} $mask_arg + disk+="${j}:" + done + disk="${disk::-1}" + setup_cmd+=" --disks=$disk" + fi + + $setup_cmd + used_vms+=" $i" +done + +$rpc_py get_vhost_controllers + +# Run VMs +vm_run $used_vms +vm_wait_for_boot 600 $used_vms + +# Get disk names from VMs and run FIO traffic + +fio_disks="" +for vm_num in $used_vms; do + vm_dir=$VM_BASE_DIR/$vm_num + qemu_mask_param="VM_${vm_num}_qemu_mask" + + host_name="VM-$vm_num-${!qemu_mask_param}" + vm_ssh $vm_num "hostname $host_name" + vm_start_fio_server $fio_bin $vm_num + + if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then + vm_check_scsi_location $vm_num + elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then + vm_check_blk_location $vm_num + fi + + fio_disks+=" --vm=${vm_num}$(printf ':/dev/%s' $SCSI_DISK)" +done + +if [[ $RUN_NIGHTLY -eq 1 ]]; then + job_file="default_integrity_nightly.job" +else + job_file="default_integrity.job" +fi +# Run FIO traffic +run_fio $fio_bin --job-file=$COMMON_DIR/fio_jobs/$job_file --out="$TEST_DIR/fio_results" $fio_disks + +notice "Shutting down virtual machines..." +vm_shutdown_all +sleep 2 + +notice "Cleaning up vhost - remove LUNs, controllers, lvol bdevs and lvol stores" +if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then + for (( i=0; i<$vm_count; i++)); do + notice "Removing devices from vhost SCSI controller naa.0.$i" + for (( j=0; j<${#bdevs[@]}; j++)); do + $rpc_py remove_vhost_scsi_target naa.0.$i $j + notice "Removed device $j" + done + notice "Removing vhost SCSI controller naa.0.$i" + $rpc_py remove_vhost_controller naa.0.$i + done +elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then + for (( i=0; i<$vm_count; i++)); do + for (( j=0; j<${#bdevs[@]}; j++)); do + notice "Removing vhost BLK controller naa.$j.$i" + $rpc_py remove_vhost_controller naa.$j.$i + notice "Removed naa.$j.$i" + done + done +fi + +clean_lvol_cfg + +$rpc_py get_lvol_stores +$rpc_py get_bdevs +$rpc_py get_vhost_controllers + +notice "Shutting down SPDK vhost app..." +spdk_vhost_kill diff --git a/src/spdk/test/vhost/migration/autotest.config b/src/spdk/test/vhost/migration/autotest.config new file mode 100644 index 00000000..ccda306e --- /dev/null +++ b/src/spdk/test/vhost/migration/autotest.config @@ -0,0 +1,14 @@ +vhost_0_reactor_mask=["0"] +vhost_0_master_core=0 + +vhost_1_reactor_mask=["0"] +vhost_1_master_core=0 + +VM_0_qemu_mask=1 +VM_0_qemu_numa_node=0 + +VM_1_qemu_mask=1 +VM_1_qemu_numa_node=0 + +VM_2_qemu_mask=1 +VM_2_qemu_numa_node=0 diff --git a/src/spdk/test/vhost/migration/migration-tc1.job b/src/spdk/test/vhost/migration/migration-tc1.job new file mode 100644 index 00000000..5383b243 --- /dev/null +++ b/src/spdk/test/vhost/migration/migration-tc1.job @@ -0,0 +1,25 @@ +[global] +blocksize_range=4k-512k +#bs=512k +iodepth=128 +ioengine=libaio +filename= +group_reporting +thread +numjobs=1 +direct=1 +do_verify=1 +verify=md5 +verify_fatal=1 +verify_dump=1 +size=100% + +[write] +rw=write +stonewall + +[randread] +rw=randread +runtime=10 +time_based +stonewall diff --git a/src/spdk/test/vhost/migration/migration-tc1.sh b/src/spdk/test/vhost/migration/migration-tc1.sh new file mode 100644 index 00000000..ec89545d --- /dev/null +++ b/src/spdk/test/vhost/migration/migration-tc1.sh @@ -0,0 +1,123 @@ +function migration_tc1_clean_vhost_config() +{ + # Restore trap + trap 'error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + + notice "Removing vhost devices & controllers via RPC ..." + # Delete bdev first to remove all LUNs and SCSI targets + $rpc delete_malloc_bdev Malloc0 + + # Delete controllers + $rpc remove_vhost_controller $incoming_vm_ctrlr + $rpc remove_vhost_controller $target_vm_ctrlr + + unset -v incoming_vm target_vm incoming_vm_ctrlr target_vm_ctrlr rpc +} + +function migration_tc1_configure_vhost() +{ + # Those are global intentionally - they will be unset in cleanup handler + incoming_vm=0 + target_vm=1 + incoming_vm_ctrlr=naa.Malloc0.$incoming_vm + target_vm_ctrlr=naa.Malloc0.$target_vm + rpc="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock" + + trap 'migration_tc1_error_handler; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + + # Construct shared Malloc Bdev + $rpc construct_malloc_bdev -b Malloc0 128 4096 + + # And two controllers - one for each VM. Both are using the same Malloc Bdev as LUN 0 + $rpc construct_vhost_scsi_controller $incoming_vm_ctrlr + $rpc add_vhost_scsi_lun $incoming_vm_ctrlr 0 Malloc0 + + $rpc construct_vhost_scsi_controller $target_vm_ctrlr + $rpc add_vhost_scsi_lun $target_vm_ctrlr 0 Malloc0 +} + +function migration_tc1_error_handler() +{ + trap - SIGINT ERR EXIT + warning "Migration TC1 ERROR HANDLER" + print_backtrace + set -x + + vm_kill_all + migration_tc1_clean_vhost_config + + warning "Migration TC1 FAILED" +} + +function migration_tc1() +{ + # Use 2 VMs: + # incoming VM - the one we want to migrate + # targe VM - the one which will accept migration + local job_file="$MIGRATION_DIR/migration-tc1.job" + + # Run vhost + spdk_vhost_run + migration_tc1_configure_vhost + + notice "Setting up VMs" + vm_setup --os="$os_image" --force=$incoming_vm --disk-type=spdk_vhost_scsi --disks=Malloc0 --migrate-to=$target_vm + vm_setup --force=$target_vm --disk-type=spdk_vhost_scsi --disks=Malloc0 --incoming=$incoming_vm + + # Run everything + vm_run $incoming_vm $target_vm + + # Wait only for incoming VM, as target is waiting for migration + vm_wait_for_boot 600 $incoming_vm + + # Run fio before migration + notice "Starting FIO" + + vm_check_scsi_location $incoming_vm + run_fio $fio_bin --job-file="$job_file" --local --vm="${incoming_vm}$(printf ':/dev/%s' $SCSI_DISK)" + + # Wait a while to let the FIO time to issue some IO + sleep 5 + + # Check if fio is still running before migration + if ! is_fio_running $incoming_vm; then + vm_ssh $incoming_vm "cat /root/$(basename ${job_file}).out" + error "FIO is not running before migration: process crashed or finished too early" + fi + + vm_migrate $incoming_vm + sleep 3 + + # Check if fio is still running after migration + if ! is_fio_running $target_vm; then + vm_ssh $target_vm "cat /root/$(basename ${job_file}).out" + error "FIO is not running after migration: process crashed or finished too early" + fi + + notice "Waiting for fio to finish" + local timeout=40 + while is_fio_running $target_vm; do + sleep 1 + echo -n "." + if (( timeout-- == 0 )); then + error "timeout while waiting for FIO!" + fi + done + + notice "Fio result is:" + vm_ssh $target_vm "cat /root/$(basename ${job_file}).out" + + notice "Migration DONE" + + notice "Shutting down all VMs" + vm_shutdown_all + + migration_tc1_clean_vhost_config + + notice "killing vhost app" + spdk_vhost_kill + + notice "Migration TC1 SUCCESS" +} + +migration_tc1 diff --git a/src/spdk/test/vhost/migration/migration-tc2.job b/src/spdk/test/vhost/migration/migration-tc2.job new file mode 100644 index 00000000..df78a3cd --- /dev/null +++ b/src/spdk/test/vhost/migration/migration-tc2.job @@ -0,0 +1,20 @@ +[global] +blocksize_range=4k-512k +iodepth=128 +ioengine=libaio +filename= +group_reporting +thread +numjobs=1 +direct=1 +do_verify=1 +verify=md5 +verify_fatal=1 +verify_dump=1 +verify_backlog=8 + +[randwrite] +rw=randwrite +runtime=15 +time_based +stonewall diff --git a/src/spdk/test/vhost/migration/migration-tc2.sh b/src/spdk/test/vhost/migration/migration-tc2.sh new file mode 100644 index 00000000..bc4a0f53 --- /dev/null +++ b/src/spdk/test/vhost/migration/migration-tc2.sh @@ -0,0 +1,209 @@ +source $SPDK_BUILD_DIR/test/nvmf/common.sh + +function migration_tc2_cleanup_nvmf_tgt() +{ + local i + + if [[ ! -r "$nvmf_dir/nvmf_tgt.pid" ]]; then + warning "Pid file '$nvmf_dir/nvmf_tgt.pid' does not exist. " + return + fi + + if [[ ! -z "$1" ]]; then + trap 'error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + pkill --signal $1 -F $nvmf_dir/nvmf_tgt.pid || true + sleep 5 + if ! pkill -F $nvmf_dir/nvmf_tgt.pid; then + fail "failed to kill nvmf_tgt app" + fi + else + pkill --signal SIGTERM -F $nvmf_dir/nvmf_tgt.pid || true + for (( i=0; i<20; i++ )); do + if ! pkill --signal 0 -F $nvmf_dir/nvmf_tgt.pid; then + break + fi + sleep 0.5 + done + + if pkill --signal 0 -F $nvmf_dir/nvmf_tgt.pid; then + error "nvmf_tgt failed to shutdown" + fi + fi + + rm $nvmf_dir/nvmf_tgt.pid + unset -v nvmf_dir rpc_nvmf +} + +function migration_tc2_cleanup_vhost_config() +{ + timing_enter migration_tc2_cleanup_vhost_config + + trap 'migration_tc2_cleanup_nvmf_tgt SIGKILL; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + + notice "Shutting down all VMs" + vm_shutdown_all + + notice "Removing vhost devices & controllers via RPC ..." + # Delete bdev first to remove all LUNs and SCSI targets + $rpc_0 delete_nvme_controller Nvme0 + $rpc_0 remove_vhost_controller $incoming_vm_ctrlr + + $rpc_1 delete_nvme_controller Nvme0 + $rpc_1 remove_vhost_controller $target_vm_ctrlr + + notice "killing vhost app" + spdk_vhost_kill 0 + spdk_vhost_kill 1 + + unset -v incoming_vm target_vm incoming_vm_ctrlr target_vm_ctrlr + unset -v rpc_0 rpc_1 + + trap 'error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + migration_tc2_cleanup_nvmf_tgt + + timing_exit migration_tc2_cleanup_vhost_config +} + +function migration_tc2_configure_vhost() +{ + timing_enter migration_tc2_configure_vhost + + # Those are global intentionally - they will be unset in cleanup handler + nvmf_dir="$TEST_DIR/nvmf_tgt" + + incoming_vm=1 + target_vm=2 + incoming_vm_ctrlr=naa.VhostScsi0.$incoming_vm + target_vm_ctrlr=naa.VhostScsi0.$target_vm + + rpc_nvmf="$SPDK_BUILD_DIR/scripts/rpc.py -s $nvmf_dir/rpc.sock" + rpc_0="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir 0)/rpc.sock" + rpc_1="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir 1)/rpc.sock" + + # Default cleanup/error handlers will not shutdown nvmf_tgt app so setup it + # here to teardown in cleanup function + trap 'migration_tc2_error_cleanup; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + + # Run nvmf_tgt and two vhost instances: + # nvmf_tgt uses core id 2 (-m 0x4) + # First uses core id 0 (vhost_0_reactor_mask=0x1) + # Second uses core id 1 (vhost_1_reactor_mask=0x2) + # This force to use VM 1 and 2. + timing_enter start_nvmf_tgt + notice "Running nvmf_tgt..." + mkdir -p $nvmf_dir + rm -f $nvmf_dir/* + $SPDK_BUILD_DIR/app/nvmf_tgt/nvmf_tgt -s 512 -m 0x4 -r $nvmf_dir/rpc.sock --wait-for-rpc & + local nvmf_tgt_pid=$! + echo $nvmf_tgt_pid > $nvmf_dir/nvmf_tgt.pid + waitforlisten "$nvmf_tgt_pid" "$nvmf_dir/rpc.sock" + $rpc_nvmf start_subsystem_init + $rpc_nvmf nvmf_create_transport -t RDMA -u 8192 -p 4 + $SPDK_BUILD_DIR/scripts/gen_nvme.sh --json | $rpc_nvmf load_subsystem_config + timing_exit start_nvmf_tgt + + spdk_vhost_run --memory=512 --vhost-num=0 --no-pci + # Those are global intentionally + vhost_1_reactor_mask=0x2 + vhost_1_master_core=1 + spdk_vhost_run --memory=512 --vhost-num=1 --no-pci + + local rdma_ip_list=$(get_available_rdma_ips) + local nvmf_target_ip=$(echo "$rdma_ip_list" | head -n 1) + + if [[ -z "$nvmf_target_ip" ]]; then + fail "no NIC for nvmf target" + fi + + notice "Configuring nvmf_tgt, vhost devices & controllers via RPC ..." + + # Construct shared bdevs and controllers + $rpc_nvmf nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 + $rpc_nvmf nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 Nvme0n1 + $rpc_nvmf nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $nvmf_target_ip -s 4420 + + $rpc_0 construct_nvme_bdev -b Nvme0 -t rdma -f ipv4 -a $nvmf_target_ip -s 4420 -n "nqn.2016-06.io.spdk:cnode1" + $rpc_0 construct_vhost_scsi_controller $incoming_vm_ctrlr + $rpc_0 add_vhost_scsi_lun $incoming_vm_ctrlr 0 Nvme0n1 + + $rpc_1 construct_nvme_bdev -b Nvme0 -t rdma -f ipv4 -a $nvmf_target_ip -s 4420 -n "nqn.2016-06.io.spdk:cnode1" + $rpc_1 construct_vhost_scsi_controller $target_vm_ctrlr + $rpc_1 add_vhost_scsi_lun $target_vm_ctrlr 0 Nvme0n1 + + notice "Setting up VMs" + vm_setup --os="$os_image" --force=$incoming_vm --disk-type=spdk_vhost_scsi --disks=VhostScsi0 \ + --migrate-to=$target_vm --memory=1024 --vhost-num=0 + vm_setup --force=$target_vm --disk-type=spdk_vhost_scsi --disks=VhostScsi0 --incoming=$incoming_vm --memory=1024 \ + --vhost-num=1 + + # Run everything + vm_run $incoming_vm $target_vm + + # Wait only for incoming VM, as target is waiting for migration + vm_wait_for_boot 600 $incoming_vm + + notice "Configuration done" + + timing_exit migration_tc2_configure_vhost +} + +function migration_tc2_error_cleanup() +{ + trap - SIGINT ERR EXIT + set -x + + vm_kill_all + migration_tc2_cleanup_vhost_config + notice "Migration TC2 FAILED" +} + +function migration_tc2() +{ + # Use 2 VMs: + # incoming VM - the one we want to migrate + # targe VM - the one which will accept migration + local job_file="$MIGRATION_DIR/migration-tc2.job" + + migration_tc2_configure_vhost + + # Run fio before migration + notice "Starting FIO" + vm_check_scsi_location $incoming_vm + run_fio $fio_bin --job-file="$job_file" --local --vm="${incoming_vm}$(printf ':/dev/%s' $SCSI_DISK)" + + # Wait a while to let the FIO time to issue some IO + sleep 5 + + # Check if fio is still running before migration + if ! is_fio_running $incoming_vm; then + vm_ssh $incoming_vm "cat /root/$(basename ${job_file}).out" + error "FIO is not running before migration: process crashed or finished too early" + fi + + vm_migrate $incoming_vm + sleep 3 + + # Check if fio is still running after migration + if ! is_fio_running $target_vm; then + vm_ssh $target_vm "cat /root/$(basename ${job_file}).out" + error "FIO is not running after migration: process crashed or finished too early" + fi + + notice "Waiting for fio to finish" + local timeout=40 + while is_fio_running $target_vm; do + sleep 1 + echo -n "." + if (( timeout-- == 0 )); then + error "timeout while waiting for FIO!" + fi + done + + notice "Fio result is:" + vm_ssh $target_vm "cat /root/$(basename ${job_file}).out" + + migration_tc2_cleanup_vhost_config + notice "Migration TC2 SUCCESS" +} + +migration_tc2 diff --git a/src/spdk/test/vhost/migration/migration-tc3.job b/src/spdk/test/vhost/migration/migration-tc3.job new file mode 100644 index 00000000..fe192966 --- /dev/null +++ b/src/spdk/test/vhost/migration/migration-tc3.job @@ -0,0 +1,20 @@ +[global] +blocksize=4k-512k +iodepth=128 +ioengine=libaio +filename= +group_reporting +thread +numjobs=1 +direct=1 +do_verify=1 +verify=md5 +verify_fatal=1 +verify_dump=1 +verify_backlog=8 + +[randwrite] +rw=randwrite +runtime=15 +time_based +stonewall diff --git a/src/spdk/test/vhost/migration/migration-tc3a.sh b/src/spdk/test/vhost/migration/migration-tc3a.sh new file mode 100644 index 00000000..0f20b994 --- /dev/null +++ b/src/spdk/test/vhost/migration/migration-tc3a.sh @@ -0,0 +1,227 @@ +source $SPDK_BUILD_DIR/test/nvmf/common.sh +source $MIGRATION_DIR/autotest.config + +incoming_vm=1 +target_vm=2 +incoming_vm_ctrlr=naa.VhostScsi0.$incoming_vm +target_vm_ctrlr=naa.VhostScsi0.$target_vm +share_dir=$TEST_DIR/share +spdk_repo_share_dir=$TEST_DIR/share_spdk +job_file=$MIGRATION_DIR/migration-tc3.job + +if [ -z "$MGMT_TARGET_IP" ]; then + error "No IP address of target is given" +fi + +if [ -z "$MGMT_INITIATOR_IP" ]; then + error "No IP address of initiator is given" +fi + +if [ -z "$RDMA_TARGET_IP" ]; then + error "No IP address of targets RDMA capable NIC is given" +fi + +if [ -z "$RDMA_INITIATOR_IP" ]; then + error "No IP address of initiators RDMA capable NIC is given" +fi + +function ssh_remote() +{ + local ssh_cmd="ssh -i $SPDK_VHOST_SSH_KEY_FILE \ + -o UserKnownHostsFile=/dev/null \ + -o StrictHostKeyChecking=no -o ControlMaster=auto \ + root@$1" + + shift + $ssh_cmd "$@" +} + +function wait_for_remote() +{ + local timeout=40 + set +x + while [[ ! -f $share_dir/DONE ]]; do + echo -n "." + if (( timeout-- == 0 )); then + error "timeout while waiting for FIO!" + fi + sleep 1 + done + set -x + rm -f $share_dir/DONE +} + +function check_rdma_connection() +{ + local nic_name=$(ip -4 -o addr show to $RDMA_TARGET_IP up | cut -d' ' -f2) + if [[ -z $nic_name ]]; then + error "There is no NIC with IP address $RDMA_TARGET_IP configured" + fi + + if ! ls /sys/class/infiniband/*/device/net/$nic_name &> /dev/null; then + error "$nic_name with IP $RDMA_TARGET_IP is not a RDMA capable NIC" + fi + +} + +function host1_cleanup_nvmf() +{ + notice "Shutting down nvmf_tgt on local server" + if [[ ! -z "$1" ]]; then + pkill --signal $1 -F $nvmf_dir/nvmf_tgt.pid + else + pkill -F $nvmf_dir/nvmf_tgt.pid + fi + rm -f $nvmf_dir/nvmf_tgt.pid +} + +function host1_cleanup_vhost() +{ + trap 'host1_cleanup_nvmf SIGKILL; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + notice "Shutting down VM $incoming_vm" + vm_kill $incoming_vm + + notice "Removing bdev & controller from vhost on local server" + $rpc_0 delete_nvme_controller Nvme0 + $rpc_0 remove_vhost_controller $incoming_vm_ctrlr + + notice "Shutting down vhost app" + spdk_vhost_kill 0 + + host1_cleanup_nvmf +} + +function host1_start_nvmf() +{ + nvmf_dir="$TEST_DIR/nvmf_tgt" + rpc_nvmf="$SPDK_BUILD_DIR/scripts/rpc.py -s $nvmf_dir/nvmf_rpc.sock" + + notice "Starting nvmf_tgt instance on local server" + mkdir -p $nvmf_dir + rm -rf $nvmf_dir/* + + trap 'host1_cleanup_nvmf SIGKILL; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + $SPDK_BUILD_DIR/app/nvmf_tgt/nvmf_tgt -s 512 -m 0xF -r $nvmf_dir/nvmf_rpc.sock --wait-for-rpc & + nvmf_tgt_pid=$! + echo $nvmf_tgt_pid > $nvmf_dir/nvmf_tgt.pid + waitforlisten "$nvmf_tgt_pid" "$nvmf_dir/nvmf_rpc.sock" + $rpc_nvmf start_subsystem_init + $rpc_nvmf nvmf_create_transport -t RDMA -u 8192 -p 4 + $SPDK_BUILD_DIR/scripts/gen_nvme.sh --json | $rpc_nvmf load_subsystem_config + + $rpc_nvmf nvmf_subsystem_create nqn.2018-02.io.spdk:cnode1 -a -s SPDK01 + $rpc_nvmf nvmf_subsystem_add_ns nqn.2018-02.io.spdk:cnode1 Nvme0n1 + $rpc_nvmf nvmf_subsystem_add_listener nqn.2018-02.io.spdk:cnode1 -t rdma -a $RDMA_TARGET_IP -s 4420 +} + +function host1_start_vhost() +{ + rpc_0="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir 0)/rpc.sock" + + notice "Starting vhost0 instance on local server" + trap 'host1_cleanup_vhost; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + spdk_vhost_run --vhost-num=0 --no-pci + $rpc_0 construct_nvme_bdev -b Nvme0 -t rdma -f ipv4 -a $RDMA_TARGET_IP -s 4420 -n "nqn.2018-02.io.spdk:cnode1" + $rpc_0 construct_vhost_scsi_controller $incoming_vm_ctrlr + $rpc_0 add_vhost_scsi_lun $incoming_vm_ctrlr 0 Nvme0n1 + + vm_setup --os="$share_dir/migration.qcow2" --force=$incoming_vm --disk-type=spdk_vhost_scsi --disks=VhostScsi0 \ + --migrate-to=$target_vm --memory=512 --queue_num=1 + + # TODO: Fix loop calculating cpu_num in common.sh + # We need -smp 1 and -queue_num 1 for this test to work, and this loop + # in some cases calculates wrong cpu_num. + sed -i "s#smp 2#smp 1#g" $VM_BASE_DIR/$incoming_vm/run.sh + vm_run $incoming_vm + vm_wait_for_boot 300 $incoming_vm +} + +function cleanup_share() +{ + set +e + notice "Cleaning up share directory on remote and local server" + ssh_remote $MGMT_INITIATOR_IP "umount $VM_BASE_DIR" + ssh_remote $MGMT_INITIATOR_IP "umount $share_dir; rm -f $share_dir/* rm -rf $spdk_repo_share_dir" + rm -f $share_dir/migration.qcow2 + rm -f $share_dir/spdk.tar.gz + set -e +} + +function host_1_create_share() +{ + notice "Creating share directory on local server to re-use on remote" + mkdir -p $share_dir + mkdir -p $VM_BASE_DIR # This dir would've been created later but we need it now + rm -rf $share_dir/spdk.tar.gz $share_dir/spdk || true + cp $os_image $share_dir/migration.qcow2 + tar --exclude="*.o"--exclude="*.d" --exclude="*.git" -C $SPDK_BUILD_DIR -zcf $share_dir/spdk.tar.gz . +} + +function host_2_create_share() +{ + # Copy & compile the sources for later use on remote server. + ssh_remote $MGMT_INITIATOR_IP "uname -a" + ssh_remote $MGMT_INITIATOR_IP "mkdir -p $share_dir" + ssh_remote $MGMT_INITIATOR_IP "mkdir -p $spdk_repo_share_dir" + ssh_remote $MGMT_INITIATOR_IP "mkdir -p $VM_BASE_DIR" + ssh_remote $MGMT_INITIATOR_IP "sshfs -o\ + ssh_command=\"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ControlMaster=auto\ + -i $SPDK_VHOST_SSH_KEY_FILE\" root@$MGMT_TARGET_IP:$VM_BASE_DIR $VM_BASE_DIR" + ssh_remote $MGMT_INITIATOR_IP "sshfs -o\ + ssh_command=\"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ControlMaster=auto\ + -i $SPDK_VHOST_SSH_KEY_FILE\" root@$MGMT_TARGET_IP:$share_dir $share_dir" + ssh_remote $MGMT_INITIATOR_IP "mkdir -p $spdk_repo_share_dir/spdk" + ssh_remote $MGMT_INITIATOR_IP "tar -zxf $share_dir/spdk.tar.gz -C $spdk_repo_share_dir/spdk --strip-components=1" + ssh_remote $MGMT_INITIATOR_IP "cd $spdk_repo_share_dir/spdk; make clean; ./configure --with-rdma --enable-debug; make -j40" +} + +function host_2_start_vhost() +{ + ssh_remote $MGMT_INITIATOR_IP "nohup $spdk_repo_share_dir/spdk/test/vhost/migration/migration.sh\ + --test-cases=3b --work-dir=$TEST_DIR --os=$share_dir/migration.qcow2\ + --rdma-tgt-ip=$RDMA_TARGET_IP &>$share_dir/output.log &" + notice "Waiting for remote to be done with vhost & VM setup..." + wait_for_remote +} + +function setup_share() +{ + trap 'cleanup_share; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + host_1_create_share + host_2_create_share +} + +function migration_tc3() +{ + check_rdma_connection + setup_share + host1_start_nvmf + host1_start_vhost + host_2_start_vhost + + # Do migration + notice "Starting fio on local VM" + vm_check_scsi_location $incoming_vm + + run_fio $fio_bin --job-file="$job_file" --local --vm="${incoming_vm}$(printf ':/dev/%s' $SCSI_DISK)" + sleep 5 + + if ! is_fio_running $incoming_vm; then + vh_ssh $incoming_vm "cat /root/$(basename ${job_file}).out" + error "Fio not running on local VM before starting migration!" + fi + + vm_migrate $incoming_vm $RDMA_INITIATOR_IP + sleep 1 + + # Verify migration on remote host and clean up vhost + ssh_remote $MGMT_INITIATOR_IP "pkill -CONT -F $TEST_DIR/tc3b.pid" + notice "Waiting for remote to finish FIO on VM and clean up..." + wait_for_remote + + # Clean up local stuff + host1_cleanup_vhost + cleanup_share +} + +migration_tc3 diff --git a/src/spdk/test/vhost/migration/migration-tc3b.sh b/src/spdk/test/vhost/migration/migration-tc3b.sh new file mode 100644 index 00000000..babba0dc --- /dev/null +++ b/src/spdk/test/vhost/migration/migration-tc3b.sh @@ -0,0 +1,79 @@ +# Set -m option is needed to be able to use "suspend" command +# as we are usin non-interactive session to connect to remote. +# Without -m it would be not possible to suspend the process. +set -m +source $MIGRATION_DIR/autotest.config + +incoming_vm=1 +target_vm=2 +target_vm_ctrl=naa.VhostScsi0.$target_vm +rpc="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir 1)/rpc.sock" +share_dir=$TEST_DIR/share + +function host_2_cleanup_vhost() +{ + notice "Shutting down VM $target_vm" + vm_kill $target_vm + + notice "Removing bdev & controller from vhost 1 on remote server" + $rpc delete_nvme_controller Nvme0 + $rpc remove_vhost_controller $target_vm_ctrl + + notice "Shutting down vhost app" + spdk_vhost_kill 1 + sleep 1 +} + +function host_2_start_vhost() +{ + echo "BASE DIR $TEST_DIR" + vhost_work_dir=$TEST_DIR/vhost1 + mkdir -p $vhost_work_dir + rm -f $vhost_work_dir/* + + notice "Starting vhost 1 instance on remote server" + trap 'host_2_cleanup_vhost; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + spdk_vhost_run --vhost-num=1 --no-pci + + $rpc construct_nvme_bdev -b Nvme0 -t rdma -f ipv4 -a $RDMA_TARGET_IP -s 4420 -n "nqn.2018-02.io.spdk:cnode1" + $rpc construct_vhost_scsi_controller $target_vm_ctrl + $rpc add_vhost_scsi_lun $target_vm_ctrl 0 Nvme0n1 + + vm_setup --os="$os_image" --force=$target_vm --disk-type=spdk_vhost_scsi --disks=VhostScsi0 \ + --memory=512 --vhost-num=1 --incoming=$incoming_vm + vm_run $target_vm + sleep 1 + + # Use this file as a flag to notify main script + # that setup on remote server is done + echo "DONE" > $share_dir/DONE +} + +echo $$ > $TEST_DIR/tc3b.pid +host_2_start_vhost +suspend -f + +if ! vm_os_booted $target_vm; then + fail "VM$target_vm is not running!" +fi + +if ! is_fio_running $target_vm; then + vm_ssh $target_vm "cat /root/migration-tc3.job.out" + error "FIO is not running on remote server after migration!" +fi + +notice "Waiting for FIO to finish on remote server VM" +timeout=40 +while is_fio_running $target_vm; do + sleep 1 + echo -n "." + if (( timeout-- == 0 )); then + error "timeout while waiting for FIO!" + fi +done + +notice "FIO result after migration:" +vm_ssh $target_vm "cat /root/migration-tc3.job.out" + +host_2_cleanup_vhost +echo "DONE" > $share_dir/DONE diff --git a/src/spdk/test/vhost/migration/migration.sh b/src/spdk/test/vhost/migration/migration.sh new file mode 100755 index 00000000..bdfcd845 --- /dev/null +++ b/src/spdk/test/vhost/migration/migration.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash + +set -e + +vms=() +declare -A vms_os +declare -A vms_raw_disks +declare -A vms_ctrlrs +declare -A vms_ctrlrs_disks + +# By default use Guest fio +fio_bin="" +test_cases="" +MGMT_TARGET_IP="" +MGMT_INITIATOR_IP="" +RDMA_TARGET_IP="" +RDMA_INITIATOR_IP="" +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for doing automated test of live migration." + echo "Usage: $(basename $1) [OPTIONS]" + echo + echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: $TEST_DIR]" + echo " --os ARGS VM configuration. This parameter might be used more than once:" + echo " --fio-bin=FIO Use specific fio binary (will be uploaded to VM)" + echo " --test-cases=TESTS Coma-separated list of tests to run. Implemented test cases are: 1" + echo " See test/vhost/test_plan.md for more info." + echo " --mgmt-tgt-ip=IP IP address of target." + echo " --mgmt-init-ip=IP IP address of initiator." + echo " --rdma-tgt-ip=IP IP address of targets rdma capable NIC." + echo " --rdma-init-ip=IP IP address of initiators rdma capable NIC." + echo "-x set -x for script debug" +} + +for param in "$@"; do + case "$param" in + --help|-h) + usage $0 + exit 0 + ;; + --work-dir=*) TEST_DIR="${param#*=}" ;; + --os=*) os_image="${param#*=}" ;; + --fio-bin=*) fio_bin="${param}" ;; + --test-cases=*) test_cases="${param#*=}" ;; + --mgmt-tgt-ip=*) MGMT_TARGET_IP="${param#*=}" ;; + --mgmt-init-ip=*) MGMT_INITIATOR_IP="${param#*=}" ;; + --rdma-tgt-ip=*) RDMA_TARGET_IP="${param#*=}" ;; + --rdma-init-ip=*) RDMA_INITIATOR_IP="${param#*=}" ;; + -x) set -x ;; + -v) SPDK_VHOST_VERBOSE=true ;; + *) + usage $0 "Invalid argument '$param'" + exit 1;; + esac +done + +. $(readlink -e "$(dirname $0)/../common/common.sh") || exit 1 +MIGRATION_DIR=$(readlink -f $(dirname $0)) + +[[ ! -z "$test_cases" ]] || fail "Need '--test-cases=' parameter" + +trap 'error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT + +function vm_monitor_send() +{ + local vm_num=$1 + local cmd_result_file="$2" + local vm_dir="$VM_BASE_DIR/$1" + local vm_monitor_port=$(cat $vm_dir/monitor_port) + + [[ ! -z "$vm_monitor_port" ]] || fail "No monitor port!" + + shift 2 + nc 127.0.0.1 $vm_monitor_port "$@" > $cmd_result_file +} + +# Migrate VM $1 +function vm_migrate() +{ + local from_vm_dir="$VM_BASE_DIR/$1" + local target_vm_dir="$(readlink -e $from_vm_dir/vm_migrate_to)" + local target_vm="$(basename $target_vm_dir)" + local target_vm_migration_port="$(cat $target_vm_dir/migration_port)" + if [[ -n "$2" ]]; then + local target_ip=$2 + else + local target_ip="127.0.0.1" + fi + + # Sanity check if target VM (QEMU) is configured to accept source VM (QEMU) migration + if [[ "$(readlink -e ${target_vm_dir}/vm_incoming)" != "$(readlink -e ${from_vm_dir})" ]]; then + fail "source VM $1 or destination VM is not properly configured for live migration" + fi + + timing_enter vm_migrate + notice "Migrating VM $1 to VM "$(basename $target_vm_dir) + echo -e \ + "migrate_set_speed 1g\n" \ + "migrate tcp:$target_ip:$target_vm_migration_port\n" \ + "info migrate\n" \ + "quit" | vm_monitor_send $1 "$from_vm_dir/migration_result" + + # Post migration checks: + if ! grep "Migration status: completed" $from_vm_dir/migration_result -q; then + cat $from_vm_dir/migration_result + fail "Migration failed:\n" + fi + + # Don't perform the following check if target VM is on remote server + # as we won't have access to it. + # If you need this check then perform it on your own. + if [[ "$target_ip" == "127.0.0.1" ]]; then + if ! vm_os_booted $target_vm; then + fail "VM$target_vm is not running" + cat $target_vm $target_vm_dir/cont_result + fi + fi + + notice "Migration complete" + timing_exit vm_migrate +} + +function is_fio_running() +{ + local shell_restore_x="$( [[ "$-" =~ x ]] && echo 'set -x' )" + set +x + + if vm_ssh $1 'kill -0 $(cat /root/fio.pid)'; then + local ret=0 + else + local ret=1 + fi + + $shell_restore_x + return $ret +} + +for test_case in ${test_cases//,/ }; do + assert_number "$test_case" + notice "===============================" + notice "Running Migration test case ${test_case}" + notice "===============================" + + timing_enter migration-tc${test_case} + source $MIGRATION_DIR/migration-tc${test_case}.sh + timing_exit migration-tc${test_case} +done + +notice "Migration Test SUCCESS" +notice "===============" + +trap - SIGINT ERR EXIT diff --git a/src/spdk/test/vhost/other/conf.json b/src/spdk/test/vhost/other/conf.json new file mode 100644 index 00000000..7a60c68c --- /dev/null +++ b/src/spdk/test/vhost/other/conf.json @@ -0,0 +1,43 @@ +{ + "subsystems": [ + { + "subsystem": "copy", + "config": null + }, + { + "subsystem": "interface", + "config": null + }, + { + "subsystem": "net_framework", + "config": null + }, + { + "subsystem": "bdev", + "config": [ + { + "params": { + "block_size": 4096, + "num_blocks": 32768 + }, + "method": "construct_malloc_bdev" + }, + { + "params": { + "block_size": 4096, + "num_blocks": 32768 + }, + "method": "construct_malloc_bdev" + } + ] + }, + { + "subsystem": "nbd", + "config": [] + }, + { + "subsystem": "scsi", + "config": null + } + ] +} diff --git a/src/spdk/test/vhost/other/negative.sh b/src/spdk/test/vhost/other/negative.sh new file mode 100755 index 00000000..5728a283 --- /dev/null +++ b/src/spdk/test/vhost/other/negative.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash + +NEGATIVE_BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $NEGATIVE_BASE_DIR/../common && pwd)" +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $NEGATIVE_BASE_DIR/../../../../ && pwd)" + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for running vhost app." + echo "Usage: $(basename $1) [-x] [-h|--help] [--clean-build] [--work-dir=PATH]" + echo "-h, --help print help and exit" + echo "-x Set -x for script debug" + echo " --work-dir=PATH Where to find source/project. [default=$TEST_DIR]" + + exit 0 +} + +run_in_background=false +while getopts 'xh-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 ;; + work-dir=*) TEST_DIR="${OPTARG#*=}" ;; + conf-dir=*) CONF_DIR="${OPTARG#*=}" ;; + *) usage $0 echo "Invalid argument '$OPTARG'" ;; + esac + ;; + h) usage $0 ;; + x) set -x ;; + *) usage $0 "Invalid argument '$optchar'" ;; + esac +done + + +. $COMMON_DIR/common.sh + +trap error_exit ERR + +VHOST_APP="$SPDK_BUILD_DIR/app/vhost/vhost" + +notice "Testing vhost command line arguments" +# Printing help will force vhost to exit without error +$VHOST_APP -c /path/to/non_existing_file/conf -S $NEGATIVE_BASE_DIR -e 0x0 -s 1024 -d -h --silence-noticelog + +# Testing vhost create pid file option. Vhost will exit with error as invalid config path is given +if $VHOST_APP -c /path/to/non_existing_file/conf -f $SPDK_VHOST_SCSI_TEST_DIR/vhost.pid; then + fail "vhost started when specifying invalid config file" +fi + +# Expecting vhost to fail if an incorrect argument is given +if $VHOST_APP -x -h; then + fail "vhost started with invalid -x command line option" +fi + +# Passing trace flags if spdk is build without CONFIG_DEBUG=y option make vhost exit with error +if ! $VHOST_APP -t vhost_scsi -h; then + warning "vhost did not started with trace flags enabled but ignoring this as it might not be a debug build" +fi + +if [[ $RUN_NIGHTLY -eq 1 ]]; then + # Run with valid config and try some negative rpc calls + notice "===============" + notice "" + notice "running SPDK" + notice "" + spdk_vhost_run --json-path=$NEGATIVE_BASE_DIR + notice "" + + rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock" + + # General commands + notice "Trying to remove nonexistent controller" + if $rpc_py remove_vhost_controller unk0 > /dev/null; then + error "Removing nonexistent controller succeeded, but it shouldn't" + fi + + # SCSI + notice "Trying to create scsi controller with incorrect cpumask" + if $rpc_py construct_vhost_scsi_controller vhost.invalid.cpumask --cpumask 0x2; then + error "Creating scsi controller with incorrect cpumask succeeded, but it shouldn't" + fi + + notice "Trying to remove device from nonexistent scsi controller" + if $rpc_py remove_vhost_scsi_target vhost.nonexistent.name 0; then + error "Removing device from nonexistent scsi controller succeeded, but it shouldn't" + fi + + notice "Trying to add device to nonexistent scsi controller" + if $rpc_py add_vhost_scsi_lun vhost.nonexistent.name 0 Malloc0; then + error "Adding device to nonexistent scsi controller succeeded, but it shouldn't" + fi + + notice "Trying to create scsi controller with incorrect name" + if $rpc_py construct_vhost_scsi_controller .; then + error "Creating scsi controller with incorrect name succeeded, but it shouldn't" + fi + + notice "Creating controller naa.0" + $rpc_py construct_vhost_scsi_controller naa.0 + + notice "Adding initial device (0) to naa.0" + $rpc_py add_vhost_scsi_lun naa.0 0 Malloc0 + + notice "Trying to remove nonexistent device on existing controller" + if $rpc_py remove_vhost_scsi_target naa.0 1 > /dev/null; then + error "Removing nonexistent device (1) from controller naa.0 succeeded, but it shouldn't" + fi + + notice "Trying to remove existing device from a controller" + $rpc_py remove_vhost_scsi_target naa.0 0 + + notice "Trying to remove a just-deleted device from a controller again" + if $rpc_py remove_vhost_scsi_target naa.0 0 > /dev/null; then + error "Removing device 0 from controller naa.0 succeeded, but it shouldn't" + fi + + notice "Re-adding device 0 to naa.0" + $rpc_py add_vhost_scsi_lun naa.0 0 Malloc0 + + # BLK + notice "Trying to create block controller with incorrect cpumask" + if $rpc_py construct_vhost_blk_controller vhost.invalid.cpumask Malloc0 --cpumask 0x2; then + error "Creating block controller with incorrect cpumask succeeded, but it shouldn't" + fi + + notice "Trying to remove nonexistent block controller" + if $rpc_py remove_vhost_controller vhost.nonexistent.name; then + error "Removing nonexistent block controller succeeded, but it shouldn't" + fi + + notice "Trying to create block controller with incorrect name" + if $rpc_py construct_vhost_blk_controller . Malloc0; then + error "Creating block controller with incorrect name succeeded, but it shouldn't" + fi + + notice "Testing done -> shutting down" + notice "killing vhost app" + spdk_vhost_kill + + notice "EXIT DONE" + notice "===============" +fi diff --git a/src/spdk/test/vhost/perf_bench/vhost_perf.sh b/src/spdk/test/vhost/perf_bench/vhost_perf.sh new file mode 100755 index 00000000..3789c8f1 --- /dev/null +++ b/src/spdk/test/vhost/perf_bench/vhost_perf.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +set -e + +vm_count=1 +vm_memory=2048 +vm_image="/home/sys_sgsw/vhost_vm_image.qcow2" +max_disks="" +ctrl_type="spdk_vhost_scsi" +use_split=false +throttle=false + +lvol_stores=() +lvol_bdevs=() +used_vms="" + +fio_bin="--fio-bin=/home/sys_sgsw/fio_ubuntu" + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for doing automated test" + echo "Usage: $(basename $1) [OPTIONS]" + echo + echo "-h, --help Print help and exit" + echo " --fio-bin=PATH Path to FIO binary on host.;" + echo " Binary will be copied to VM, static compilation" + echo " of binary is recommended." + echo " --fio-job=PATH Fio config to use for test." + echo " --vm-count=INT Total number of virtual machines to launch in this test;" + echo " Each VM will get one bdev (lvol or split vbdev)" + echo " to run FIO test." + echo " Default: 1" + echo " --vm-memory=INT Amount of RAM memory (in MB) to pass to a single VM." + echo " Default: 2048 MB" + echo " --vm-image=PATH OS image to use for running the VMs." + echo " Default: /home/sys_sgsw/vhost_vm_image.qcow2" + echo " --max-disks=INT Maximum number of NVMe drives to use in test." + echo " Default: will use all available NVMes." + echo " --ctrl-type=TYPE Controller type to use for test:" + echo " spdk_vhost_scsi - use spdk vhost scsi" + echo " spdk_vhost_blk - use spdk vhost block" + echo " Default: spdk_vhost_scsi" + echo " --use-split Use split vbdevs instead of Logical Volumes" + echo " --throttle=INT I/Os throttle rate in IOPS for each device on the VMs." + echo " --custom-cpu-cfg=PATH Custom CPU config for test." + echo " Default: spdk/test/vhost/common/autotest.config" + echo "-x set -x for script debug" + exit 0 +} + +function cleanup_lvol_cfg() +{ + notice "Removing lvol bdevs" + for lvol_bdev in "${lvol_bdevs[@]}"; do + $rpc_py destroy_lvol_bdev $lvol_bdev + notice "lvol bdev $lvol_bdev removed" + done + + notice "Removing lvol stores" + for lvol_store in "${lvol_stores[@]}"; do + $rpc_py destroy_lvol_store -u $lvol_store + notice "lvol store $lvol_store removed" + done +} + +function cleanup_split_cfg() +{ + notice "Removing split vbdevs" + for (( i=0; i<$max_disks; i++ ));do + $rpc_py destruct_split_vbdev Nvme${i}n1 + done +} + +while getopts 'xh-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 ;; + fio-bin=*) fio_bin="--fio-bin=${OPTARG#*=}" ;; + fio-job=*) fio_job="${OPTARG#*=}" ;; + vm-count=*) vm_count="${OPTARG#*=}" ;; + vm-memory=*) vm_memory="${OPTARG#*=}" ;; + vm-image=*) vm_image="${OPTARG#*=}" ;; + max-disks=*) max_disks="${OPTARG#*=}" ;; + ctrl-type=*) ctrl_type="${OPTARG#*=}" ;; + use-split) use_split=true ;; + throttle) throttle=true ;; + custom-cpu-cfg=*) custom_cpu_cfg="${OPTARG#*=}" ;; + thin-provisioning) thin=" -t " ;; + multi-os) multi_os=true ;; + *) usage $0 "Invalid argument '$OPTARG'" ;; + esac + ;; + h) usage $0 ;; + x) set -x + x="-x" ;; + *) usage $0 "Invalid argument '$OPTARG'" + esac +done + +. $(readlink -e "$(dirname $0)/../common/common.sh") || exit 1 +. $(readlink -e "$(dirname $0)/../../../scripts/common.sh") || exit 1 +COMMON_DIR="$(cd $(readlink -f $(dirname $0))/../common && pwd)" +rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock" + +if [[ -n $custom_cpu_cfg ]]; then + source $custom_cpu_cfg +fi + +if [[ -z $fio_job ]]; then + warning "No FIO job specified! Will use default from common directory." + fio_job="$COMMON_DIR/fio_jobs/default_integrity.job" +fi + +trap 'error_exit "${FUNCNAME}" "${LINENO}"' INT ERR +notice "Get NVMe disks:" +nvmes=($(iter_pci_class_code 01 08 02)) + +if [[ -z $max_disks ]]; then + max_disks=${#nvmes[@]} +fi + +if [[ ${#nvmes[@]} -lt max_disks ]]; then + fail "Number of NVMe drives (${#nvmes[@]}) is lower than number of requested disks for test ($max_disks)" +fi + +notice "running SPDK vhost" +spdk_vhost_run +notice "..." + +# Calculate number of needed splits per NVMe +# so that each VM gets it's own bdev during test +splits=() + +#Calculate least minimum number of splits on each disks +for i in `seq 0 $((max_disks - 1))`; do + splits+=( $((vm_count / max_disks)) ) +done + +# Split up the remainder +for i in `seq 0 $((vm_count % max_disks - 1))`; do + (( splits[i]++ )) +done + +notice "Preparing NVMe setup..." +notice "Using $max_disks physical NVMe drives" +notice "Nvme split list: ${splits[@]}" +# Prepare NVMes - Lvols or Splits +if [[ $use_split == true ]]; then + notice "Using split vbdevs" + trap 'cleanup_split_cfg; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR + split_bdevs=() + for (( i=0; i<$max_disks; i++ ));do + out=$($rpc_py construct_split_vbdev Nvme${i}n1 ${splits[$i]}) + for s in $out; do + split_bdevs+=("$s") + done + done + bdevs=("${split_bdevs[@]}") +else + notice "Using logical volumes" + trap 'cleanup_lvol_cfg; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR + for (( i=0; i<$max_disks; i++ ));do + ls_guid=$($rpc_py construct_lvol_store Nvme${i}n1 lvs_$i) + lvol_stores+=("$ls_guid") + for (( j=0; j<${splits[$i]}; j++)); do + free_mb=$(get_lvs_free_mb "$ls_guid") + size=$((free_mb / (${splits[$i]}-j) )) + lb_name=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_$j $size) + lvol_bdevs+=("$lb_name") + done + done + bdevs=("${lvol_bdevs[@]}") +fi + +# Prepare VMs and controllers +for (( i=0; i<$vm_count; i++)); do + vm="vm_$i" + + setup_cmd="vm_setup --disk-type=$ctrl_type --force=$i" + setup_cmd+=" --os=$vm_image" + + if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then + $rpc_py construct_vhost_scsi_controller naa.0.$i + $rpc_py add_vhost_scsi_lun naa.0.$i 0 ${bdevs[$i]} + setup_cmd+=" --disks=0" + elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then + $rpc_py construct_vhost_blk_controller naa.$i.$i ${bdevs[$i]} + setup_cmd+=" --disks=$i" + fi + $setup_cmd + used_vms+=" $i" +done + +# Start VMs +# Run VMs +vm_run $used_vms +vm_wait_for_boot 300 $used_vms + +# Run FIO +fio_disks="" +for vm_num in $used_vms; do + vm_dir=$VM_BASE_DIR/$vm_num + host_name="VM-$vm_num" + vm_ssh $vm_num "hostname $host_name" + vm_start_fio_server $fio_bin $vm_num + + if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then + vm_check_scsi_location $vm_num + elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then + vm_check_blk_location $vm_num + fi + + fio_disks+=" --vm=${vm_num}$(printf ':/dev/%s' $SCSI_DISK)" +done + +# Run FIO traffic +run_fio $fio_bin --job-file="$fio_job" --out="$TEST_DIR/fio_results" --json $fio_disks + +notice "Shutting down virtual machines..." +vm_shutdown_all + +#notice "Shutting down SPDK vhost app..." +if [[ $use_split == true ]]; then + cleanup_split_cfg +else + cleanup_lvol_cfg +fi +spdk_vhost_kill diff --git a/src/spdk/test/vhost/readonly/delete_partition_vm.sh b/src/spdk/test/vhost/readonly/delete_partition_vm.sh new file mode 100755 index 00000000..18230896 --- /dev/null +++ b/src/spdk/test/vhost/readonly/delete_partition_vm.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -xe +BASE_DIR=$(readlink -f $(dirname $0)) + +disk_name="vda" +test_folder_name="readonly_test" +test_file_name="some_test_file" + +function error() +{ + echo "===========" + echo -e "ERROR: $@" + echo "===========" + trap - ERR + set +e + umount "$test_folder_name" + rm -rf "$BASE_DIR/$test_folder_name" + exit 1 +} + +trap 'error "In delete_partition_vm.sh, line:" "${LINENO}"' ERR + +if [[ ! -d "/sys/block/$disk_name" ]]; then + error "No vhost-blk disk found!" +fi + +if (( $(lsblk -r -n -o RO -d "/dev/$disk_name") == 1 )); then + error "Vhost-blk disk is set as readonly!" +fi + +mkdir -p $test_folder_name + +echo "INFO: Mounting disk" +mount /dev/$disk_name"1" $test_folder_name + +echo "INFO: Removing folder and unmounting $test_folder_name" +umount "$test_folder_name" +rm -rf "$BASE_DIR/$test_folder_name" + +echo "INFO: Deleting partition" +echo -e "d\n1\nw" | fdisk /dev/$disk_name diff --git a/src/spdk/test/vhost/readonly/disabled_readonly_vm.sh b/src/spdk/test/vhost/readonly/disabled_readonly_vm.sh new file mode 100755 index 00000000..bd202433 --- /dev/null +++ b/src/spdk/test/vhost/readonly/disabled_readonly_vm.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +set -xe +BASE_DIR=$(readlink -f $(dirname $0)) + +disk_name="vda" +test_folder_name="readonly_test" +test_file_name="some_test_file" + +function error() +{ + echo "===========" + echo -e "ERROR: $@" + echo "===========" + trap - ERR + set +e + umount "$test_folder_name" + rm -rf "$BASE_DIR/$test_folder_name" + exit 1 +} + +trap 'error "In disabled_readonly_vm.sh, line:" "${LINENO}"' ERR + +if [[ ! -d "/sys/block/$disk_name" ]]; then + error "No vhost-blk disk found!" +fi + +if (( $(lsblk -r -n -o RO -d "/dev/$disk_name") == 1 )); then + error "Vhost-blk disk is set as readonly!" +fi + +parted -s /dev/$disk_name mklabel gpt +parted -s /dev/$disk_name mkpart primary 2048s 100% +partprobe +sleep 0.1 + +echo "INFO: Creating file system" +mkfs.ext4 -F /dev/$disk_name"1" + +echo "INFO: Mounting disk" +mkdir -p $test_folder_name +mount /dev/$disk_name"1" $test_folder_name + +echo "INFO: Creating a test file $test_file_name" +truncate -s "200M" $test_folder_name/$test_file_name +umount "$test_folder_name" +rm -rf "$BASE_DIR/$test_folder_name" diff --git a/src/spdk/test/vhost/readonly/enabled_readonly_vm.sh b/src/spdk/test/vhost/readonly/enabled_readonly_vm.sh new file mode 100755 index 00000000..79cf1ae5 --- /dev/null +++ b/src/spdk/test/vhost/readonly/enabled_readonly_vm.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +set -x +BASE_DIR=$(readlink -f $(dirname $0)) + +disk_name="vda" +test_folder_name="readonly_test" +test_file_name="some_test_file" + +function error() +{ + echo "===========" + echo -e "ERROR: $@" + echo "===========" + umount "$test_folder_name" + rm -rf "$BASE_DIR/$test_folder_name" + exit 1 +} + +if [[ ! -d "/sys/block/$disk_name" ]]; then + error "No vhost-blk disk found!" +fi + +if (( $(lsblk -r -n -o RO -d "/dev/$disk_name") == 0 )); then + error "Vhost-blk disk is not set as readonly!" +fi + +echo "INFO: Found vhost-blk disk with readonly flag" +if [[ ! -b "/dev/$disk_name"1"" ]]; then + error "Partition not found!" +fi + +mkdir $BASE_DIR/$test_folder_name +if [[ $? != 0 ]]; then + error "Failed to create test folder $test_folder_name" +fi + +echo "INFO: Mounting partition" +mount /dev/$disk_name"1" $BASE_DIR/$test_folder_name +if [[ $? != 0 ]]; then + error "Failed to mount partition $disk_name""1" +fi + +echo "INFO: Trying to create file on readonly disk" +truncate -s "200M" $test_folder_name/$test_file_name"_on_readonly" +if [[ $? == 0 ]]; then + error "Created a file on a readonly disk!" +fi + +if [[ -f $test_folder_name/$test_file_name ]]; then + echo "INFO: Trying to delete previously created file" + rm $test_folder_name/$test_file_name + if [[ $? == 0 ]]; then + error "Deleted a file from a readonly disk!" + fi +else + error "Previously created file not found!" +fi + +echo "INFO: Copying file from readonly disk" +cp $test_folder_name/$test_file_name $BASE_DIR +if ! rm $BASE_DIR/$test_file_name; then + error "Copied file from a readonly disk was not found!" +fi + +umount "$test_folder_name" +rm -rf "$BASE_DIR/$test_folder_name" +echo "INFO: Trying to create file system on a readonly disk" +if mkfs.ext4 -F /dev/$disk_name"1"; then + error "Created file system on a readonly disk!" +fi + +echo "INFO: Trying to delete partition from readonly disk" +if echo -e "d\n1\nw" | fdisk /dev/$disk_name; then + error "Deleted partition from readonly disk!" +fi diff --git a/src/spdk/test/vhost/readonly/readonly.sh b/src/spdk/test/vhost/readonly/readonly.sh new file mode 100755 index 00000000..d0b7968f --- /dev/null +++ b/src/spdk/test/vhost/readonly/readonly.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash + +set -e +READONLY_BASE_DIR=$(readlink -f $(dirname $0)) +[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $READONLY_BASE_DIR/../../../../ && pwd)" +[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $READONLY_BASE_DIR/../common && pwd)" +source $COMMON_DIR/common.sh + +rpc_py="$READONLY_BASE_DIR/../../../scripts/rpc.py -s $(get_vhost_dir)/rpc.sock" + +vm_img="" +disk="Nvme0n1" +x="" + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Shortcut script for automated readonly test for vhost-block" + echo "For test details check test_plan.md" + echo + echo "Usage: $(basename $1) [OPTIONS]" + echo + echo "-h, --help Print help and exit" + echo " --vm_image= Path to VM image" + echo " --disk= Disk name." + echo " If disk=malloc, then creates malloc disk. For malloc disks, size is always 512M," + echo " e.g. --disk=malloc. (Default: Nvme0n1)" + echo "-x set -x for script debug" +} + +while getopts 'xh-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + help) usage $0 && exit 0;; + vm_image=*) vm_img="${OPTARG#*=}" ;; + disk=*) disk="${OPTARG#*=}" ;; + *) usage $0 "Invalid argument '$OPTARG'" && exit 1 + esac + ;; + h) usage $0 && exit 0 ;; + x) set -x + x="-x" ;; + *) usage $0 "Invalid argument '$OPTARG'" && exit 1 + esac +done + +trap 'error_exit "${FUNCNAME}" "${LINENO}"' ERR + +if [[ $EUID -ne 0 ]]; then + fail "Go away user come back as root" +fi + +function print_tc_name() +{ + notice "" + notice "===============================================================" + notice "Now running: $1" + notice "===============================================================" +} + +function blk_ro_tc1() +{ + print_tc_name ${FUNCNAME[0]} + local vm_no="0" + local disk_name=$disk + local vhost_blk_name="" + local vm_dir="$TEST_DIR/vms/$vm_no" + + if [[ $disk =~ .*malloc.* ]]; then + disk_name=$($rpc_py construct_malloc_bdev 512 4096) + if [ $? != 0 ]; then + fail "Failed to create malloc bdev" + fi + + disk=$disk_name + else + disk_name=${disk%%_*} + if ! $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi $disk_name$; then + fail "$disk_name bdev not found!" + fi + fi + +#Create controller and create file on disk for later test + notice "Creating vhost_blk controller" + vhost_blk_name="naa.$disk_name.$vm_no" + $rpc_py construct_vhost_blk_controller $vhost_blk_name $disk_name + vm_setup --disk-type=spdk_vhost_blk --force=$vm_no --os=$vm_img --disks=$disk --read-only=true + + vm_run $vm_no + vm_wait_for_boot 600 $vm_no + notice "Preparing partition and file on guest VM" + vm_ssh $vm_no "bash -s" < $READONLY_BASE_DIR/disabled_readonly_vm.sh + sleep 1 + + vm_shutdown_all +#Create readonly controller and test readonly feature + notice "Removing controller and creating new one with readonly flag" + $rpc_py remove_vhost_controller $vhost_blk_name + $rpc_py construct_vhost_blk_controller -r $vhost_blk_name $disk_name + + vm_run $vm_no + vm_wait_for_boot 600 $vm_no + notice "Testing readonly feature on guest VM" + vm_ssh $vm_no "bash -s" < $READONLY_BASE_DIR/enabled_readonly_vm.sh + sleep 3 + + vm_shutdown_all +#Delete file from disk and delete partition + echo "INFO: Removing controller and creating new one" + $rpc_py remove_vhost_controller $vhost_blk_name + $rpc_py construct_vhost_blk_controller $vhost_blk_name $disk_name + + vm_run $vm_no + vm_wait_for_boot 600 $vm_no + notice "Removing partition and file from test disk on guest VM" + vm_ssh $vm_no "bash -s" < $READONLY_BASE_DIR/delete_partition_vm.sh + sleep 1 + + vm_shutdown_all +} + +spdk_vhost_run +if [[ -z $x ]]; then + set +x +fi + +blk_ro_tc1 + +$rpc_py delete_nvme_controller Nvme0 + +spdk_vhost_kill diff --git a/src/spdk/test/vhost/readonly/test_plan.md b/src/spdk/test/vhost/readonly/test_plan.md new file mode 100644 index 00000000..957000e8 --- /dev/null +++ b/src/spdk/test/vhost/readonly/test_plan.md @@ -0,0 +1,30 @@ +# vhost-block readonly feature test plan + +## Objective +Vhost block controllers can be created with readonly feature which prevents any write operations on this device. +The purpose of this test is to verify proper operation of this feature. + +## Test cases description +To test readonly feature, this test will create normal vhost-blk controller with NVMe device and on a VM it will +create and mount a partition to which it will copy a file. Next it will poweroff a VM, remove vhost controller and +create new readonly vhost-blk controller with the same device. + +## Test cases + +### blk_ro_tc1 +1. Start vhost +2. Create vhost-blk controller with NVMe device and readonly feature disabled using RPC +3. Run VM with attached vhost-blk controller +4. Check visibility of readonly flag using lsblk, fdisk +5. Create new partition +6. Create new file on new partition +7. Shutdown VM, remove vhost controller +8. Create vhost-blk with previously used NVMe device and readonly feature now enabled using RPC +9. Run VM with attached vhost-blk controller +10. Check visibility of readonly flag using lsblk, fdisk +11. Try to delete previous file +12. Try to create new file +13. Try to remove partition +14. Repeat steps 2 to 4 +15. Remove file from disk, delete partition +16. Shutdown VM, exit vhost diff --git a/src/spdk/test/vhost/spdk_vhost.sh b/src/spdk/test/vhost/spdk_vhost.sh new file mode 100755 index 00000000..a7c0a6ba --- /dev/null +++ b/src/spdk/test/vhost/spdk_vhost.sh @@ -0,0 +1,210 @@ +#!/usr/bin/env bash +rootdir=$(readlink -f $(dirname $0))/../.. +source "$rootdir/test/common/autotest_common.sh" + +set -e + +DEFAULT_VM_IMAGE="/home/sys_sgsw/vhost_vm_image.qcow2" +CENTOS_VM_IMAGE="/home/sys_sgsw/spdk_vhost_CentOS_vm_image.qcow2" +DEFAULT_FIO_BIN="/home/sys_sgsw/fio_ubuntu" +CENTOS_FIO_BIN="/home/sys_sgsw/fio_ubuntu_bak" + +case $1 in + -h|--help) + echo "usage: $(basename $0) TEST_TYPE" + echo "Test type can be:" + echo " -i |--integrity for running an integrity test with vhost scsi" + echo " -fs|--fs-integrity-scsi for running an integrity test with filesystem" + echo " -fb|--fs-integrity-blk for running an integrity test with filesystem" + echo " -p |--performance for running a performance test with vhost scsi" + echo " -ib|--integrity-blk for running an integrity test with vhost blk" + echo " -pb|--performance-blk for running a performance test with vhost blk" + echo " -ils|--integrity-lvol-scsi for running an integrity test with vhost scsi and lvol backends" + echo " -ilb|--integrity-lvol-blk for running an integrity test with vhost blk and lvol backends" + echo " -ilsn|--integrity-lvol-scsi-nightly for running an nightly integrity test with vhost scsi and lvol backends" + echo " -ilbn|--integrity-lvol-blk-nightly for running an nightly integrity test with vhost blk and lvol backends" + echo " -hp|--hotplug for running hotplug tests" + echo " -shr|--scsi-hot-remove for running scsi hot remove tests" + echo " -bhr|--blk-hot-remove for running blk hot remove tests" + echo " -ro|--readonly for running readonly test for vhost blk" + echo " -b|--boot for booting vm from vhost controller" + echo " -h |--help prints this message" + echo "" + echo "Environment:" + echo " VM_IMAGE path to QCOW2 VM image used during test (default: $DEFAULT_VM_IMAGE)" + echo "" + echo "Tests are performed only on Linux machine. For other OS no action is performed." + echo "" + exit 0; + ;; +esac + +echo "Running SPDK vhost fio autotest..." +if [[ $(uname -s) != Linux ]]; then + echo "" + echo "INFO: Vhost tests are only for Linux machine." + echo "" + exit 0 +fi + +: ${VM_IMAGE="$DEFAULT_VM_IMAGE"} +: ${FIO_BIN="$DEFAULT_FIO_BIN"} + +if [[ ! -r "${VM_IMAGE}" ]]; then + echo "" + echo "ERROR: VM image '${VM_IMAGE}' does not exist." + echo "" + exit 1 +fi + +DISKS_NUMBER=`lspci -mm -n | grep 0108 | tr -d '"' | awk -F " " '{print "0000:"$1}'| wc -l` + +WORKDIR=$(readlink -f $(dirname $0)) + +case $1 in + -n|--negative) + echo 'Negative tests suite...' + run_test case $WORKDIR/other/negative.sh + report_test_completion "vhost_negative" + ;; + -p|--performance) + echo 'Running performance suite...' + run_test case $WORKDIR/fiotest/autotest.sh --fio-bin=$FIO_BIN \ + --vm=0,$VM_IMAGE,Nvme0n1p0 \ + --test-type=spdk_vhost_scsi \ + --fio-job=$WORKDIR/common/fio_jobs/default_performance.job + report_test_completion "vhost_perf" + ;; + -pb|--performance-blk) + echo 'Running blk performance suite...' + run_test case $WORKDIR/fiotest/autotest.sh --fio-bin=$FIO_BIN \ + --vm=0,$VM_IMAGE,Nvme0n1p0 \ + --test-type=spdk_vhost_blk \ + --fio-job=$WORKDIR/common/fio_jobs/default_performance.job + report_test_completion "vhost_perf_blk" + ;; + -m|--migration) + echo 'Running migration suite...' + run_test case $WORKDIR/migration/migration.sh -x \ + --fio-bin=$FIO_BIN --os=$VM_IMAGE --test-cases=1,2 + ;; + -i|--integrity) + echo 'Running SCSI integrity suite...' + run_test case $WORKDIR/fiotest/autotest.sh -x --fio-bin=$FIO_BIN \ + --vm=0,$VM_IMAGE,Nvme0n1p0:Nvme0n1p1:Nvme0n1p2:Nvme0n1p3 \ + --test-type=spdk_vhost_scsi \ + --fio-job=$WORKDIR/common/fio_jobs/default_integrity.job + report_test_completion "nightly_vhost_integrity" + ;; + -ib|--integrity-blk) + echo 'Running blk integrity suite...' + run_test case $WORKDIR/fiotest/autotest.sh -x --fio-bin=$FIO_BIN \ + --vm=0,$VM_IMAGE,Nvme0n1p0:Nvme0n1p1:Nvme0n1p2:Nvme0n1p3 \ + --test-type=spdk_vhost_blk \ + --fio-job=$WORKDIR/common/fio_jobs/default_integrity.job + report_test_completion "nightly_vhost_integrity_blk" + ;; + -fs|--fs-integrity-scsi) + echo 'Running filesystem integrity suite with SCSI...' + run_test case $WORKDIR/integrity/integrity_start.sh --ctrl-type=spdk_vhost_scsi --fs="xfs ntfs btrfs ext4" + report_test_completion "vhost_fs_integrity_scsi" + ;; + -fb|--fs-integrity-blk) + echo 'Running filesystem integrity suite with BLK...' + run_test case $WORKDIR/integrity/integrity_start.sh --ctrl-type=spdk_vhost_blk --fs="xfs ntfs btrfs ext4" + report_test_completion "vhost_fs_integrity_blk" + ;; + -ils|--integrity-lvol-scsi) + echo 'Running lvol integrity suite...' + run_test case $WORKDIR/lvol/lvol_test.sh -x --fio-bin=$FIO_BIN \ + --ctrl-type=spdk_vhost_scsi --thin-provisioning + report_test_completion "vhost_integrity_lvol_scsi" + ;; + -ilb|--integrity-lvol-blk) + echo 'Running lvol integrity suite...' + run_test case $WORKDIR/lvol/lvol_test.sh -x --fio-bin=$FIO_BIN \ + --ctrl-type=spdk_vhost_blk + report_test_completion "vhost_integrity_lvol_blk" + ;; + -ilsn|--integrity-lvol-scsi-nightly) + if [[ $DISKS_NUMBER -ge 2 ]]; then + echo 'Running lvol integrity nightly suite with two cores and two controllers' + run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \ + --ctrl-type=spdk_vhost_scsi --max-disks=2 --distribute-cores --vm-count=2 + + echo 'Running lvol integrity nightly suite with one core and two controllers' + run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \ + --ctrl-type=spdk_vhost_scsi --max-disks=2 --vm-count=2 + fi + if [[ -e $CENTOS_VM_IMAGE ]]; then + echo 'Running lvol integrity nightly suite with different os types' + run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$CENTOS_FIO_BIN \ + --ctrl-type=spdk_vhost_scsi --vm-count=2 --multi-os + fi + echo 'Running lvol integrity nightly suite with one core and one controller' + run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \ + --ctrl-type=spdk_vhost_scsi --max-disks=1 + ;; + -ilbn|--integrity-lvol-blk-nightly) + if [[ $DISKS_NUMBER -ge 2 ]]; then + echo 'Running lvol integrity nightly suite with two cores and two controllers' + run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \ + --ctrl-type=spdk_vhost_blk --max-disks=2 --distribute-cores --vm-count=2 + + echo 'Running lvol integrity nightly suite with one core and two controllers' + run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \ + --ctrl-type=spdk_vhost_blk --max-disks=2 --vm-count=2 + fi + if [[ -e $CENTOS_VM_IMAGE ]]; then + echo 'Running lvol integrity nightly suite with different os types' + run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$CENTOS_FIO_BIN \ + --ctrl-type=spdk_vhost_blk --vm-count=2 --multi-os + fi + echo 'Running lvol integrity nightly suite with one core and one controller' + run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \ + --ctrl-type=spdk_vhost_blk --max-disks=1 + ;; + -hp|--hotplug) + echo 'Running hotplug tests suite...' + run_test case $WORKDIR/hotplug/scsi_hotplug.sh --fio-bin=$FIO_BIN \ + --vm=0,$VM_IMAGE,Nvme0n1p0:Nvme0n1p1 \ + --vm=1,$VM_IMAGE,Nvme0n1p2:Nvme0n1p3 \ + --vm=2,$VM_IMAGE,Nvme0n1p4:Nvme0n1p5 \ + --vm=3,$VM_IMAGE,Nvme0n1p6:Nvme0n1p7 \ + --test-type=spdk_vhost_scsi \ + --fio-jobs=$WORKDIR/hotplug/fio_jobs/default_integrity.job -x + report_test_completion "vhost_hotplug" + ;; + -shr|--scsi-hot-remove) + echo 'Running scsi hotremove tests suite...' + run_test case $WORKDIR/hotplug/scsi_hotplug.sh --fio-bin=$FIO_BIN \ + --vm=0,$VM_IMAGE,Nvme0n1p0:Nvme0n1p1 \ + --vm=1,$VM_IMAGE,Nvme0n1p2:Nvme0n1p3 \ + --test-type=spdk_vhost_scsi \ + --scsi-hotremove-test \ + --fio-jobs=$WORKDIR/hotplug/fio_jobs/default_integrity.job + ;; + -bhr|--blk-hot-remove) + echo 'Running blk hotremove tests suite...' + run_test case $WORKDIR/hotplug/scsi_hotplug.sh --fio-bin=$FIO_BIN \ + --vm=0,$VM_IMAGE,Nvme0n1p0:Nvme0n1p1 \ + --vm=1,$VM_IMAGE,Nvme0n1p2:Nvme0n1p3 \ + --test-type=spdk_vhost_blk \ + --blk-hotremove-test \ + --fio-jobs=$WORKDIR/hotplug/fio_jobs/default_integrity.job + ;; + -ro|--readonly) + echo 'Running readonly tests suite...' + run_test case $WORKDIR/readonly/readonly.sh --vm_image=$VM_IMAGE --disk=Nvme0n1 -x + report_test_completion "vhost_readonly" + ;; + -b|--boot) + echo 'Running os boot from vhost controller...' + $WORKDIR/vhost_boot/vhost_boot.sh --vm_image=$VM_IMAGE + report_test_completion "vhost_boot" + ;; + *) + echo "unknown test type: $1" + exit 1 + ;; +esac diff --git a/src/spdk/test/vhost/test_plan.md b/src/spdk/test/vhost/test_plan.md new file mode 100644 index 00000000..b412436a --- /dev/null +++ b/src/spdk/test/vhost/test_plan.md @@ -0,0 +1,252 @@ +# SPDK vhost Test Plan + +## Current Tests + +### Integrity tests + +#### vhost self test +- compiles SPDK and Qemu +- launches SPDK Vhost +- starts VM with 1 NVMe device attached to it +- issues controller "reset" command using sg3_utils on guest system +- performs data integrity check using dd to write and read data from the device +- runs on 3 host systems (Ubuntu 16.04, Centos 7.3 and Fedora 25) + and 1 guest system (Ubuntu 16.04) +- runs against vhost scsi and vhost blk + +#### FIO Integrity tests +- NVMe device is split into 4 LUNs, each is attached to separate vhost controller +- FIO uses job configuration with randwrite mode to verify if random pattern was + written to and read from correctly on each LUN +- runs on Fedora 25 and Ubuntu 16.04 guest systems +- runs against vhost scsi and vhost blk + +#### Lvol tests +- starts vhost with at least 1 NVMe device +- starts 1 VM or multiple VMs +- lvol store is constructed on each NVMe device +- on each lvol store 1 lvol bdev will be constructed for each running VM +- Logical volume block device is used as backend instead of using + NVMe device backend directly +- after set up, data integrity check will be performed by FIO randwrite + operation with verify flag enabled +- optionally nested lvols can be tested with use of appropriate flag; + On each base lvol store additional lvol bdev will be created which will + serve as a base for nested lvol stores. + On each of the nested lvol stores there will be 1 lvol bdev created for each + VM running. Nested lvol bdevs will be used along with base lvol bdevs for + data integrity check. +- runs against vhost scsi and vhost blk + +#### Filesystem integrity +- runs SPDK with 1 VM with 1 NVMe device attached. +- creates a partition table and filesystem on passed device, and mounts it +- 1GB test file is created on mounted file system and FIO randrw traffic + (with enabled verification) is run +- Tested file systems: ext4, brtfs, ntfs, xfs +- runs against vhost scsi and vhost blk + +#### Windows HCK SCSI Compliance Test 2.0. +- Runs SPDK with 1 VM with Windows Server 2012 R2 operating system +- 4 devices are passed into the VM: NVMe, Split NVMe, Malloc and Split Malloc +- On each device Windows HCK SCSI Compliance Test 2.0 is run + +#### MultiOS test +- start 3 VMs with guest systems: Ubuntu 16.04, Fedora 25 and Windows Server 2012 R2 +- 3 physical NVMe devices are split into 9 LUNs +- each guest uses 3 LUNs from 3 different physical NVMe devices +- Linux guests run FIO integrity jobs to verify read/write operations, + while Windows HCK SCSI Compliance Test 2.0 is running on Windows guest + +#### vhost hot-remove tests +- removing NVMe device (unbind from driver) which is already claimed + by controller in vhost +- hotremove tests performed with and without I/O traffic to device +- I/O traffic, if present in test, has verification enabled +- checks that vhost and/or VMs do not crash +- checks that other devices are unaffected by hot-remove of a NVMe device +- performed against vhost blk and vhost scsi + +#### vhost scsi hot-attach and hot-detach tests +- adding and removing devices via RPC to a controller which is already in use by a VM +- I/O traffic generated with FIO read/write operations, verification enabled +- checks that vhost and/or VMs do not crash +- checks that other devices in the same controller are unaffected by hot-attach + and hot-detach operations + +#### virtio initiator tests +- virtio user mode: connect to vhost-scsi controller sockets directly on host +- virtio pci mode: connect to virtual pci devices on guest virtual machine +- 6 concurrent jobs are run simultaneously on 7 devices, each with 8 virtqueues + +##### kernel virtio-scsi-pci device +- test support for kernel vhost-scsi device +- create 1GB ramdisk using targetcli +- create target and add ramdisk to it using targetcli +- add created device to virtio pci tests + +##### emulated virtio-scsi-pci device +- test support for QEMU emulated virtio-scsi-pci device +- add emulated virtio device "Virtio0" to virtio pci tests + +##### Test configuration +- SPDK vhost application is used for testing +- FIO using spdk fio_plugin: rw, randrw, randwrite, write with verification enabled. +- trim sequential and trim random then write on trimmed areas with verification enabled + only on unmap supporting devices +- FIO job configuration: iodepth=128, block size=4k, runtime=10s +- all test cases run jobs in parallel on multiple bdevs +- 8 queues per device + +##### vhost configuration +- scsi controller with 4 NVMe splits +- 2 block controllers, each with 1 NVMe split +- scsi controller with malloc with 512 block size +- scsi controller with malloc with 4096 block size + +##### Test case 1 +- virtio user on host +- perform FIO rw, randwrite, randrw, write, parallel jobs on all devices + +##### Test case 2 +- virtio user on host +- perform FIO trim, randtrim, rw, randwrite, randrw, write, - parallel jobs + then write on trimmed areas on unmap supporting devices + +##### Test case 3 +- virtio pci on vm +- same config as in TC#1 + +##### Test case 4 +- virtio pci on vm +- same config as in TC#2 + +### Live migration +Live migration feature allows to move running virtual machines between SPDK vhost +instances. +Following tests include scenarios with SPDK vhost instances running on both the same +physical server and between remote servers. +Additional configuration of utilities like SSHFS share, NIC IP address adjustment, +etc., might be necessary. + +#### Test case 1 - single vhost migration +- Start SPDK Vhost application. + - Construct a single Malloc bdev. + - Construct two SCSI controllers and add previously created Malloc bdev to it. +- Start first VM (VM_1) and connect to Vhost_1 controller. + Verify if attached disk is visible in the system. +- Start second VM (VM_2) but with "-incoming" option enabled, connect to. + Connect to Vhost_2 controller. Use the same VM image as VM_1. +- On VM_1 start FIO write job with verification enabled to connected Malloc bdev. +- Start VM migration from VM_1 to VM_2 while FIO is still running on VM_1. +- Once migration is complete check the result using Qemu monitor. Migration info + on VM_1 should return "Migration status: completed". +- VM_2 should be up and running after migration. Via SSH log in and check FIO + job result - exit code should be 0 and there should be no data verification errors. +- Cleanup: + - Shutdown both VMs. + - Gracefully shutdown Vhost instance. + +#### Test case 2 - single server migration +- Detect RDMA NICs; At least 1 RDMA NIC is needed to run the test. + If there is no physical NIC available then emulated Soft Roce NIC will + be used instead. +- Create /tmp/share directory and put a test VM image in there. +- Start SPDK NVMeOF Target application. + - Construct a single NVMe bdev from available bound NVMe drives. + - Create NVMeoF subsystem with NVMe bdev as single namespace. +- Start first SDPK Vhost application instance (later referred to as "Vhost_1"). + - Use different shared memory ID and CPU mask than NVMeOF Target. + - Construct a NVMe bdev by connecting to NVMeOF Target + (using trtype: rdma). + - Construct a single SCSI controller and add NVMe bdev to it. +- Start first VM (VM_1) and connect to Vhost_1 controller. Verify if attached disk + is visible in the system. +- Start second SDPK Vhost application instance (later referred to as "Vhost_2"). + - Use different shared memory ID and CPU mask than previous SPDK instances. + - Construct a NVMe bdev by connecting to NVMeOF Target. Connect to the same + subsystem as Vhost_1, multiconnection is allowed. + - Construct a single SCSI controller and add NVMe bdev to it. +- Start second VM (VM_2) but with "-incoming" option enabled. +- Check states of both VMs using Qemu monitor utility. + VM_1 should be in running state. + VM_2 should be in paused (inmigrate) state. +- Run FIO I/O traffic with verification enabled on to attached NVME on VM_1. +- While FIO is running issue a command for VM_1 to migrate. +- When the migrate call returns check the states of VMs again. + VM_1 should be in paused (postmigrate) state. "info migrate" should report + "Migration status: completed". + VM_2 should be in running state. +- Verify that FIO task completed successfully on VM_2 after migrating. + There should be no I/O failures, no verification failures, etc. +- Cleanup: + - Shutdown both VMs. + - Gracefully shutdown Vhost instances and NVMEoF Target instance. + - Remove /tmp/share directory and it's contents. + - Clean RDMA NIC / Soft RoCE configuration. + +#### Test case 3 - remote server migration +- Detect RDMA NICs on physical hosts. At least 1 RDMA NIC per host is needed + to run the test. +- On Host 1 create /tmp/share directory and put a test VM image in there. +- On Host 2 create /tmp/share directory. Using SSHFS mount /tmp/share from Host 1 + so that the same VM image can be used on both hosts. +- Start SPDK NVMeOF Target application on Host 1. + - Construct a single NVMe bdev from available bound NVMe drives. + - Create NVMeoF subsystem with NVMe bdev as single namespace. +- Start first SDPK Vhost application instance on Host 1(later referred to as "Vhost_1"). + - Use different shared memory ID and CPU mask than NVMeOF Target. + - Construct a NVMe bdev by connecting to NVMeOF Target + (using trtype: rdma). + - Construct a single SCSI controller and add NVMe bdev to it. +- Start first VM (VM_1) and connect to Vhost_1 controller. Verify if attached disk + is visible in the system. +- Start second SDPK Vhost application instance on Host 2(later referred to as "Vhost_2"). + - Construct a NVMe bdev by connecting to NVMeOF Target. Connect to the same + subsystem as Vhost_1, multiconnection is allowed. + - Construct a single SCSI controller and add NVMe bdev to it. +- Start second VM (VM_2) but with "-incoming" option enabled. +- Check states of both VMs using Qemu monitor utility. + VM_1 should be in running state. + VM_2 should be in paused (inmigrate) state. +- Run FIO I/O traffic with verification enabled on to attached NVME on VM_1. +- While FIO is running issue a command for VM_1 to migrate. +- When the migrate call returns check the states of VMs again. + VM_1 should be in paused (postmigrate) state. "info migrate" should report + "Migration status: completed". + VM_2 should be in running state. +- Verify that FIO task completed successfully on VM_2 after migrating. + There should be no I/O failures, no verification failures, etc. +- Cleanup: + - Shutdown both VMs. + - Gracefully shutdown Vhost instances and NVMEoF Target instance. + - Remove /tmp/share directory and it's contents. + - Clean RDMA NIC configuration. + +### Performance tests +Tests verifying the performance and efficiency of the module. + +#### FIO Performance 6 NVMes +- SPDK and created controllers run on 2 CPU cores. +- Each NVMe drive is split into 2 Split NVMe bdevs, which gives a total of 12 + in test setup. +- 12 vhost controllers are created, one for each Split NVMe bdev. All controllers + use the same CPU mask as used for running Vhost instance. +- 12 virtual machines are run as guest systems (with Ubuntu 16.04.2); Each VM + connects to a single corresponding vhost controller. + Per VM configuration is: 2 pass-through host CPU's, 1 GB RAM, 2 IO controller queues. +- NVMe drives are pre-conditioned before the test starts. Pre-conditioning is done by + writing over whole disk sequentially at least 2 times. +- FIO configurations used for tests: + - IO depths: 1, 8, 128 + - Blocksize: 4k + - RW modes: read, randread, write, randwrite, rw, randrw + - Write modes are additionally run with 15 minute ramp-up time to allow better + measurements. Randwrite mode uses longer ramp-up preconditioning of 90 minutes per run. +- Each FIO job result is compared with baseline results to allow detecting performance drops. + +## Future tests and improvements + +### Stress tests +- Add stability and stress tests (long duration tests, long looped start/stop tests, etc.) +to test pool diff --git a/src/spdk/test/vhost/vhost_boot/vhost_boot.sh b/src/spdk/test/vhost/vhost_boot/vhost_boot.sh new file mode 100755 index 00000000..42bd7f22 --- /dev/null +++ b/src/spdk/test/vhost/vhost_boot/vhost_boot.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +set -xe + +basedir=$(readlink -f $(dirname $0)) +. $basedir/../common/common.sh +rpc_py="python $SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock" +vm_no="0" + +function err_clean +{ + trap - ERR + print_backtrace + set +e + error "Error on $1 $2" + vm_kill_all + $rpc_py remove_vhost_scsi_target naa.vhost_vm.$vm_no 0 + $rpc_py remove_vhost_controller naa.vhost_vm.$vm_no + $rpc_py destroy_lvol_bdev $lvb_u + $rpc_py destroy_lvol_store -u $lvs_u + spdk_vhost_kill + exit 1 +} + +function usage() +{ + [[ ! -z $2 ]] && ( echo "$2"; echo ""; ) + echo "Usage: $(basename $1) vm_image=PATH [-h|--help]" + echo "-h, --help Print help and exit" + echo " --vm_image=PATH Path to VM image used in these tests" +} + +while getopts 'h-:' optchar; do + case "$optchar" in + -) + case "$OPTARG" in + vm_image=*) os_image="${OPTARG#*=}" ;; + *) usage $0 echo "Invalid argument '$OPTARG'" && exit 1 ;; + esac + ;; + h) usage $0 && exit 0 ;; + *) usage $0 "Invalid argument '$optchar'" && exit 1 ;; + esac +done + +if [[ $EUID -ne 0 ]]; then + echo "INFO: Go away user come back as root" + exit 1 +fi + +if [[ -z $os_image ]]; then + echo "No path to os image is given" + exit 1 +fi + +timing_enter vhost_boot +trap 'err_clean "${FUNCNAME}" "${LINENO}"' ERR +timing_enter start_vhost +spdk_vhost_run +timing_exit start_vhost + +timing_enter create_lvol +lvs_u=$($rpc_py construct_lvol_store Nvme0n1 lvs0) +lvb_u=$($rpc_py construct_lvol_bdev -u $lvs_u lvb0 20000) +timing_exit create_lvol + +timing_enter convert_vm_image +modprobe nbd +trap '$rpc_py stop_nbd_disk /dev/nbd0; rmmod nbd; err_clean "${FUNCNAME}" "${LINENO}"' ERR +$rpc_py start_nbd_disk $lvb_u /dev/nbd0 +$QEMU_PREFIX/bin/qemu-img convert $os_image -O raw /dev/nbd0 +sync +$rpc_py stop_nbd_disk /dev/nbd0 +sleep 1 +rmmod nbd +timing_exit convert_vm_image + +trap 'err_clean "${FUNCNAME}" "${LINENO}"' ERR +timing_enter create_vhost_controller +$rpc_py construct_vhost_scsi_controller naa.vhost_vm.$vm_no +$rpc_py add_vhost_scsi_lun naa.vhost_vm.$vm_no 0 $lvb_u +timing_exit create_vhost_controller + +timing_enter setup_vm +vm_setup --disk-type=spdk_vhost_scsi --force=$vm_no --disks="vhost_vm" --spdk-boot="vhost_vm" +vm_run $vm_no +vm_wait_for_boot 600 $vm_no +timing_exit setup_vm + +timing_enter run_vm_cmd +vm_ssh $vm_no "parted -s /dev/sda mkpart primary 10GB 100%; partprobe; sleep 0.1;" +vm_ssh $vm_no "mkfs.ext4 -F /dev/sda2; mkdir -p /mnt/sda2test; mount /dev/sda2 /mnt/sda2test;" +vm_ssh $vm_no "fio --name=integrity --bsrange=4k-512k --iodepth=128 --numjobs=1 --direct=1 \ + --thread=1 --group_reporting=1 --rw=randrw --rwmixread=70 --filename=/mnt/sda2test/test_file \ + --verify=md5 --do_verify=1 --verify_backlog=1024 --fsync_on_close=1 --runtime=20 \ + --time_based=1 --size=1024m" +vm_ssh $vm_no "umount /mnt/sda2test; rm -rf /mnt/sda2test" +alignment_offset=$(vm_ssh $vm_no "cat /sys/block/sda/sda1/alignment_offset") +echo "alignment_offset: $alignment_offset" +timing_exit run_vm_cmd + +vm_shutdown_all + +timing_enter clean_vhost +$rpc_py remove_vhost_scsi_target naa.vhost_vm.$vm_no 0 +$rpc_py remove_vhost_controller naa.vhost_vm.$vm_no +$rpc_py destroy_lvol_bdev $lvb_u +$rpc_py destroy_lvol_store -u $lvs_u +spdk_vhost_kill +timing_exit clean_vhost + +timing_exit vhost_boot |