summaryrefslogtreecommitdiffstats
path: root/src/spdk/test/json_config
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
commite6918187568dbd01842d8d1d2c808ce16a894239 (patch)
tree64f88b554b444a49f656b6c656111a145cbbaa28 /src/spdk/test/json_config
parentInitial commit. (diff)
downloadceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz
ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/spdk/test/json_config')
-rwxr-xr-xsrc/spdk/test/json_config/alias_rpc/alias_rpc.sh20
-rw-r--r--src/spdk/test/json_config/alias_rpc/conf.json44
-rwxr-xr-xsrc/spdk/test/json_config/clear_config.py215
-rwxr-xr-xsrc/spdk/test/json_config/config_filter.py96
-rwxr-xr-xsrc/spdk/test/json_config/json_config.sh475
-rwxr-xr-xsrc/spdk/test/json_config/json_diff.sh41
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