From 293cdc484bb01ca3a97c8e7858ae6e8332f5f6dd Mon Sep 17 00:00:00 2001 From: Artur Paszkiewicz Date: Wed, 22 Jun 2022 12:11:47 +0200 Subject: [PATCH] ftl: management framework Signed-off-by: Artur Paszkiewicz Signed-off-by: Kozlowski Mateusz Change-Id: I8261863e80a53a37183b0148d4a08fa97e208dda Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/13289 Tested-by: SPDK CI Jenkins Reviewed-by: Jim Harris Reviewed-by: Ben Walker Community-CI: Mellanox Build Bot --- lib/ftl/Makefile | 3 +- lib/ftl/ftl_core.c | 1 + lib/ftl/mngt/ftl_mngt.c | 570 +++++++++++ lib/ftl/mngt/ftl_mngt.h | 294 ++++++ mk/spdk.lib_deps.mk | 2 +- test/unit/lib/Makefile | 1 + test/unit/lib/ftl/Makefile | 16 + test/unit/lib/ftl/ftl_mngt/.gitignore | 1 + test/unit/lib/ftl/ftl_mngt/Makefile | 12 + test/unit/lib/ftl/ftl_mngt/ftl_mngt_ut.c | 1110 ++++++++++++++++++++++ test/unit/unittest.sh | 7 + 11 files changed, 2015 insertions(+), 2 deletions(-) create mode 100644 lib/ftl/mngt/ftl_mngt.c create mode 100644 lib/ftl/mngt/ftl_mngt.h create mode 100644 test/unit/lib/ftl/Makefile create mode 100644 test/unit/lib/ftl/ftl_mngt/.gitignore create mode 100644 test/unit/lib/ftl/ftl_mngt/Makefile create mode 100644 test/unit/lib/ftl/ftl_mngt/ftl_mngt_ut.c diff --git a/lib/ftl/Makefile b/lib/ftl/Makefile index 3eb7d81cf..d565abf94 100644 --- a/lib/ftl/Makefile +++ b/lib/ftl/Makefile @@ -11,9 +11,10 @@ SO_MINOR := 0 CFLAGS += -I. -FTL_SUBDIRS := utils +FTL_SUBDIRS := mngt utils C_SRCS = ftl_core.c +C_SRCS += mngt/ftl_mngt.c SPDK_MAP_FILE = $(abspath $(CURDIR)/spdk_ftl.map) diff --git a/lib/ftl/ftl_core.c b/lib/ftl/ftl_core.c index 53d20abfc..a3a997019 100644 --- a/lib/ftl/ftl_core.c +++ b/lib/ftl/ftl_core.c @@ -14,5 +14,6 @@ #include "ftl_core.h" #include "ftl_internal.h" +#include "mngt/ftl_mngt.h" SPDK_LOG_REGISTER_COMPONENT(ftl_core) diff --git a/lib/ftl/mngt/ftl_mngt.c b/lib/ftl/mngt/ftl_mngt.c new file mode 100644 index 000000000..5faadd3e7 --- /dev/null +++ b/lib/ftl/mngt/ftl_mngt.c @@ -0,0 +1,570 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (c) Intel Corporation. + * All rights reserved. + */ + +#include "spdk/queue.h" +#include "spdk/assert.h" +#include "spdk/env.h" + +#include "ftl_mngt.h" +#include "ftl_core.h" + +struct ftl_mngt_step_status { + uint64_t start; + uint64_t stop; + int status; + int silent; + TAILQ_ENTRY(ftl_mngt_step) entry; +}; + +struct ftl_mngt_step { + void *ctx; + const struct ftl_mngt_step_desc *desc; + struct ftl_mngt_step_status action; + struct ftl_mngt_step_status rollback; +}; + +struct ftl_mngt_process { + struct spdk_ftl_dev *dev; + int status; + int silent; + bool rollback; + bool continuing; + struct { + ftl_mngt_fn cb; + void *cb_ctx; + struct spdk_thread *thread; + } caller; + void *ctx; + uint64_t tsc_start; + uint64_t tsc_stop; + const struct ftl_mngt_process_desc *desc; + TAILQ_HEAD(, ftl_mngt_step) action_queue_todo; + TAILQ_HEAD(, ftl_mngt_step) action_queue_done; + TAILQ_HEAD(, ftl_mngt_step) rollback_queue_todo; + TAILQ_HEAD(, ftl_mngt_step) rollback_queue_done; + struct { + struct ftl_mngt_step step; + struct ftl_mngt_step_desc desc; + } cleanup; + struct ftl_mng_tracer *tracer; +}; + +static void action_next(struct ftl_mngt_process *mngt); +static void action_msg(void *ctx); +static void action_execute(struct ftl_mngt_process *mngt); +static void action_done(struct ftl_mngt_process *mngt, int status); +static void rollback_next(struct ftl_mngt_process *mngt); +static void rollback_msg(void *ctx); +static void rollback_execute(struct ftl_mngt_process *mngt); +static void rollback_done(struct ftl_mngt_process *mngt, int status); + +static inline struct ftl_mngt_step * +get_current_step(struct ftl_mngt_process *mngt) +{ + if (!mngt->rollback) { + return TAILQ_FIRST(&mngt->action_queue_todo); + } else { + return TAILQ_FIRST(&mngt->rollback_queue_todo); + } +} + +static int +init_step(struct ftl_mngt_process *mngt, + const struct ftl_mngt_step_desc *desc) +{ + struct ftl_mngt_step *step; + + step = calloc(1, sizeof(*step)); + if (!step) { + return -ENOMEM; + } + + /* Initialize the step's argument */ + if (desc->ctx_size) { + step->ctx = calloc(1, desc->ctx_size); + if (!step->ctx) { + free(step); + return -ENOMEM; + } + } + step->desc = desc; + TAILQ_INSERT_TAIL(&mngt->action_queue_todo, step, action.entry); + + return 0; +} + +static void +free_mngt(struct ftl_mngt_process *mngt) +{ + TAILQ_HEAD(, ftl_mngt_step) steps; + + if (!mngt) { + return; + } + + TAILQ_INIT(&steps); + TAILQ_CONCAT(&steps, &mngt->action_queue_todo, action.entry); + TAILQ_CONCAT(&steps, &mngt->action_queue_done, action.entry); + + while (!TAILQ_EMPTY(&steps)) { + struct ftl_mngt_step *step = TAILQ_FIRST(&steps); + TAILQ_REMOVE(&steps, step, action.entry); + + free(step->ctx); + free(step); + } + + free(mngt->ctx); + free(mngt); +} + +static struct ftl_mngt_process * +allocate_mngt(struct spdk_ftl_dev *dev, + const struct ftl_mngt_process_desc *pdesc, + ftl_mngt_fn cb, void *cb_ctx) +{ + struct ftl_mngt_process *mngt; + + /* Initialize management process */ + mngt = calloc(1, sizeof(*mngt)); + if (!mngt) { + goto error; + } + mngt->dev = dev; + mngt->caller.cb = cb; + mngt->caller.cb_ctx = cb_ctx; + mngt->caller.thread = spdk_get_thread(); + + /* Initialize process context */ + if (pdesc->ctx_size) { + mngt->ctx = calloc(1, pdesc->ctx_size); + if (!mngt->ctx) { + goto error; + } + } + mngt->tsc_start = spdk_get_ticks(); + mngt->desc = pdesc; + TAILQ_INIT(&mngt->action_queue_todo); + TAILQ_INIT(&mngt->action_queue_done); + TAILQ_INIT(&mngt->rollback_queue_todo); + TAILQ_INIT(&mngt->rollback_queue_done); + + return mngt; +error: + free_mngt(mngt); + return NULL; +} + +int +ftl_mngt_process_execute(struct spdk_ftl_dev *dev, + const struct ftl_mngt_process_desc *pdesc, + ftl_mngt_fn cb, void *cb_ctx) +{ + const struct ftl_mngt_step_desc *sdesc; + struct ftl_mngt_process *mngt; + int rc = 0; + + mngt = allocate_mngt(dev, pdesc, cb, cb_ctx); + if (!mngt) { + rc = -ENOMEM; + goto error; + } + + if (pdesc->error_handler) { + /* Initialize a step for error handler */ + mngt->cleanup.step.desc = &mngt->cleanup.desc; + mngt->cleanup.desc.name = "Handle ERROR"; + mngt->cleanup.desc.cleanup = pdesc->error_handler; + + /* Queue error handler to the rollback queue, it will be executed at the end */ + TAILQ_INSERT_HEAD(&mngt->rollback_queue_todo, &mngt->cleanup.step, + rollback.entry); + } + + /* Initialize steps */ + sdesc = mngt->desc->steps; + while (sdesc->action) { + rc = init_step(mngt, sdesc); + if (rc) { + goto error; + } + sdesc++; + } + + action_execute(mngt); + return 0; +error: + free_mngt(mngt); + return rc; +} + +int +ftl_mngt_process_rollback(struct spdk_ftl_dev *dev, + const struct ftl_mngt_process_desc *pdesc, + ftl_mngt_fn cb, void *cb_ctx) +{ + const struct ftl_mngt_step_desc *sdesc; + struct ftl_mngt_process *mngt; + int rc = 0; + + mngt = allocate_mngt(dev, pdesc, cb, cb_ctx); + if (!mngt) { + rc = -ENOMEM; + goto error; + } + + /* Initialize steps for rollback */ + sdesc = mngt->desc->steps; + while (sdesc->action) { + if (!sdesc->cleanup) { + sdesc++; + continue; + } + rc = init_step(mngt, sdesc); + if (rc) { + goto error; + } + sdesc++; + } + + /* Build rollback list */ + struct ftl_mngt_step *step; + TAILQ_FOREACH(step, &mngt->action_queue_todo, action.entry) { + step->action.silent = true; + TAILQ_INSERT_HEAD(&mngt->rollback_queue_todo, step, + rollback.entry); + } + + mngt->rollback = true; + rollback_execute(mngt); + return 0; +error: + free_mngt(mngt); + return rc; +} + +struct spdk_ftl_dev * +ftl_mngt_get_dev(struct ftl_mngt_process *mngt) +{ + return mngt->dev; +} + +int +ftl_mngt_alloc_step_ctx(struct ftl_mngt_process *mngt, size_t size) +{ + struct ftl_mngt_step *step = get_current_step(mngt); + void *arg = calloc(1, size); + + if (!arg) { + return -ENOMEM; + } + + free(step->ctx); + step->ctx = arg; + + return 0; +} + +void * +ftl_mngt_get_step_ctx(struct ftl_mngt_process *mngt) +{ + return get_current_step(mngt)->ctx; +} + +void * +ftl_mngt_get_process_ctx(struct ftl_mngt_process *mngt) +{ + return mngt->ctx; +} + +void * +ftl_mngt_get_caller_ctx(struct ftl_mngt_process *mngt) +{ + return mngt->caller.cb_ctx; +} + +int +ftl_mngt_get_status(struct ftl_mngt_process *mngt) +{ + return mngt->status; +} + +void +ftl_mngt_next_step(struct ftl_mngt_process *mngt) +{ + if (false == mngt->rollback) { + action_next(mngt); + } else { + rollback_next(mngt); + } +} + +void +ftl_mngt_skip_step(struct ftl_mngt_process *mngt) +{ + if (mngt->rollback) { + get_current_step(mngt)->rollback.silent = true; + } else { + get_current_step(mngt)->action.silent = true; + } + ftl_mngt_next_step(mngt); +} + +void +ftl_mngt_continue_step(struct ftl_mngt_process *mngt) +{ + + if (!mngt->continuing) { + if (false == mngt->rollback) { + action_execute(mngt); + } else { + rollback_execute(mngt); + } + } + + mngt->continuing = true; +} + +static void +child_cb(struct spdk_ftl_dev *dev, struct ftl_mngt_process *child) +{ + int status = ftl_mngt_get_status(child); + struct ftl_mngt_process *parent = ftl_mngt_get_caller_ctx(child); + + child->silent = true; + + if (status) { + ftl_mngt_fail_step(parent); + } else { + ftl_mngt_next_step(parent); + } +} + +void +ftl_mngt_call_process(struct ftl_mngt_process *mngt, + const struct ftl_mngt_process_desc *pdesc) +{ + if (ftl_mngt_process_execute(mngt->dev, pdesc, child_cb, mngt)) { + ftl_mngt_fail_step(mngt); + } else { + if (mngt->rollback) { + get_current_step(mngt)->rollback.silent = true; + } else { + get_current_step(mngt)->action.silent = true; + } + } +} + +void +ftl_mngt_call_process_rollback(struct ftl_mngt_process *mngt, + const struct ftl_mngt_process_desc *pdesc) +{ + if (ftl_mngt_process_rollback(mngt->dev, pdesc, child_cb, mngt)) { + ftl_mngt_fail_step(mngt); + } else { + if (mngt->rollback) { + get_current_step(mngt)->rollback.silent = true; + } else { + get_current_step(mngt)->action.silent = true; + } + } +} + +void +ftl_mngt_fail_step(struct ftl_mngt_process *mngt) +{ + mngt->status = -1; + + if (false == mngt->rollback) { + action_done(mngt, -1); + } else { + rollback_done(mngt, -1); + } + + mngt->rollback = true; + rollback_execute(mngt); +} + +static inline float +tsc_to_ms(uint64_t tsc) +{ + float ms = tsc; + ms /= (float)spdk_get_ticks_hz(); + ms *= 1000.0; + return ms; +} + +static void +trace_step(struct spdk_ftl_dev *dev, struct ftl_mngt_step *step, bool rollback) +{ + uint64_t duration; + const char *what = rollback ? "Rollback" : "Action"; + int silent = rollback ? step->rollback.silent : step->action.silent; + + if (silent) { + return; + } + + FTL_NOTICELOG(dev, "%s\n", what); + FTL_NOTICELOG(dev, "\t name: %s\n", step->desc->name); + duration = step->action.stop - step->action.start; + FTL_NOTICELOG(dev, "\t duration: %.3f ms\n", tsc_to_ms(duration)); + FTL_NOTICELOG(dev, "\t status: %d\n", step->action.status); +} + +static void +process_summary(struct ftl_mngt_process *mngt) +{ + uint64_t duration; + + if (mngt->silent) { + return; + } + + duration = mngt->tsc_stop - mngt->tsc_start; + FTL_NOTICELOG(mngt->dev, "Management process finished, " + "name '%s', duration = %.3f ms, result %d\n", + mngt->desc->name, + tsc_to_ms(duration), + mngt->status); +} + +static void +finish_msg(void *ctx) +{ + struct ftl_mngt_process *mngt = ctx; + + mngt->caller.cb(mngt->dev, mngt); + process_summary(mngt); + free_mngt(mngt); +} + +void +ftl_mngt_finish(struct ftl_mngt_process *mngt) +{ + mngt->tsc_stop = spdk_get_ticks(); + spdk_thread_send_msg(mngt->caller.thread, finish_msg, mngt); +} + +/* + * Actions + */ +static void +action_next(struct ftl_mngt_process *mngt) +{ + if (TAILQ_EMPTY(&mngt->action_queue_todo)) { + /* Nothing to do, finish the management process */ + ftl_mngt_finish(mngt); + return; + } else { + action_done(mngt, 0); + action_execute(mngt); + } +} + +static void +action_msg(void *ctx) +{ + struct ftl_mngt_process *mngt = ctx; + struct ftl_mngt_step *step; + + mngt->continuing = false; + + if (TAILQ_EMPTY(&mngt->action_queue_todo)) { + ftl_mngt_finish(mngt); + return; + } + + step = TAILQ_FIRST(&mngt->action_queue_todo); + if (!step->action.start) { + step->action.start = spdk_get_ticks(); + } + step->desc->action(mngt->dev, mngt); +} + +static void +action_execute(struct ftl_mngt_process *mngt) +{ + spdk_thread_send_msg(mngt->dev->core_thread, action_msg, mngt); +} + +static void +action_done(struct ftl_mngt_process *mngt, int status) +{ + struct ftl_mngt_step *step; + + assert(!TAILQ_EMPTY(&mngt->action_queue_todo)); + step = TAILQ_FIRST(&mngt->action_queue_todo); + TAILQ_REMOVE(&mngt->action_queue_todo, step, action.entry); + + TAILQ_INSERT_TAIL(&mngt->action_queue_done, step, action.entry); + if (step->desc->cleanup) { + TAILQ_INSERT_HEAD(&mngt->rollback_queue_todo, step, + rollback.entry); + } + + step->action.stop = spdk_get_ticks(); + step->action.status = status; + + trace_step(mngt->dev, step, false); +} + +/* + * Rollback + */ +static void +rollback_next(struct ftl_mngt_process *mngt) +{ + if (TAILQ_EMPTY(&mngt->rollback_queue_todo)) { + /* Nothing to do, finish the management process */ + ftl_mngt_finish(mngt); + return; + } else { + rollback_done(mngt, 0); + rollback_execute(mngt); + } +} + +static void +rollback_msg(void *ctx) +{ + struct ftl_mngt_process *mngt = ctx; + struct ftl_mngt_step *step; + + mngt->continuing = false; + + if (TAILQ_EMPTY(&mngt->rollback_queue_todo)) { + ftl_mngt_finish(mngt); + return; + } + + step = TAILQ_FIRST(&mngt->rollback_queue_todo); + if (!step->rollback.start) { + step->rollback.start = spdk_get_ticks(); + } + step->desc->cleanup(mngt->dev, mngt); +} + +static void +rollback_execute(struct ftl_mngt_process *mngt) +{ + spdk_thread_send_msg(mngt->dev->core_thread, rollback_msg, mngt); +} + +void +rollback_done(struct ftl_mngt_process *mngt, int status) +{ + struct ftl_mngt_step *step; + + assert(!TAILQ_EMPTY(&mngt->rollback_queue_todo)); + step = TAILQ_FIRST(&mngt->rollback_queue_todo); + TAILQ_REMOVE(&mngt->rollback_queue_todo, step, rollback.entry); + TAILQ_INSERT_TAIL(&mngt->rollback_queue_done, step, rollback.entry); + + step->rollback.stop = spdk_get_ticks(); + step->rollback.status = status; + + trace_step(mngt->dev, step, true); +} diff --git a/lib/ftl/mngt/ftl_mngt.h b/lib/ftl/mngt/ftl_mngt.h new file mode 100644 index 000000000..fe4100287 --- /dev/null +++ b/lib/ftl/mngt/ftl_mngt.h @@ -0,0 +1,294 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (c) Intel Corporation. + * All rights reserved. + */ + +#ifndef FTL_MNGT_H +#define FTL_MNGT_H + +#include "spdk/stdinc.h" +#include "spdk/ftl.h" + +struct spdk_ftl_dev; +struct ftl_mngt_process; + +/** + * The FTL management callback function + * + * @param dev FTL device + * @param mngt FTL management handle + */ +typedef void (*ftl_mngt_fn)(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt); + +/** + * The FTL management step descriptior + */ +struct ftl_mngt_step_desc { + /** + * Name of the step + */ + const char *name; + + /** + * Size of the step argument (context) + * + * The step context will be allocated before execution of step's + * callback. + * + * @note The context can be reallocated (freed and newly allocated + * when calling ftl_mngt_alloc_step_ctx). The main usage is the ability + * to set this value to 0 and only allocate as needed if the step is + * going to be extremely similar - eg. recovery from shared memory and + * disk - in case of shm all the data is already available in memory, while + * recovery from disk needs extra context to be able to synchronize IO. This + * allows for saving a little bit of time on alloc/dealloc in the cases where + * execution time may be critical. + * @note It doesn't work like realloc + * @note The context can be retrieved within callback when calling + * ftl_mngt_get_step_ctx + */ + size_t ctx_size; + + /** + * Step callback function + */ + ftl_mngt_fn action; + + /** + * It the step requires cleanup this is right place to put your handler. + * When a FTL management process fails cleanup callbacks are executed + * in rollback procedure. Cleanup functions are executed in reverse + * order to actions already called. + */ + ftl_mngt_fn cleanup; +}; + +/** + * The FTL management process descriptor + */ +struct ftl_mngt_process_desc { + /** + * The name of the process + */ + const char *name; + + /** + * Size of the process argument (context) + * + * The process context will be allocated before execution of the first + * step + * + * @note To get context of the process within FTL management callback, + * execute ftl_mngt_get_process_ctx + */ + size_t ctx_size; + + /** + * Pointer to the additional error handler when the process fails + */ + ftl_mngt_fn error_handler; + + /** + * The FTL process steps + * + * The process context will be allocated before execution of the first + * step + * + * @note The step array terminator shall end with action equals NULL + */ + struct ftl_mngt_step_desc steps[]; +}; + +/** + * @brief Executes the FTL management process defined by the process descriptor + * + * In case of an error all already executed steps will have their rollback functions + * called in reverse order. + * + * @param dev FTL device + * @param process The descriptor of process to be executed + * @param cb Caller callback + * @param cb_ctx Caller context + * + * @return Result of invoking the operation + * @retval 0 - The FTL management process has been started + * @retval Non-zero An error occurred when starting The FTL management process + */ +int ftl_mngt_process_execute(struct spdk_ftl_dev *dev, + const struct ftl_mngt_process_desc *process, + ftl_mngt_fn cb, void *cb_ctx); + +/** + * @brief Executes rollback on the FTL management process defined by the process + * descriptor + * + * All cleanup function from steps will be executed in reversed order + * + * @param dev FTL device + * @param process The descriptor of process to be rollback + * @param cb Caller callback + * @param cb_ctx Caller context + * + * @return Result of invoking the rollback operation + * @retval 0 - Rollback of the FTL management process has been started + * @retval Non-zero An error occurred when starting the rollback + */ +int ftl_mngt_process_rollback(struct spdk_ftl_dev *dev, + const struct ftl_mngt_process_desc *process, + ftl_mngt_fn cb, void *cb_ctx); + +/* + * FTL management API for steps + */ + +/** + * @brief Gets FTL device + * + * @param mngt FTL management handle + * + * @note This function can be invoked within step handler only + * + * @return FTL device + */ +struct spdk_ftl_dev *ftl_mngt_get_dev(struct ftl_mngt_process *mngt); + +/** + * @brief Allocates a context for the management step + * + * @param mngt FTL management handle + * @param size Size of the step context + * + * @note This function can be invoked within ftl_mngt_fn callback only + * + * @return Operation result + * @retval 0 Operation successful + * @retval Non-zero Operation failure + */ +int ftl_mngt_alloc_step_ctx(struct ftl_mngt_process *mngt, size_t size); + +/** + * @brief Gets the management step context + * + * @param mngt FTL management handle + * + * @note This function can be invoked within ftl_mngt_fn callback only + * + * @return Context of the step containing pointer to buffer and its size + */ +void *ftl_mngt_get_step_ctx(struct ftl_mngt_process *mngt); + +/** + * @brief Gets the management process context + * + * @param mngt FTL management handle + * + * @note This function can be invoked within ftl_mngt_fn callback only + * + * @return Context of the process containing pointer to buffer and its size + */ +void *ftl_mngt_get_process_ctx(struct ftl_mngt_process *mngt); + +/** + * @brief Gets the caller context + * + * @param mngt FTL management handle + * + * @note This function can be invoked within ftl_mngt_fn callback only + * + * @return Pointer to the caller context + */ +void *ftl_mngt_get_caller_ctx(struct ftl_mngt_process *mngt); + +/** + * @brief Gets the status of executed management process + * + * @param mngt FTL management handle + * + * @note This function can be invoked within ftl_mngt_fn callback only + * + * @return The operation result of the management process + * @retval 0 Operation successful + * @retval Non-zero Operation failure + */ +int ftl_mngt_get_status(struct ftl_mngt_process *mngt); + +/** + * @brief Finishes the management process immediately + * + * @note This function can be invoked within ftl_mngt_fn callback only + * + * @param mngt FTL management handle of process to be finished + */ +void ftl_mngt_finish(struct ftl_mngt_process *mngt); + +/** + * @brief Completes the step currently in progress and jump to a next one + * + * If no more steps to be executed then the management process is finished and + * caller callback is invoked + * + * @note This function can be invoked within ftl_mngt_fn callback only + * + * @param mngt FTL management handle + */ +void ftl_mngt_next_step(struct ftl_mngt_process *mngt); + +/** + * @brief Skips the step currently in progress and jump to a next one + * + * @note This function can be invoked within ftl_mngt_fn callback only + * + * @param mngt FTL management handle + */ +void ftl_mngt_skip_step(struct ftl_mngt_process *mngt); + +/** + * @brief Continue the step currently in progress + * + * This causes invoking the same step handler in next iteration of the + * management process. This mechanism can be used by a job when polling for + * something. + * + * @note This function can be invoked within ftl_mngt_fn callback only + * + * @param mngt FTL management handle + */ +void ftl_mngt_continue_step(struct ftl_mngt_process *mngt); + +/** + * @brief Fail the step currently in progress. + * + * It stops executing all steps and starts the rollback procedure (calling + * the cleanup functions of all already executed steps). + * If executed from a cleanup function, it will stop executing and the following + * cleanup functions (if any) will be executed. + * + * @param mngt FTL management handle + */ +void ftl_mngt_fail_step(struct ftl_mngt_process *mngt); + +/** + * @brief Calls another management process + * + * Ends the current step and executes specified process and finally continues + * executing the the remaining steps + * + * @param mngt The management handle + * @param process The management process to be called + */ +void ftl_mngt_call_process(struct ftl_mngt_process *mngt, + const struct ftl_mngt_process_desc *process); + +/** + * @brief Calls rollback steps of another management process + * + * Ends the current step and executes rollback steps of specified process + * and finally continues executing the remaining steps in the original process + * + * @param mngt The management handle + * @param process The management process to be called to execute rollback + */ +void ftl_mngt_call_process_rollback(struct ftl_mngt_process *mngt, + const struct ftl_mngt_process_desc *process); + +#endif /* LIB_FTL_FTL_MNGT_H */ diff --git a/mk/spdk.lib_deps.mk b/mk/spdk.lib_deps.mk index 52422c4ad..faaf0cebb 100644 --- a/mk/spdk.lib_deps.mk +++ b/mk/spdk.lib_deps.mk @@ -60,7 +60,7 @@ DEPDIRS-blobfs := log thread blob trace util DEPDIRS-event := log util thread $(JSON_LIBS) trace init DEPDIRS-init := jsonrpc json log rpc thread util -DEPDIRS-ftl := log +DEPDIRS-ftl := log thread DEPDIRS-nbd := log util thread $(JSON_LIBS) bdev DEPDIRS-nvmf := accel log sock util nvme thread $(JSON_LIBS) trace bdev ifeq ($(CONFIG_RDMA),y) diff --git a/test/unit/lib/Makefile b/test/unit/lib/Makefile index 312f63509..5269b83ad 100644 --- a/test/unit/lib/Makefile +++ b/test/unit/lib/Makefile @@ -12,6 +12,7 @@ DIRS-$(CONFIG_IDXD) += idxd DIRS-$(CONFIG_REDUCE) += reduce ifeq ($(OS),Linux) DIRS-$(CONFIG_VHOST) += vhost +DIRS-y += ftl endif .PHONY: all clean $(DIRS-y) diff --git a/test/unit/lib/ftl/Makefile b/test/unit/lib/ftl/Makefile new file mode 100644 index 000000000..26fd4cf05 --- /dev/null +++ b/test/unit/lib/ftl/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Intel Corporation. +# All rights reserved. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = ftl_mngt + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/test/unit/lib/ftl/ftl_mngt/.gitignore b/test/unit/lib/ftl/ftl_mngt/.gitignore new file mode 100644 index 000000000..71b95d350 --- /dev/null +++ b/test/unit/lib/ftl/ftl_mngt/.gitignore @@ -0,0 +1 @@ +ftl_mngt_ut diff --git a/test/unit/lib/ftl/ftl_mngt/Makefile b/test/unit/lib/ftl/ftl_mngt/Makefile new file mode 100644 index 000000000..ab8ea5709 --- /dev/null +++ b/test/unit/lib/ftl/ftl_mngt/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Intel Corporation. +# All rights reserved. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..) + +TEST_FILE = ftl_mngt_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk + +CFLAGS += -I$(SPDK_ROOT_DIR)/lib/ftl diff --git a/test/unit/lib/ftl/ftl_mngt/ftl_mngt_ut.c b/test/unit/lib/ftl/ftl_mngt/ftl_mngt_ut.c new file mode 100644 index 000000000..92dc413ad --- /dev/null +++ b/test/unit/lib/ftl/ftl_mngt/ftl_mngt_ut.c @@ -0,0 +1,1110 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (c) Intel Corporation. + * All rights reserved. + */ + +#include + +#include "spdk/stdinc.h" + +#include "spdk_cunit.h" +#include "common/lib/test_env.c" + +#include "ftl/mngt/ftl_mngt.c" + +#define CALLER_CB_RET_VALUE 999 + +/* list for structure with results of tests from callbacks */ +struct entry { + int data; + TAILQ_ENTRY(entry) entries; +}; + +TAILQ_HEAD(listhead, entry) g_head; + +struct thread_send_msg_container { + spdk_msg_fn fn; + void *ctx; +} g_thread_send_msg_container; + +struct spdk_ftl_dev g_dev; + +int +spdk_thread_send_msg(const struct spdk_thread *thread, spdk_msg_fn fn, void *ctx) +{ + g_thread_send_msg_container.fn = fn; + g_thread_send_msg_container.ctx = ctx; + return 0; +} + +struct spdk_thread * +spdk_get_thread(void) +{ + struct spdk_thread *thd = (struct spdk_thread *)0x1; + return thd; +} + +static int +setup_test_list(void) +{ + TAILQ_INIT(&g_head); + return 0; +} + +static void +check_list_empty(void) +{ + CU_ASSERT_TRUE(TAILQ_EMPTY(&g_head)); +} + +static void +add_elem_to_test_list(int data) +{ + struct entry *en = calloc(1, sizeof(*en)); + SPDK_CU_ASSERT_FATAL(en != NULL); + en->data = data; + TAILQ_INSERT_TAIL(&g_head, en, entries); +} + +static void +check_elem_on_list_and_remove(int compared_elem) +{ + struct entry *en = TAILQ_FIRST(&g_head); + if (en != NULL) { + CU_ASSERT_EQUAL(en->data, compared_elem); + TAILQ_REMOVE(&g_head, en, entries); + free(en); + } else { + CU_FAIL("not null value was expected"); + } +} + +static void +fn_finish(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(CALLER_CB_RET_VALUE); + g_thread_send_msg_container.fn = NULL; + g_thread_send_msg_container.ctx = NULL; +} + +typedef int (*ftl_execute_fn)(struct spdk_ftl_dev *dev, + const struct ftl_mngt_process_desc *process, + ftl_mngt_fn cb, void *cb_cntx); + +static void +run_ftl_mngt_with_cb_cntx(ftl_execute_fn exec_fn, + const struct ftl_mngt_process_desc *process, void *cb_cntx) +{ + int result = exec_fn(&g_dev, process, fn_finish, cb_cntx); + CU_ASSERT_EQUAL(result, 0); + while (g_thread_send_msg_container.fn != NULL) { + g_thread_send_msg_container.fn(g_thread_send_msg_container.ctx); + } +} + +static void +run_ftl_mngt(ftl_execute_fn exec_fn, + const struct ftl_mngt_process_desc *process) +{ + run_ftl_mngt_with_cb_cntx(exec_fn, process, NULL); +} + +/*- + * test 1 + * tests simple invoking next steps + * it is shown if ftl_mngt_process_execute and ftl_mngt_process_rollback invoke functions in proper order + * (functions call only ftl_mngt_next_step) + */ + +static void +fn_1_1_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(1); + ftl_mngt_next_step(mngt); +} + +static void +fn_1_1_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-1); + ftl_mngt_next_step(mngt); +} + +static void +fn_1_2_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(2); + ftl_mngt_next_step(mngt); +} + +static void +fn_1_3_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(3); + ftl_mngt_next_step(mngt); +} + +static void +fn_1_3_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-3); + ftl_mngt_next_step(mngt); +} + +static struct ftl_mngt_process_desc pdesc_test_1 = { + .name = "process 1", + .steps = { + { + .name = "step 1", + .action = fn_1_1_action, + .cleanup = fn_1_1_cleanup + }, + { + .name = "step 2", + .action = fn_1_2_action + }, + { + .name = "step 3", + .action = fn_1_3_action, + .cleanup = fn_1_3_cleanup + }, + {} + } +}; + +static void +test_next_step(void) +{ + run_ftl_mngt(ftl_mngt_process_execute, &pdesc_test_1); + + /* check proper order of action functions */ + for (int i = 1; i <= 3; i++) { + check_elem_on_list_and_remove(i); + } + + /* check if caller callback was invoked */ + check_elem_on_list_and_remove(CALLER_CB_RET_VALUE); + + run_ftl_mngt(ftl_mngt_process_rollback, &pdesc_test_1); + + /* Check proper order of cleanup functions. + * Cleanup functions add to list opposite values to action functions. + * Cleanup functions are invoked in reverse order, + * moreover action 2 does not have cleanup, + * so expected values are -3, then -1 */ + check_elem_on_list_and_remove(-3); + check_elem_on_list_and_remove(-1); + + /* check if caller callback was invoked */ + check_elem_on_list_and_remove(CALLER_CB_RET_VALUE); + + check_list_empty(); +} + +/*- + * test 2 + * tests action and cleanup function which invoke + * ftl_mngt_continue_step function + */ + +static void +fn_2_common_part(struct ftl_mngt_process *mngt, int elem) +{ + struct entry *en = TAILQ_LAST(&g_head, listhead); + + if (en == NULL || en->data != elem) { + /* if function was invoked 1st time, make it once again */ + add_elem_to_test_list(elem); + ftl_mngt_continue_step(mngt); + } else { + /* otherwise go to the next function */ + add_elem_to_test_list(elem); + ftl_mngt_next_step(mngt); + } +} + +static void +fn_2_1_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + fn_2_common_part(mngt, 1); +} + +static void +fn_2_1_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + fn_2_common_part(mngt, -1); +} + +static void +fn_2_2_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + fn_2_common_part(mngt, 2); +} + +static void +fn_2_2_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + fn_2_common_part(mngt, -2); +} + +static struct ftl_mngt_process_desc pdesc_test_2 = { + .name = "process 2", + .steps = { + { + .name = "step 1", + .action = fn_2_1_action, + .cleanup = fn_2_1_cleanup + }, + { + .name = "step 2", + .action = fn_2_2_action, + .cleanup = fn_2_2_cleanup + }, + {} + } +}; + +static void +test_continue_step(void) +{ + run_ftl_mngt(ftl_mngt_process_execute, &pdesc_test_2); + + /* check proper order of action functions */ + check_elem_on_list_and_remove(1); + check_elem_on_list_and_remove(1); + check_elem_on_list_and_remove(2); + check_elem_on_list_and_remove(2); + + /* check if caller callback was invoked */ + check_elem_on_list_and_remove(CALLER_CB_RET_VALUE); + + run_ftl_mngt(ftl_mngt_process_rollback, &pdesc_test_2); + + /* check proper order of action functions */ + check_elem_on_list_and_remove(-2); + check_elem_on_list_and_remove(-2); + check_elem_on_list_and_remove(-1); + check_elem_on_list_and_remove(-1); + + /* check if caller callback was invoked */ + check_elem_on_list_and_remove(CALLER_CB_RET_VALUE); + + check_list_empty(); +} + +/*- + * test 3 + * tests ftl_mngt_alloc_step_cntx and all ftl_mngt_get functions + */ + +const int PROCESS_CNTX_TEST_VAL_0 = 21; +const int PROCESS_CNTX_TEST_VAL_1 = 37; +const int STEP_CNTX_TEST_VAL = 1; + +static void +put_on_list(void) +{ + struct entry *en = calloc(1, sizeof(*en)); + SPDK_CU_ASSERT_FATAL(en != NULL); + TAILQ_INSERT_TAIL(&g_head, en, entries); +} + +static bool +check_if_list_empty_and_clean(void) +{ + struct entry *en = TAILQ_FIRST(&g_head); + if (en == NULL) { + return true; + } else { + TAILQ_REMOVE(&g_head, en, entries); + free(en); + return false; + } +} + +static void +fn_3_1_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + int *step_cntx_ptr, *process_cntx_ptr; + char *caller_cntx_ptr; + int status; + + step_cntx_ptr = ftl_mngt_get_step_ctx(mngt); + if (check_if_list_empty_and_clean()) { + /* In 1st run of this function test list is empty + * and 'if' is true, this part of function is done. + * That 'if' part ends with ftl_mngt_continue_step, + * so function will be called once again. + * Element is added to the test list + * to invoke 'else' in second run */ + put_on_list(); + /* this step descriptor does not locate any context + * at the beginning, + * so pointer should contain NULL */ + CU_ASSERT_PTR_NULL(step_cntx_ptr); + + status = ftl_mngt_alloc_step_ctx(mngt, sizeof(*step_cntx_ptr)); + SPDK_CU_ASSERT_FATAL(status == 0); + step_cntx_ptr = ftl_mngt_get_step_ctx(mngt); + /* now pointer should point to allocated context */ + CU_ASSERT_PTR_NOT_NULL(step_cntx_ptr); + + /* this value should be retrieved in second run of function + * (in 'else' part) */ + *step_cntx_ptr = STEP_CNTX_TEST_VAL; + + ftl_mngt_continue_step(mngt); + } else { + /* In second run retrieved pointer is not empty. + * Moreover it should contain value allocated for this step + * in previous run of function */ + CU_ASSERT_PTR_NOT_NULL(step_cntx_ptr); + CU_ASSERT_EQUAL(*step_cntx_ptr, STEP_CNTX_TEST_VAL); + + /* check getting device */ + CU_ASSERT_EQUAL(ftl_mngt_get_dev(mngt), dev); + CU_ASSERT_EQUAL(ftl_mngt_get_dev(mngt), &g_dev); + + /* tests for process context */ + process_cntx_ptr = ftl_mngt_get_process_ctx(mngt); + + /* 1st get of process context, should be clear ('0' values) */ + CU_ASSERT_EQUAL(process_cntx_ptr[0], 0); + CU_ASSERT_EQUAL(process_cntx_ptr[1], 0); + + /* Random values put in process context. + * Should be retrieved in the next function + * (it is common space for the entire process) */ + process_cntx_ptr[0] = PROCESS_CNTX_TEST_VAL_0; + process_cntx_ptr[1] = PROCESS_CNTX_TEST_VAL_1; + + /* tests for caller context */ + caller_cntx_ptr = ftl_mngt_get_caller_ctx(mngt); + + /* check previously located values */ + CU_ASSERT_EQUAL(caller_cntx_ptr[0], 'd'); + CU_ASSERT_EQUAL(caller_cntx_ptr[1], 'a'); + CU_ASSERT_EQUAL(caller_cntx_ptr[2], 'j'); + + /* insert new */ + caller_cntx_ptr[0] = ' '; + caller_cntx_ptr[1] = 'k'; + caller_cntx_ptr[2] = 'a'; + + ftl_mngt_next_step(mngt); + } +} + +static void +fn_3_2_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + int *step_cntx_ptr, *process_cntx_ptr; + char *caller_cntx_ptr; + int status; + + step_cntx_ptr = ftl_mngt_get_step_ctx(mngt); + /* context of this step descriptor is never empty + * so pointer cannot contain NULL */ + CU_ASSERT_PTR_NOT_NULL(step_cntx_ptr); + + if (check_if_list_empty_and_clean()) { + /* In 1st run of this function test list is empty + * and 'if' is true, this part of function is done. + * That 'if' part ends with ftl_mngt_continue_step, + * so function will be called once again. + * Element is added to the test list + * to invoke 'else' in second run */ + put_on_list(); + + /* check getting device */ + CU_ASSERT_EQUAL(ftl_mngt_get_dev(mngt), dev); + CU_ASSERT_EQUAL(ftl_mngt_get_dev(mngt), &g_dev); + + /* tests for process context */ + process_cntx_ptr = ftl_mngt_get_process_ctx(mngt); + + /* check if it is possible to retrieve values located + * in process context by previous function */ + CU_ASSERT_EQUAL(process_cntx_ptr[0], PROCESS_CNTX_TEST_VAL_0); + CU_ASSERT_EQUAL(process_cntx_ptr[1], PROCESS_CNTX_TEST_VAL_1); + + /* tests for caller context */ + caller_cntx_ptr = ftl_mngt_get_caller_ctx(mngt); + + /* check previously located values */ + CU_ASSERT_EQUAL(caller_cntx_ptr[0], ' '); + CU_ASSERT_EQUAL(caller_cntx_ptr[1], 'k'); + CU_ASSERT_EQUAL(caller_cntx_ptr[2], 'a'); + + /* insert new */ + caller_cntx_ptr[0] = 'm'; + caller_cntx_ptr[1] = 'i'; + caller_cntx_ptr[2] = 'e'; + + /* first run of step so reserved step context + * was never used before and should contain 0 */ + CU_ASSERT_EQUAL(*step_cntx_ptr, 0); + + /* this value should be retrieved in second run of function + * (in 'else' part) */ + *step_cntx_ptr = STEP_CNTX_TEST_VAL; + + ftl_mngt_continue_step(mngt); + } else { + /* In second run retrieved pointer should contain value + * allocated for this step in previous run of function */ + CU_ASSERT_EQUAL(*step_cntx_ptr, STEP_CNTX_TEST_VAL); + + status = ftl_mngt_alloc_step_ctx(mngt, sizeof(*step_cntx_ptr)); + SPDK_CU_ASSERT_FATAL(status == 0); + step_cntx_ptr = ftl_mngt_get_step_ctx(mngt); + + /* now pointer should point to newly allocated context + * and be cleaned up (should contain '0') */ + CU_ASSERT_PTR_NOT_NULL(step_cntx_ptr); + CU_ASSERT_EQUAL(*step_cntx_ptr, 0); + + ftl_mngt_next_step(mngt); + } +} + +static void +fn_3_2_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + int *step_cntx_ptr, *process_cntx_ptr; + char *caller_cntx_ptr; + int status; + + step_cntx_ptr = ftl_mngt_get_step_ctx(mngt); + /* context of this step descriptor is never empty + * so pointer cannot contain NULL */ + CU_ASSERT_PTR_NOT_NULL(step_cntx_ptr); + + if (check_if_list_empty_and_clean()) { + /* In 1st run of this function test list is empty + * and 'if' is true, this part of function is done. + * That 'if' part ends with ftl_mngt_continue_step, + * so function will be called once again. + * Element is added to the test list + * to invoke 'else' in second run */ + put_on_list(); + + /* first run of step so reserved step context + * was never used before and should contain 0 */ + CU_ASSERT_EQUAL(*step_cntx_ptr, 0); + + /* this value should be retrieved in second run of function + * (in 'else' part) */ + *step_cntx_ptr = STEP_CNTX_TEST_VAL; + + ftl_mngt_continue_step(mngt); + } else { + /* In second run retrieved pointer should contain value + * allocated for this step in previous run of function */ + CU_ASSERT_EQUAL(*step_cntx_ptr, STEP_CNTX_TEST_VAL); + + status = ftl_mngt_alloc_step_ctx(mngt, sizeof(*step_cntx_ptr)); + SPDK_CU_ASSERT_FATAL(status == 0); + step_cntx_ptr = ftl_mngt_get_step_ctx(mngt); + + /* now pointer should point to newly allocated context + * and be cleaned up (should contain '0') */ + CU_ASSERT_PTR_NOT_NULL(step_cntx_ptr); + CU_ASSERT_EQUAL(*step_cntx_ptr, 0); + + /* check getting device */ + CU_ASSERT_EQUAL(ftl_mngt_get_dev(mngt), dev); + CU_ASSERT_EQUAL(ftl_mngt_get_dev(mngt), &g_dev); + + /* tests for process context */ + process_cntx_ptr = ftl_mngt_get_process_ctx(mngt); + + /* 1st get of process context, should be clear ('0' values) */ + CU_ASSERT_EQUAL(process_cntx_ptr[0], 0); + CU_ASSERT_EQUAL(process_cntx_ptr[1], 0); + + /* Random values put in process context. + * Should be retrieved in the next function + * (it is common space for the entire process) */ + process_cntx_ptr[0] = PROCESS_CNTX_TEST_VAL_0; + process_cntx_ptr[1] = PROCESS_CNTX_TEST_VAL_1; + + /* tests for caller context */ + caller_cntx_ptr = ftl_mngt_get_caller_ctx(mngt); + + /* check previously located values */ + CU_ASSERT_EQUAL(caller_cntx_ptr[0], 'm'); + CU_ASSERT_EQUAL(caller_cntx_ptr[1], 'i'); + CU_ASSERT_EQUAL(caller_cntx_ptr[2], 'e'); + + /* insert new */ + caller_cntx_ptr[0] = 'n'; + caller_cntx_ptr[1] = 'i'; + caller_cntx_ptr[2] = 'a'; + + ftl_mngt_next_step(mngt); + } +} + +static void +fn_3_1_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + int *step_cntx_ptr, *process_cntx_ptr; + char *caller_cntx_ptr; + int status; + + step_cntx_ptr = ftl_mngt_get_step_ctx(mngt); + if (check_if_list_empty_and_clean()) { + /* In 1st run of this function test list is empty + * and 'if' is true, this part of function is done. + * That 'if' part ends with ftl_mngt_continue_step, + * so function will be called once again. + * Element is added to the test list + * to invoke 'else' in second run */ + put_on_list(); + /* this step descriptor does not locate any context + * at the beginning, + * so pointer should contain NULL */ + CU_ASSERT_PTR_NULL(step_cntx_ptr); + + /* check getting device */ + CU_ASSERT_EQUAL(ftl_mngt_get_dev(mngt), dev); + CU_ASSERT_EQUAL(ftl_mngt_get_dev(mngt), &g_dev); + + /* tests for process context */ + process_cntx_ptr = ftl_mngt_get_process_ctx(mngt); + + /* check if it is possible to retrieve values located + * in process context by previous function */ + CU_ASSERT_EQUAL(process_cntx_ptr[0], PROCESS_CNTX_TEST_VAL_0); + CU_ASSERT_EQUAL(process_cntx_ptr[1], PROCESS_CNTX_TEST_VAL_1); + + /* tests for caller context */ + caller_cntx_ptr = ftl_mngt_get_caller_ctx(mngt); + + /* check previously located values */ + CU_ASSERT_EQUAL(caller_cntx_ptr[0], 'n'); + CU_ASSERT_EQUAL(caller_cntx_ptr[1], 'i'); + CU_ASSERT_EQUAL(caller_cntx_ptr[2], 'a'); + + /* insert new */ + caller_cntx_ptr[0] = '!'; + caller_cntx_ptr[1] = '!'; + caller_cntx_ptr[2] = '!'; + + status = ftl_mngt_alloc_step_ctx(mngt, sizeof(*step_cntx_ptr)); + SPDK_CU_ASSERT_FATAL(status == 0); + step_cntx_ptr = ftl_mngt_get_step_ctx(mngt); + /* now pointer should point to allocated context */ + CU_ASSERT_PTR_NOT_NULL(step_cntx_ptr); + + /* this value should be retrieved in second run of function + * (in 'else' part) */ + *step_cntx_ptr = STEP_CNTX_TEST_VAL; + + ftl_mngt_continue_step(mngt); + } else { + /* In second run retrieved pointer is not empty. + * Moreover it should contain value allocated for this step + * in previous run of function */ + CU_ASSERT_PTR_NOT_NULL(step_cntx_ptr); + CU_ASSERT_EQUAL(*step_cntx_ptr, STEP_CNTX_TEST_VAL); + + ftl_mngt_next_step(mngt); + } +} + +static struct ftl_mngt_process_desc pdesc_test_3 = { + .name = "process 3", + .ctx_size = 2 * sizeof(int), + .steps = { + { + .name = "step 1", + .action = fn_3_1_action, + .cleanup = fn_3_1_cleanup + }, + { + .name = "step 2", + .ctx_size = sizeof(int), + .action = fn_3_2_action, + .cleanup = fn_3_2_cleanup + }, + {} + } +}; + +static void +test_get_func_and_step_cntx_alloc(void) +{ + char cb_cntx[4] = "daj"; + + run_ftl_mngt_with_cb_cntx(ftl_mngt_process_execute, &pdesc_test_3, cb_cntx); + + /* check if caller callback was invoked */ + check_elem_on_list_and_remove(CALLER_CB_RET_VALUE); + + /* check if steps changed cb_cntx correctly */ + CU_ASSERT_EQUAL(cb_cntx[0], 'm'); + CU_ASSERT_EQUAL(cb_cntx[1], 'i'); + CU_ASSERT_EQUAL(cb_cntx[2], 'e'); + + run_ftl_mngt_with_cb_cntx(ftl_mngt_process_rollback, &pdesc_test_3, cb_cntx); + + /* check if caller callback was invoked */ + check_elem_on_list_and_remove(CALLER_CB_RET_VALUE); + + /* check if steps changed cb_cntx correctly */ + CU_ASSERT_EQUAL(cb_cntx[0], '!'); + CU_ASSERT_EQUAL(cb_cntx[1], '!'); + CU_ASSERT_EQUAL(cb_cntx[2], '!'); + + check_list_empty(); +} + + + +/*- + * test 4 + * tests ftl_mngt_fail_step function + * + * In that test one of the action functions fails (third one). + * Because of that expected result (saved on the test result list) + * are numbers of the next action function up to failing function. + * After that cleanup functions are invoked in reversed order. + */ + +static void +fn_4_1_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(1); + ftl_mngt_next_step(mngt); +} + +static void +fn_4_1_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-1); + ftl_mngt_next_step(mngt); +} + +static void +fn_4_2_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(2); + ftl_mngt_next_step(mngt); +} + +static void +fn_4_2_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-2); + ftl_mngt_next_step(mngt); +} + +static void +fn_4_3_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(3); + /* this action fails, so cleanup should begin now */ + ftl_mngt_fail_step(mngt); +} + +static void +fn_4_3_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-3); + ftl_mngt_next_step(mngt); +} + +static void +fn_4_4_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + CU_FAIL("failure cannot start another action"); + ftl_mngt_next_step(mngt); +} + +static struct ftl_mngt_process_desc pdesc_test_4 = { + .name = "process 4", + .steps = { + { + .name = "step 1", + .action = fn_4_1_action, + .cleanup = fn_4_1_cleanup + }, + { + .name = "step 2", + .action = fn_4_2_action, + .cleanup = fn_4_2_cleanup + }, + { + .name = "step 3", + .action = fn_4_3_action, + .cleanup = fn_4_3_cleanup + }, + { + .name = "step 2", + .action = fn_4_4_action + }, + {} + } +}; + +static void +test_fail_step(void) +{ + run_ftl_mngt(ftl_mngt_process_execute, &pdesc_test_4); + + /* check proper order of action functions */ + for (int i = 1; i <= 3; i++) { + check_elem_on_list_and_remove(i); + } + + /* 3rd action function fails, so now should be + * cleanup functions in reverse order */ + for (int i = 3; i > 0; i--) { + check_elem_on_list_and_remove(-i); + } + + /* check if caller callback was invoked */ + check_elem_on_list_and_remove(CALLER_CB_RET_VALUE); + + check_list_empty(); +} + +/*- + * test 5 + * tests ftl_mngt_call_process and ftl_mngt_call_process_rollback functions + * tests only proper flow without failures + */ + +static void +fn_5_2_1_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(21); + ftl_mngt_next_step(mngt); +} + +static void +fn_5_2_1_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-21); + ftl_mngt_next_step(mngt); +} + +static void +fn_5_2_2_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(22); + ftl_mngt_next_step(mngt); +} + +static void +fn_5_2_2_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-22); + ftl_mngt_next_step(mngt); +} + +static struct ftl_mngt_process_desc pdesc_test_5_2 = { + .name = "process nested inside step 2 from process 5", + .steps = { + { + .name = "step 2_1", + .action = fn_5_2_1_action, + .cleanup = fn_5_2_1_cleanup + }, + { + .name = "step 2_2", + .action = fn_5_2_2_action, + .cleanup = fn_5_2_2_cleanup + }, + {} + } +}; + +static void +fn_5_3_1_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(31); + ftl_mngt_next_step(mngt); +} + +static void +fn_5_3_1_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-31); + ftl_mngt_next_step(mngt); +} + +static void +fn_5_3_2_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(32); + ftl_mngt_next_step(mngt); +} + +static void +fn_5_3_2_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-32); + ftl_mngt_next_step(mngt); +} + +static struct ftl_mngt_process_desc pdesc_test_5_3 = { + .name = "process nested inside step 2 from process 5", + .steps = { + { + .name = "step 3_1", + .action = fn_5_3_1_action, + .cleanup = fn_5_3_1_cleanup + }, + { + .name = "step 3_2", + .action = fn_5_3_2_action, + .cleanup = fn_5_3_2_cleanup + }, + {} + } +}; + +static void +fn_5_1_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(1); + ftl_mngt_next_step(mngt); +} + +static void +fn_5_1_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-1); + ftl_mngt_next_step(mngt); +} + +static void +fn_5_2_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(2); + ftl_mngt_call_process(mngt, &pdesc_test_5_2); +} + +static void +fn_5_2_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-2); + ftl_mngt_call_process_rollback(mngt, &pdesc_test_5_2); +} + +static void +fn_5_3_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(3); + ftl_mngt_call_process_rollback(mngt, &pdesc_test_5_3); +} + +static void +fn_5_3_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-3); + ftl_mngt_call_process(mngt, &pdesc_test_5_3); +} + +static struct ftl_mngt_process_desc pdesc_test_5 = { + .name = "process 5 main", + .steps = { + { + .name = "step 1", + .action = fn_5_1_action, + .cleanup = fn_5_1_cleanup + }, + { + .name = "step 2", + .action = fn_5_2_action, + .cleanup = fn_5_2_cleanup + }, + { + .name = "step 3", + .action = fn_5_3_action, + .cleanup = fn_5_3_cleanup + }, + {} + } +}; + +static void +test_mngt_call_and_call_rollback(void) +{ + run_ftl_mngt(ftl_mngt_process_execute, &pdesc_test_5); + + check_elem_on_list_and_remove(1); + check_elem_on_list_and_remove(2); + check_elem_on_list_and_remove(21); + check_elem_on_list_and_remove(22); + check_elem_on_list_and_remove(3); + check_elem_on_list_and_remove(-32); + check_elem_on_list_and_remove(-31); + + /* check if caller callback was invoked */ + check_elem_on_list_and_remove(CALLER_CB_RET_VALUE); + + run_ftl_mngt(ftl_mngt_process_rollback, &pdesc_test_5); + + check_elem_on_list_and_remove(-3); + check_elem_on_list_and_remove(31); + check_elem_on_list_and_remove(32); + check_elem_on_list_and_remove(-2); + check_elem_on_list_and_remove(-22); + check_elem_on_list_and_remove(-21); + check_elem_on_list_and_remove(-1); + + /* check if caller callback was invoked */ + check_elem_on_list_and_remove(CALLER_CB_RET_VALUE); + + check_list_empty(); +} + +/* + * test 6 + * tests failure inside nested process + */ + +static void +fn_6_2_1_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(21); + ftl_mngt_next_step(mngt); +} + +static void +fn_6_2_1_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-21); + ftl_mngt_next_step(mngt); +} + +static void +fn_6_2_2_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(22); + /* this action fails, so cleanup should begin now */ + ftl_mngt_fail_step(mngt); +} + +static void +fn_6_2_3_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + CU_FAIL("failure cannot start another action"); + ftl_mngt_next_step(mngt); +} + +static struct ftl_mngt_process_desc pdesc_test_6_2 = { + .name = "process nested inside step 2 from process 6", + .steps = { + { + .name = "step 6_1", + .action = fn_6_2_1_action, + .cleanup = fn_6_2_1_cleanup + }, + { + .name = "step 6_2", + .action = fn_6_2_2_action + }, + { + .name = "step 6_3", + .action = fn_6_2_3_action + }, + {} + } +}; + +static void +fn_6_1_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(1); + ftl_mngt_next_step(mngt); +} + +static void +fn_6_2_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(2); + ftl_mngt_call_process(mngt, &pdesc_test_6_2); +} + +static void +fn_6_2_cleanup(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + add_elem_to_test_list(-2); + ftl_mngt_next_step(mngt); +} + +static void +fn_6_3_action(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt) +{ + CU_FAIL("failure cannot start another action"); + ftl_mngt_next_step(mngt); +} + +static struct ftl_mngt_process_desc pdesc_test_6 = { + .name = "process 6 main", + .steps = { + { + .name = "step 1", + .action = fn_6_1_action + }, + { + .name = "step 2", + .action = fn_6_2_action, + .cleanup = fn_6_2_cleanup + }, + { + .name = "step 3", + .action = fn_6_3_action + }, + {} + } +}; + +static void +test_nested_process_failure(void) +{ + run_ftl_mngt(ftl_mngt_process_execute, &pdesc_test_6); + + check_elem_on_list_and_remove(1); + check_elem_on_list_and_remove(2); + check_elem_on_list_and_remove(21); + check_elem_on_list_and_remove(22); + check_elem_on_list_and_remove(-21); + check_elem_on_list_and_remove(-2); + + /* check if caller callback was invoked */ + check_elem_on_list_and_remove(CALLER_CB_RET_VALUE); + + check_list_empty(); +} + +int +main(int argc, char **argv) +{ + CU_pSuite suite = NULL; + unsigned int num_failures; + + CU_set_error_action(CUEA_ABORT); + CU_initialize_registry(); + + suite = CU_add_suite("ftl_mngt", setup_test_list, NULL); + + CU_ADD_TEST(suite, test_next_step); + CU_ADD_TEST(suite, test_continue_step); + CU_ADD_TEST(suite, test_get_func_and_step_cntx_alloc); + CU_ADD_TEST(suite, test_fail_step); + CU_ADD_TEST(suite, test_mngt_call_and_call_rollback); + CU_ADD_TEST(suite, test_nested_process_failure); + + 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/test/unit/unittest.sh b/test/unit/unittest.sh index 8007085df..5a928fbe7 100755 --- a/test/unit/unittest.sh +++ b/test/unit/unittest.sh @@ -44,6 +44,10 @@ function unittest_event() { $valgrind $testdir/lib/event/reactor.c/reactor_ut } +function unittest_ftl() { + $valgrind $testdir/lib/ftl/ftl_mngt/ftl_mngt_ut +} + function unittest_iscsi() { $valgrind $testdir/lib/iscsi/conn.c/conn_ut $valgrind $testdir/lib/iscsi/param.c/param_ut @@ -206,6 +210,9 @@ fi run_test "unittest_blob_blobfs" unittest_blob run_test "unittest_event" unittest_event +if [ $(uname -s) = Linux ]; then + run_test "unittest_ftl" unittest_ftl +fi run_test "unittest_accel" $valgrind $testdir/lib/accel/accel.c/accel_engine_ut run_test "unittest_ioat" $valgrind $testdir/lib/ioat/ioat.c/ioat_ut