diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/spdk/test/json_config | |
parent | Initial commit. (diff) | |
download | ceph-upstream.tar.xz ceph-upstream.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/spdk/test/json_config')
-rwxr-xr-x | src/spdk/test/json_config/alias_rpc/alias_rpc.sh | 20 | ||||
-rw-r--r-- | src/spdk/test/json_config/alias_rpc/conf.json | 44 | ||||
-rwxr-xr-x | src/spdk/test/json_config/clear_config.py | 215 | ||||
-rwxr-xr-x | src/spdk/test/json_config/config_filter.py | 96 | ||||
-rwxr-xr-x | src/spdk/test/json_config/json_config.sh | 475 | ||||
-rwxr-xr-x | src/spdk/test/json_config/json_diff.sh | 41 |
6 files changed, 891 insertions, 0 deletions
diff --git a/src/spdk/test/json_config/alias_rpc/alias_rpc.sh b/src/spdk/test/json_config/alias_rpc/alias_rpc.sh new file mode 100755 index 000000000..25e07fae4 --- /dev/null +++ b/src/spdk/test/json_config/alias_rpc/alias_rpc.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh + +trap 'killprocess $spdk_tgt_pid; exit 1' ERR + +$SPDK_BIN_DIR/spdk_tgt & +spdk_tgt_pid=$! +waitforlisten $spdk_tgt_pid + +# Test deprecated rpcs in json +$rootdir/scripts/rpc.py load_config -i < $testdir/conf.json + +# Test deprecated rpcs in rpc.py +$rootdir/scripts/rpc.py delete_malloc_bdev "Malloc0" +$rootdir/scripts/rpc.py delete_malloc_bdev "Malloc1" + +killprocess $spdk_tgt_pid diff --git a/src/spdk/test/json_config/alias_rpc/conf.json b/src/spdk/test/json_config/alias_rpc/conf.json new file mode 100644 index 000000000..ba3e6b254 --- /dev/null +++ b/src/spdk/test/json_config/alias_rpc/conf.json @@ -0,0 +1,44 @@ +{ + "subsystems": [ + { + "subsystem": "accel", + "config": [] + }, + { + "subsystem": "interface", + "config": null + }, + { + "subsystem": "net_framework", + "config": null + }, + { + "subsystem": "bdev", + "config": [ + { + "params": { + "block_size": 4096, + "num_blocks": 32 + }, + "method": "construct_malloc_bdev" + }, + { + "params": { + "name": "Malloc1", + "block_size": 4096, + "num_blocks": 32 + }, + "method": "construct_malloc_bdev" + } + ] + }, + { + "subsystem": "nbd", + "config": [] + }, + { + "subsystem": "scsi", + "config": null + } + ] +} 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 000000000..bac1beebb --- /dev/null +++ b/src/spdk/test/json_config/clear_config.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse +import logging +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'] == 'bdev_split_create': + 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'] == 'bdev_error_create': + bdev_name = "EE_%s" % bdev_name + return bdev_name + + +def get_bdev_delete_method(bdev): + delete_method_map = {'bdev_malloc_create': "bdev_malloc_delete", + 'bdev_null_create': "bdev_null_delete", + 'bdev_rbd_create': "bdev_rbd_delete", + 'bdev_pmem_create': "bdev_pmem_delete", + 'bdev_aio_create': "bdev_aio_delete", + 'bdev_error_create': "bdev_error_delete", + 'construct_split_vbdev': "destruct_split_vbdev", + 'bdev_virtio_attach_controller': "remove_virtio_bdev", + 'bdev_crypto_create': "bdev_crypto_delete", + 'bdev_delay_create': "bdev_delay_delete", + 'bdev_passthru_create': "bdev_passthru_delete", + 'bdev_compress_create': 'bdev_compress_delete', + } + destroy_method = None + if 'method' in bdev: + construct_method = bdev['method'] + if construct_method in list(delete_method_map.keys()): + destroy_method = delete_method_map[construct_method] + + return destroy_method + + +def clear_bdev_subsystem(args, bdev_config): + rpc_bdevs = args.client.call("bdev_get_bdevs") + for bdev in bdev_config: + bdev_name_key = get_bdev_name_key(bdev) + bdev_name = get_bdev_name(bdev) + destroy_method = get_bdev_delete_method(bdev) + if destroy_method: + args.client.call(destroy_method, {bdev_name_key: bdev_name}) + + nvme_controllers = args.client.call("bdev_nvme_get_controllers") + for ctrlr in nvme_controllers: + args.client.call('bdev_nvme_detach_controller', {'name': ctrlr['name']}) + + ''' Disable and reset hotplug ''' + rpc.bdev.bdev_nvme_set_hotplug(args.client, False) + + +def get_nvmf_destroy_method(nvmf): + delete_method_map = {'nvmf_create_subsystem': "nvmf_delete_subsystem"} + try: + return delete_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): + delete_method_map = {'iscsi_create_portal_group': "iscsi_delete_portal_group", + 'iscsi_create_initiator_group': "iscsi_delete_initiator_group", + 'iscsi_create_target_node': "iscsi_delete_target_node", + 'iscsi_set_options': None + } + return delete_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'] == 'iscsi_create_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): + delete_method_map = {'nbd_start_disk': "nbd_stop_disk" + } + return delete_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_accel_subsystem(args, accel_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 ['vhost_scsi_controller_add_target']: + args.client.call("vhost_scsi_controller_remove_target", + {"ctrlr": vhost['params']['ctrlr'], + "scsi_target_num": vhost['params']['scsi_target_num']}) + elif method in ['vhost_create_scsi_controller', 'vhost_create_blk_controller', + 'vhost_create_nvme_controller']: + args.client.call("vhost_delete_controller", {'ctrlr': vhost['params']['ctrlr']}) + + +def clear_vmd_subsystem(args, vmd_config): + pass + + +def clear_sock_subsystem(args, sock_config): + pass + + +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_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') + + @call_test_cmd + def clear_config(args): + for subsystem_item in reversed(args.client.call('framework_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('framework_get_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() + + with rpc.client.JSONRPCClient(args.server_addr, args.port, args.timeout, log_level=getattr(logging, args.verbose.upper())) as client: + try: + args.client = client + args.func(args) + except JSONRPCException as ex: + print((ex.message)) + 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 000000000..cde2e24f9 --- /dev/null +++ b/src/spdk/test/json_config/config_filter.py @@ -0,0 +1,96 @@ +#!/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 = [ + 'idxd_scan_accel_engine', + 'iscsi_set_options', + 'nvmf_set_config', + 'nvmf_set_max_subsystems', + 'nvmf_create_transport', + 'bdev_set_options', + 'bdev_nvme_set_options', + 'bdev_nvme_set_hotplug', + 'sock_impl_set_options', + ] + + 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)) + + +def check_empty(): + data = json.loads(sys.stdin.read()) + if not data: + raise EOFError("Cant read config!") + + for s in data['subsystems']: + if s['config']: + print("Config not empty") + print(s['config']) + sys.exit(1) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('-method', dest='method', default=None, + help="""One of the methods: +check_empty + check if provided configuration is logically empty +delete_global_parameters + remove pre-init configuration (pre framework_start_init RPC methods) +delete_configs + remove post-init configuration (post framework_start_init RPC methods) +sort + remove nothing - just sort JSON objects (and subobjects but not arrays) + in lexicographical order. This can be used to do plain text diff.""") + + args = parser.parse_args() + if args.method == "delete_global_parameters": + filter_methods(True) + elif args.method == "delete_configs": + filter_methods(False) + elif args.method == "check_empty": + check_empty() + elif args.method == "sort": + """ Wrap input into JSON object so any input is possible here + like output from bdev_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 '{}'\n\n{}".format(args.method, parser.format_help())) diff --git a/src/spdk/test/json_config/json_config.sh b/src/spdk/test/json_config/json_config.sh new file mode 100755 index 000000000..03d6bd5bd --- /dev/null +++ b/src/spdk/test/json_config/json_config.sh @@ -0,0 +1,475 @@ +#!/usr/bin/env bash + +rootdir=$(readlink -f $(dirname $0)/../..) +source "$rootdir/test/common/autotest_common.sh" +source "$rootdir/test/nvmf/common.sh" + +if [[ $SPDK_TEST_ISCSI -eq 1 ]]; then + source "$rootdir/test/iscsi_tgt/common.sh" +fi + +if [[ $SPDK_TEST_VHOST -ne 1 && $SPDK_TEST_VHOST_INIT -eq 1 ]]; then + SPDK_TEST_VHOST=1 + echo "WARNING: Virtio initiator JSON_config test requires vhost target." + echo " Setting SPDK_TEST_VHOST=1 for duration of current script." +fi + +if ((SPDK_TEST_BLOCKDEV + \ + SPDK_TEST_ISCSI + \ + SPDK_TEST_NVMF + \ + SPDK_TEST_VHOST + \ + SPDK_TEST_VHOST_INIT + \ + SPDK_TEST_PMDK + \ + SPDK_TEST_RBD == 0)); then + echo "WARNING: No tests are enabled so not running JSON configuration tests" + exit 0 +fi + +declare -A app_pid=([target]="" [initiator]="") +declare -A app_socket=([target]='/var/tmp/spdk_tgt.sock' [initiator]='/var/tmp/spdk_initiator.sock') +declare -A app_params=([target]='-m 0x1 -s 1024' [initiator]='-m 0x2 -g -u -s 1024') +declare -A configs_path=([target]="$rootdir/spdk_tgt_config.json" [initiator]="$rootdir/spdk_initiator_config.json") + +function tgt_rpc() { + $rootdir/scripts/rpc.py -s "${app_socket[target]}" "$@" +} + +function initiator_rpc() { + $rootdir/scripts/rpc.py -s "${app_socket[initiator]}" "$@" +} + +RE_UUID="[[:alnum:]-]+" +last_event_id=0 + +function tgt_check_notification_types() { + timing_enter "${FUNCNAME[0]}" + + local ret=0 + local enabled_types=("bdev_register" "bdev_unregister") + + local get_types=($(tgt_rpc notify_get_types | jq -r '.[]')) + if [[ ${enabled_types[*]} != "${get_types[*]}" ]]; then + echo "ERROR: expected types: ${enabled_types[*]}, but got: ${get_types[*]}" + ret=1 + fi + + timing_exit "${FUNCNAME[0]}" + return $ret +} + +function tgt_check_notifications() { + local event_line event ev_type ev_ctx + local rc="" + + while read -r event_line; do + # remove ID + event="${event_line%:*}" + + ev_type=${event%:*} + ev_ctx=${event#*:} + + ex_ev_type=${1%%:*} + ex_ev_ctx=${1#*:} + + last_event_id=${event_line##*:} + + # set rc=false in case of failure so all errors can be printed + if (($# == 0)); then + echo "ERROR: got extra event: $event_line" + rc=false + continue + elif ! echo "$ev_type" | grep -E -q "^${ex_ev_type}\$" || ! echo "$ev_ctx" | grep -E -q "^${ex_ev_ctx}\$"; then + echo "ERROR: expected event '$1' but got '$event' (whole event line: $event_line)" + rc=false + fi + + shift + done < <(tgt_rpc notify_get_notifications -i ${last_event_id} | jq -r '.[] | "\(.type):\(.ctx):\(.id)"') + + $rc + + if (($# != 0)); then + echo "ERROR: missing events:" + echo "$@" + return 1 + fi +} + +# $1 - target / initiator +# $2..$n app parameters +function json_config_test_start_app() { + local app=$1 + shift + + [[ -n "${#app_socket[$app]}" ]] # Check app type + [[ -z "${app_pid[$app]}" ]] # Assert if app is not running + + local app_extra_params="" + if [[ $SPDK_TEST_VHOST -eq 1 || $SPDK_TEST_VHOST_INIT -eq 1 ]]; then + # If PWD is nfs/sshfs we can't create UNIX sockets there. Always use safe location instead. + app_extra_params='-S /var/tmp' + fi + + $SPDK_BIN_DIR/spdk_tgt ${app_params[$app]} ${app_extra_params} -r ${app_socket[$app]} "$@" & + app_pid[$app]=$! + + echo "Waiting for $app to run..." + waitforlisten ${app_pid[$app]} ${app_socket[$app]} + echo "" +} + +# $1 - target / initiator +function json_config_test_shutdown_app() { + local app=$1 + + # Check app type && assert app was started + [[ -n "${#app_socket[$app]}" ]] + [[ -n "${app_pid[$app]}" ]] + + # spdk_kill_instance RPC will trigger ASAN + kill -SIGINT ${app_pid[$app]} + + for ((i = 0; i < 30; i++)); do + if ! kill -0 ${app_pid[$app]} 2> /dev/null; then + app_pid[$app]= + break + fi + sleep 0.5 + done + + if [[ -n "${app_pid[$app]}" ]]; then + echo "SPDK $app shutdown timeout" + return 1 + fi + + echo "SPDK $app shutdown done" +} + +function create_bdev_subsystem_config() { + timing_enter "${FUNCNAME[0]}" + + local expected_notifications=() + + if [[ $SPDK_TEST_BLOCKDEV -eq 1 ]]; then + local lvol_store_base_bdev=Nvme0n1 + if ! tgt_rpc get_bdevs --name ${lvol_store_base_bdev} > /dev/null; then + if [[ $(uname -s) = Linux ]]; then + lvol_store_base_bdev=aio_disk + echo "WARNING: No NVMe drive found. Using '$lvol_store_base_bdev' instead." + else + echo "ERROR: No NVMe drive found and bdev_aio is not supported on $(uname -s)." + timing_exit "${FUNCNAME[0]}" + return 1 + fi + fi + + tgt_rpc bdev_split_create $lvol_store_base_bdev 2 + tgt_rpc bdev_split_create Malloc0 3 + tgt_rpc bdev_malloc_create 8 4096 --name Malloc3 + tgt_rpc bdev_passthru_create -b Malloc3 -p PTBdevFromMalloc3 + + tgt_rpc bdev_null_create Null0 32 512 + + tgt_rpc bdev_malloc_create 32 512 --name Malloc0 + tgt_rpc bdev_malloc_create 16 4096 --name Malloc1 + + expected_notifications+=( + bdev_register:${lvol_store_base_bdev} + bdev_register:${lvol_store_base_bdev}p0 + bdev_register:${lvol_store_base_bdev}p1 + bdev_register:Malloc3 + bdev_register:PTBdevFromMalloc3 + bdev_register:Null0 + bdev_register:Malloc0p0 + bdev_register:Malloc0p1 + bdev_register:Malloc0p2 + bdev_register:Malloc0 + bdev_register:Malloc1 + ) + + if [[ $(uname -s) = Linux ]]; then + # This AIO bdev must be large enough to be used as LVOL store + dd if=/dev/zero of="$SPDK_TEST_STORAGE/sample_aio" bs=1024 count=102400 + tgt_rpc bdev_aio_create "$SPDK_TEST_STORAGE/sample_aio" aio_disk 1024 + expected_notifications+=(bdev_register:aio_disk) + fi + + # For LVOLs use split to check for proper order of initialization. + # If LVOLs cofniguration will be reordered (eg moved before splits or AIO/NVMe) + # it should fail loading JSON config from file. + tgt_rpc bdev_lvol_create_lvstore -c 1048576 ${lvol_store_base_bdev}p0 lvs_test + tgt_rpc bdev_lvol_create -l lvs_test lvol0 32 + tgt_rpc bdev_lvol_create -l lvs_test -t lvol1 32 + tgt_rpc bdev_lvol_snapshot lvs_test/lvol0 snapshot0 + tgt_rpc bdev_lvol_clone lvs_test/snapshot0 clone0 + + expected_notifications+=( + "bdev_register:$RE_UUID" + "bdev_register:$RE_UUID" + "bdev_register:$RE_UUID" + "bdev_register:$RE_UUID" + ) + fi + + if [[ $SPDK_TEST_CRYPTO -eq 1 ]]; then + tgt_rpc bdev_malloc_create 8 1024 --name MallocForCryptoBdev + if [[ $(lspci -d:37c8 | wc -l) -eq 0 ]]; then + local crypto_dirver=crypto_aesni_mb + else + local crypto_dirver=crypto_qat + fi + + tgt_rpc bdev_crypto_create MallocForCryptoBdev CryptoMallocBdev $crypto_dirver 0123456789123456 + expected_notifications+=( + bdev_register:MallocForCryptoBdev + bdev_register:CryptoMallocBdev + ) + fi + + if [[ $SPDK_TEST_PMDK -eq 1 ]]; then + pmem_pool_file=$(mktemp /tmp/pool_file1.XXXXX) + rm -f $pmem_pool_file + tgt_rpc create_pmem_pool $pmem_pool_file 128 4096 + tgt_rpc bdev_pmem_create -n pmem1 $pmem_pool_file + expected_notifications+=(bdev_register:pmem1) + fi + + if [[ $SPDK_TEST_RBD -eq 1 ]]; then + rbd_setup 127.0.0.1 + tgt_rpc bdev_rbd_create $RBD_POOL $RBD_NAME 4096 + expected_notifications+=(bdev_register:Ceph0) + fi + + tgt_check_notifications "${expected_notifications[@]}" + + timing_exit "${FUNCNAME[0]}" +} + +function cleanup_bdev_subsystem_config() { + timing_enter "${FUNCNAME[0]}" + + if [[ $SPDK_TEST_BLOCKDEV -eq 1 ]]; then + tgt_rpc bdev_lvol_delete lvs_test/clone0 + tgt_rpc bdev_lvol_delete lvs_test/lvol0 + tgt_rpc bdev_lvol_delete lvs_test/snapshot0 + tgt_rpc bdev_lvol_delete_lvstore -l lvs_test + fi + + if [[ $(uname -s) = Linux ]]; then + rm -f "$SPDK_TEST_STORAGE/sample_aio" + fi + + if [[ $SPDK_TEST_PMDK -eq 1 && -n "$pmem_pool_file" && -f "$pmem_pool_file" ]]; then + tgt_rpc bdev_pmem_delete pmem1 + tgt_rpc bdev_pmem_delete_pool $pmem_pool_file + rm -f $pmem_pool_file + fi + + if [[ $SPDK_TEST_RBD -eq 1 ]]; then + rbd_cleanup + fi + + timing_exit "${FUNCNAME[0]}" +} + +function create_vhost_subsystem_config() { + timing_enter "${FUNCNAME[0]}" + + tgt_rpc bdev_malloc_create 64 1024 --name MallocForVhost0 + tgt_rpc bdev_split_create MallocForVhost0 8 + + tgt_rpc vhost_create_scsi_controller VhostScsiCtrlr0 + tgt_rpc vhost_scsi_controller_add_target VhostScsiCtrlr0 0 MallocForVhost0p3 + tgt_rpc vhost_scsi_controller_add_target VhostScsiCtrlr0 -1 MallocForVhost0p4 + tgt_rpc vhost_controller_set_coalescing VhostScsiCtrlr0 1 100 + + tgt_rpc vhost_create_blk_controller VhostBlkCtrlr0 MallocForVhost0p5 + + # FIXME: enable after vhost-nvme is properly implemented against the latest rte_vhost (DPDK 19.05+) + # tgt_rpc vhost_create_nvme_controller VhostNvmeCtrlr0 16 + # tgt_rpc vhost_nvme_controller_add_ns VhostNvmeCtrlr0 MallocForVhost0p6 + + timing_exit "${FUNCNAME[0]}" +} + +function create_iscsi_subsystem_config() { + timing_enter "${FUNCNAME[0]}" + tgt_rpc bdev_malloc_create 64 1024 --name MallocForIscsi0 + tgt_rpc iscsi_create_portal_group $PORTAL_TAG 127.0.0.1:$ISCSI_PORT + tgt_rpc iscsi_create_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK + tgt_rpc iscsi_create_target_node Target3 Target3_alias 'MallocForIscsi0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d + timing_exit "${FUNCNAME[0]}" +} + +function create_nvmf_subsystem_config() { + timing_enter "${FUNCNAME[0]}" + + 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 + + tgt_rpc bdev_malloc_create 8 512 --name MallocForNvmf0 + tgt_rpc bdev_malloc_create 4 1024 --name MallocForNvmf1 + + tgt_rpc nvmf_create_transport -t RDMA -u 8192 -c 0 + tgt_rpc nvmf_create_subsystem nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 + tgt_rpc nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 MallocForNvmf0 + tgt_rpc nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 MallocForNvmf1 + tgt_rpc nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_FIRST_TARGET_IP -s "$NVMF_PORT" + + timing_exit "${FUNCNAME[0]}" +} + +function create_virtio_initiator_config() { + timing_enter "${FUNCNAME[0]}" + initiator_rpc bdev_virtio_attach_controller -t user -a /var/tmp/VhostScsiCtrlr0 -d scsi VirtioScsiCtrlr0 + initiator_rpc bdev_virtio_attach_controller -t user -a /var/tmp/VhostBlkCtrlr0 -d blk VirtioBlk0 + # TODO: initiator_rpc bdev_virtio_attach_controller -t user -a /var/tmp/VhostNvmeCtrlr0 -d nvme VirtioNvme0 + timing_exit "${FUNCNAME[0]}" +} + +function json_config_test_init() { + timing_enter "${FUNCNAME[0]}" + timing_enter json_config_setup_target + + json_config_test_start_app target --wait-for-rpc + + #TODO: global subsystem params + + # Load nvme configuration. The load_config will issue framework_start_init automatically + ( + echo '{"subsystems": [' + $rootdir/scripts/gen_nvme.sh --json | jq -r "del(.config[] | select(.params.name!=\"Nvme0\"))" + echo ']}' + ) | tgt_rpc load_config + + tgt_check_notification_types + + if [[ $SPDK_TEST_BLOCKDEV -eq 1 ]]; then + create_bdev_subsystem_config + fi + + if [[ $SPDK_TEST_VHOST -eq 1 ]]; then + create_vhost_subsystem_config + fi + + if [[ $SPDK_TEST_ISCSI -eq 1 ]]; then + create_iscsi_subsystem_config + fi + + if [[ $SPDK_TEST_NVMF -eq 1 ]]; then + create_nvmf_subsystem_config + fi + timing_exit json_config_setup_target + + if [[ $SPDK_TEST_VHOST_INIT -eq 1 ]]; then + json_config_test_start_app initiator + create_virtio_initiator_config + fi + + tgt_rpc bdev_malloc_create 8 512 --name MallocBdevForConfigChangeCheck + + timing_exit "${FUNCNAME[0]}" +} + +function json_config_test_fini() { + timing_enter "${FUNCNAME[0]}" + local ret=0 + + if [[ -n "${app_pid[initiator]}" ]]; then + killprocess ${app_pid[initiator]} + fi + + if [[ -n "${app_pid[target]}" ]]; then + + # Remove any artifacts we created (files, lvol etc) + cleanup_bdev_subsystem_config + + # SPDK_TEST_NVMF: Should we clear something? + killprocess ${app_pid[target]} + fi + + rm -f "${configs_path[@]}" + timing_exit "${FUNCNAME[0]}" + return $ret +} + +function json_config_clear() { + [[ -n "${#app_socket[$1]}" ]] # Check app type + $rootdir/test/json_config/clear_config.py -s ${app_socket[$1]} clear_config + + # Check if config is clean. + # Global params can't be cleared so need to filter them out. + local config_filter="$rootdir/test/json_config/config_filter.py" + + # RPC's used to cleanup configuration (e.g. to delete split and nvme bdevs) + # complete immediately and they don't wait for the unregister callback. + # It causes that configuration may not be fully cleaned at this moment and + # we should to wait a while. (See github issue #789) + count=100 + while [ $count -gt 0 ]; do + $rootdir/scripts/rpc.py -s "${app_socket[$1]}" save_config | $config_filter -method delete_global_parameters | $config_filter -method check_empty && break + count=$((count - 1)) + sleep 0.1 + done + + if [ $count -eq 0 ]; then + return 1 + fi +} + +on_error_exit() { + set -x + set +e + print_backtrace + trap - ERR + echo "Error on $1 - $2" + json_config_test_fini + exit 1 +} + +trap 'on_error_exit "${FUNCNAME}" "${LINENO}"' ERR +echo "INFO: JSON configuration test init" +json_config_test_init + +tgt_rpc save_config > ${configs_path[target]} + +echo "INFO: shutting down applications..." +if [[ $SPDK_TEST_VHOST_INIT -eq 1 ]]; then + initiator_rpc save_config > ${configs_path[initiator]} + json_config_clear initiator + json_config_test_shutdown_app initiator +fi + +json_config_clear target +json_config_test_shutdown_app target + +echo "INFO: relaunching applications..." +json_config_test_start_app target --json ${configs_path[target]} +if [[ $SPDK_TEST_VHOST_INIT -eq 1 ]]; then + json_config_test_start_app initiator --json ${configs_path[initiator]} +fi + +echo "INFO: Checking if target configuration is the same..." +$rootdir/test/json_config/json_diff.sh <(tgt_rpc save_config) "${configs_path[target]}" +if [[ $SPDK_TEST_VHOST_INIT -eq 1 ]]; then + echo "INFO: Checking if virtio initiator configuration is the same..." + $rootdir/test/json_config/json_diff.sh <(initiator_rpc save_config) "${configs_path[initiator]}" +fi + +echo "INFO: changing configuration and checking if this can be detected..." +# Self test to check if configuration diff can be detected. +tgt_rpc bdev_malloc_delete MallocBdevForConfigChangeCheck +if $rootdir/test/json_config/json_diff.sh <(tgt_rpc save_config) "${configs_path[target]}" > /dev/null; then + echo "ERROR: intentional configuration difference not detected!" + false +else + echo "INFO: configuration change detected." +fi + +json_config_test_fini + +echo "INFO: Success" diff --git a/src/spdk/test/json_config/json_diff.sh b/src/spdk/test/json_config/json_diff.sh new file mode 100755 index 000000000..e6b9e223d --- /dev/null +++ b/src/spdk/test/json_config/json_diff.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -x + +if [ $# -ne 2 ]; then + echo "This script need exactly two arguments" + exit 1 +fi + +rootdir=$(readlink -f $(dirname $0)/../..) + +# 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. +# + +tmp_file_1=$(mktemp /tmp/$(basename ${1}).XXX) +tmp_file_2=$(mktemp /tmp/$(basename ${2}).XXX) +ret=0 + +$rootdir/test/json_config/config_filter.py -method "sort" < $1 > $tmp_file_1 +$rootdir/test/json_config/config_filter.py -method "sort" < $2 > $tmp_file_2 + +if ! diff -u $tmp_file_1 $tmp_file_2; then + ret=1 + + echo "=== Start of file: $tmp_file_1 ===" + cat $tmp_file_1 + echo "=== End of file: $tmp_file_1 ===" + echo "" + echo "=== Start of file: $tmp_file_2 ===" + cat $tmp_file_2 + echo "=== End of file: $tmp_file_2 ===" + echo "" +else + echo "INFO: JSON config files are the same" +fi + +rm $tmp_file_1 $tmp_file_2 +exit $ret |