From 7140c2f71003b245e1a0789ea044364a44de6920 Mon Sep 17 00:00:00 2001 From: Tomasz Zawadzki Date: Thu, 23 May 2019 03:53:44 -0400 Subject: [PATCH] test/bdevio: add perform_tests RPC New RPC added specifically to the bdevio app. It will launch all tests on all targets by default. When optional parameter for bdev name provided is supplied tests will be ran against just that bdev. After finishing, the application will await another RPC. stop_init_thread() and __run_ut_thread now pass json request. Added separate test.py to not pollute rpc.py with custom RPC just for the bdevio application. Added '-w' argument to the bdevio app, so that instead of performing the tests immidietly - it awaits RPC. After a lot of changes to blockdev.sh, we might end up removing this argument and just use it as default. Change-Id: I82e52352bdf8082c1712caa223ad5ab78aa4e7fa Signed-off-by: Tomasz Zawadzki Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/455200 Tested-by: SPDK CI Jenkins Reviewed-by: Paul Luse Reviewed-by: Darek Stojaczyk Reviewed-by: Shuhei Matsumoto Reviewed-by: Jim Harris --- test/bdev/bdevio/bdevio.c | 119 ++++++++++++++++++++++++++++++++++++-- test/bdev/bdevio/tests.py | 84 +++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 6 deletions(-) create mode 100755 test/bdev/bdevio/tests.py diff --git a/test/bdev/bdevio/bdevio.c b/test/bdev/bdevio/bdevio.c index 8d5e23c57..416980c9d 100644 --- a/test/bdev/bdevio/bdevio.c +++ b/test/bdev/bdevio/bdevio.c @@ -39,6 +39,9 @@ #include "spdk/log.h" #include "spdk/thread.h" #include "spdk/event.h" +#include "spdk/rpc.h" +#include "spdk/util.h" +#include "spdk/string.h" #include "CUnit/Basic.h" @@ -52,6 +55,7 @@ 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; +static bool g_wait_for_tests = false; struct io_target { struct spdk_bdev *bdev; @@ -71,6 +75,7 @@ struct bdevio_request { struct io_target *g_io_targets = NULL; struct io_target *g_current_io_target = NULL; +static void rpc_perform_tests_cb(unsigned num_failures, struct spdk_jsonrpc_request *request); static void execute_spdk_function(spdk_event_fn fn, void *arg1, void *arg2) @@ -938,18 +943,24 @@ static void __stop_init_thread(void *arg1, void *arg2) { unsigned num_failures = (unsigned)(uintptr_t)arg1; + struct spdk_jsonrpc_request *request = arg2; bdevio_cleanup_targets(); + if (g_wait_for_tests) { + /* Do not stop the app yet, wait for another RPC */ + rpc_perform_tests_cb(num_failures, request); + return; + } spdk_app_stop(num_failures); } static void -stop_init_thread(unsigned num_failures) +stop_init_thread(unsigned num_failures, struct spdk_jsonrpc_request *request) { struct spdk_event *event; event = spdk_event_allocate(g_lcore_id_init, __stop_init_thread, - (void *)(uintptr_t)num_failures, NULL); + (void *)(uintptr_t)num_failures, request); spdk_event_call(event); } @@ -1032,6 +1043,7 @@ __setup_ut_on_single_target(struct io_target *target) static void __run_ut_thread(void *arg1, void *arg2) { + struct spdk_jsonrpc_request *request = arg2; int rc = 0; struct io_target *target; unsigned num_failures; @@ -1039,7 +1051,7 @@ __run_ut_thread(void *arg1, void *arg2) if (CU_initialize_registry() != CUE_SUCCESS) { /* CUnit error, probably won't recover */ rc = CU_get_error(); - stop_init_thread(-rc); + stop_init_thread(-rc, request); } target = g_io_targets; @@ -1047,7 +1059,7 @@ __run_ut_thread(void *arg1, void *arg2) rc = __setup_ut_on_single_target(target); if (rc < 0) { /* CUnit error, probably won't recover */ - stop_init_thread(-rc); + stop_init_thread(-rc, request); } target = target->next; } @@ -1056,7 +1068,7 @@ __run_ut_thread(void *arg1, void *arg2) num_failures = CU_get_number_of_failures(); CU_cleanup_registry(); - stop_init_thread(num_failures); + stop_init_thread(num_failures, request); } static void @@ -1078,6 +1090,11 @@ test_main(void *arg1) spdk_app_stop(-1); } + if (g_wait_for_tests) { + /* Do not perform any tests until RPC is received */ + return; + } + if (bdevio_construct_targets() < 0) { spdk_app_stop(-1); return; @@ -1090,14 +1107,104 @@ test_main(void *arg1) static void bdevio_usage(void) { + printf(" -w start bdevio app and wait for RPC to start the tests\n"); } static int bdevio_parse_arg(int ch, char *arg) { + switch (ch) { + case 'w': + g_wait_for_tests = true; + break; + default: + return -EINVAL; + } return 0; } +struct rpc_perform_tests { + char *name; +}; + +static void +free_rpc_perform_tests(struct rpc_perform_tests *r) +{ + free(r->name); +} + +static const struct spdk_json_object_decoder rpc_perform_tests_decoders[] = { + {"name", offsetof(struct rpc_perform_tests, name), spdk_json_decode_string, true}, +}; + +static void +rpc_perform_tests_cb(unsigned num_failures, struct spdk_jsonrpc_request *request) +{ + struct spdk_json_write_ctx *w; + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + spdk_json_write_uint32(w, num_failures); + spdk_jsonrpc_end_result(request, w); +} + +static void +rpc_perform_tests(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params) +{ + struct rpc_perform_tests req = {NULL}; + struct spdk_event *event; + struct spdk_bdev *bdev; + int rc; + + if (params && spdk_json_decode_object(params, rpc_perform_tests_decoders, + SPDK_COUNTOF(rpc_perform_tests_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + goto invalid; + } + + if (req.name) { + bdev = spdk_bdev_get_by_name(req.name); + if (bdev == NULL) { + SPDK_ERRLOG("Bdev '%s' does not exist\n", req.name); + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "Bdev '%s' does not exist: %s", + req.name, spdk_strerror(ENODEV)); + goto invalid; + } + rc = bdevio_construct_target(bdev); + if (rc < 0) { + SPDK_ERRLOG("Could not construct target for bdev '%s'\n", spdk_bdev_get_name(bdev)); + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "Could not construct target for bdev '%s': %s", + spdk_bdev_get_name(bdev), spdk_strerror(-rc)); + goto invalid; + } + } else { + rc = bdevio_construct_targets(); + if (rc < 0) { + SPDK_ERRLOG("Could not construct targets for all bdevs\n"); + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "Could not construct targets for all bdevs: %s", + spdk_strerror(-rc)); + goto invalid; + } + } + free_rpc_perform_tests(&req); + + event = spdk_event_allocate(g_lcore_id_ut, __run_ut_thread, NULL, request); + spdk_event_call(event); + + return; + +invalid: + free_rpc_perform_tests(&req); +} +SPDK_RPC_REGISTER("perform_tests", rpc_perform_tests, SPDK_RPC_RUNTIME) + int main(int argc, char **argv) { @@ -1108,7 +1215,7 @@ main(int argc, char **argv) opts.name = "bdevio"; opts.reactor_mask = "0x7"; - if ((rc = spdk_app_parse_args(argc, argv, &opts, "", NULL, + if ((rc = spdk_app_parse_args(argc, argv, &opts, "w", NULL, bdevio_parse_arg, bdevio_usage)) != SPDK_APP_PARSE_ARGS_SUCCESS) { return rc; diff --git a/test/bdev/bdevio/tests.py b/test/bdev/bdevio/tests.py new file mode 100755 index 000000000..e89a5fb04 --- /dev/null +++ b/test/bdev/bdevio/tests.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 + +from rpc.client import print_dict, JSONRPCException + +import logging +import argparse +import rpc +import sys +import shlex + +try: + from shlex import quote +except ImportError: + from pipes import quote + + +def print_array(a): + print(" ".join((quote(v) for v in a))) + + +def perform_tests_func(client, name=None): + """ + + Args: + name: bdev name to perform bdevio tests on (optional; if omitted, test all bdevs) + + Returns: + Number of failures in tests. 0 means no errors found. + """ + params = {} + if name: + params['name'] = name + return client.call('perform_tests', params) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description='SPDK RPC command line interface. NOTE: spdk/scripts/ is expected in PYTHONPATH') + parser.add_argument('-s', dest='server_addr', + help='RPC domain socket path or IP address', default='/var/tmp/spdk.sock') + parser.add_argument('-p', dest='port', + help='RPC port number (if server_addr is IP address)', + default=5260, type=int) + parser.add_argument('-t', dest='timeout', + help='Timeout as a floating point number expressed in seconds waiting for response. Default: 60.0', + default=60.0, type=float) + parser.add_argument('-v', dest='verbose', action='store_const', const="INFO", + help='Set verbose mode to INFO', default="ERROR") + parser.add_argument('--verbose', dest='verbose', choices=['DEBUG', 'INFO', 'ERROR'], + help="""Set verbose level. """) + subparsers = parser.add_subparsers(help='RPC methods') + + def perform_tests(args): + print_dict(perform_tests_func(args.client, name=args.name)) + + p = subparsers.add_parser('perform_tests', help='Perform all bdevio tests on select bdev') + p.add_argument('-b', '--name', help="Name of the Blockdev. Example: Nvme0n1") + p.set_defaults(func=perform_tests) + + def call_rpc_func(args): + try: + args.func(args) + except JSONRPCException as ex: + print(ex.message) + exit(1) + + def execute_script(parser, client, fd): + for rpc_call in map(str.rstrip, fd): + if not rpc_call.strip(): + continue + args = parser.parse_args(shlex.split(rpc_call)) + args.client = client + call_rpc_func(args) + + args = parser.parse_args() + args.client = rpc.client.JSONRPCClient(args.server_addr, args.port, args.timeout, log_level=getattr(logging, args.verbose.upper())) + if hasattr(args, 'func'): + call_rpc_func(args) + elif sys.stdin.isatty(): + # No arguments and no data piped through stdin + parser.print_help() + exit(1) + else: + execute_script(parser, args.client, sys.stdin)