From 19fcec84d8d7d21e796c7624e521b60d28ee21ed Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:45:59 +0200 Subject: Adding upstream version 16.2.11+ds. Signed-off-by: Daniel Baumann --- src/spdk/test/Makefile | 48 + src/spdk/test/app/Makefile | 44 + src/spdk/test/app/bdev_svc/.gitignore | 1 + src/spdk/test/app/bdev_svc/Makefile | 63 + src/spdk/test/app/bdev_svc/bdev_svc.c | 112 + src/spdk/test/app/fuzz/Makefile | 48 + src/spdk/test/app/fuzz/common/fuzz_common.h | 303 + src/spdk/test/app/fuzz/common/fuzz_rpc.py | 106 + src/spdk/test/app/fuzz/iscsi_fuzz/.gitignore | 1 + src/spdk/test/app/fuzz/iscsi_fuzz/Makefile | 51 + src/spdk/test/app/fuzz/iscsi_fuzz/README.md | 27 + src/spdk/test/app/fuzz/iscsi_fuzz/iscsi_fuzz.c | 1092 ++++ src/spdk/test/app/fuzz/nvme_fuzz/.gitignore | 1 + src/spdk/test/app/fuzz/nvme_fuzz/Makefile | 49 + src/spdk/test/app/fuzz/nvme_fuzz/README.md | 52 + src/spdk/test/app/fuzz/nvme_fuzz/example.json | 290 + src/spdk/test/app/fuzz/nvme_fuzz/nvme_fuzz.c | 931 +++ src/spdk/test/app/fuzz/vhost_fuzz/.gitignore | 1 + src/spdk/test/app/fuzz/vhost_fuzz/Makefile | 42 + src/spdk/test/app/fuzz/vhost_fuzz/README.md | 46 + src/spdk/test/app/fuzz/vhost_fuzz/example.json | 95 + src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.c | 1146 ++++ src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.h | 41 + src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz_rpc.c | 108 + src/spdk/test/app/histogram_perf/.gitignore | 1 + src/spdk/test/app/histogram_perf/Makefile | 43 + src/spdk/test/app/histogram_perf/histogram_perf.c | 102 + src/spdk/test/app/jsoncat/.gitignore | 1 + src/spdk/test/app/jsoncat/Makefile | 43 + src/spdk/test/app/jsoncat/jsoncat.c | 192 + src/spdk/test/app/match/match | 332 + src/spdk/test/app/stub/.gitignore | 1 + src/spdk/test/app/stub/Makefile | 49 + src/spdk/test/app/stub/stub.c | 203 + src/spdk/test/bdev/Makefile | 44 + src/spdk/test/bdev/bdev_raid.sh | 119 + src/spdk/test/bdev/bdevio/.gitignore | 1 + src/spdk/test/bdev/bdevio/Makefile | 48 + src/spdk/test/bdev/bdevio/bdevio.c | 1433 +++++ src/spdk/test/bdev/bdevio/tests.py | 88 + src/spdk/test/bdev/bdevperf/.gitignore | 1 + src/spdk/test/bdev/bdevperf/Makefile | 55 + src/spdk/test/bdev/bdevperf/bdevperf.c | 2137 +++++++ src/spdk/test/bdev/bdevperf/bdevperf.py | 86 + src/spdk/test/bdev/bdevperf/common.sh | 33 + src/spdk/test/bdev/bdevperf/conf.json | 25 + src/spdk/test/bdev/bdevperf/test_config.sh | 41 + src/spdk/test/bdev/blockdev.sh | 408 ++ src/spdk/test/bdev/nbd_common.sh | 123 + src/spdk/test/blobfs/Makefile | 45 + src/spdk/test/blobfs/blobfs.sh | 141 + src/spdk/test/blobfs/fuse/.gitignore | 1 + src/spdk/test/blobfs/fuse/Makefile | 50 + src/spdk/test/blobfs/fuse/fuse.c | 114 + src/spdk/test/blobfs/mkfs/.gitignore | 1 + src/spdk/test/blobfs/mkfs/Makefile | 52 + src/spdk/test/blobfs/mkfs/mkfs.c | 115 + src/spdk/test/blobfs/rocksdb/.gitignore | 1 + src/spdk/test/blobfs/rocksdb/common_flags.txt | 27 + src/spdk/test/blobfs/rocksdb/postprocess.py | 70 + src/spdk/test/blobfs/rocksdb/rocksdb.sh | 155 + src/spdk/test/blobfs/rocksdb/rocksdb_commit_id | 1 + .../test/blobstore/blob_io_wait/blob_io_wait.sh | 61 + src/spdk/test/blobstore/blobstore.sh | 30 + src/spdk/test/blobstore/btest.out.ignore | 5 + src/spdk/test/blobstore/btest.out.match | 90 + src/spdk/test/blobstore/test.bs | 12 + src/spdk/test/common/applications.sh | 24 + src/spdk/test/common/autotest_common.sh | 1350 ++++ src/spdk/test/common/config/README.md | 104 + src/spdk/test/common/config/pkgdep/apt-get | 100 + src/spdk/test/common/config/pkgdep/dnf | 72 + src/spdk/test/common/config/pkgdep/git | 325 + src/spdk/test/common/config/pkgdep/pacman | 62 + src/spdk/test/common/config/pkgdep/pkg | 27 + src/spdk/test/common/config/pkgdep/swupd | 21 + src/spdk/test/common/config/pkgdep/yum | 67 + src/spdk/test/common/config/vm_setup.conf | 12 + src/spdk/test/common/config/vm_setup.sh | 176 + src/spdk/test/common/lib/nvme/common_stubs.h | 117 + src/spdk/test/common/lib/test_env.c | 637 ++ src/spdk/test/common/lib/test_rdma.c | 49 + src/spdk/test/common/lib/test_sock.c | 70 + src/spdk/test/common/lib/ut_multithread.c | 214 + src/spdk/test/common/skipped_build_files.txt | 60 + src/spdk/test/common/skipped_tests.txt | 73 + src/spdk/test/compress/compress.sh | 119 + src/spdk/test/config_converter/config.ini | 153 + src/spdk/test/config_converter/config_virtio.ini | 21 + src/spdk/test/config_converter/spdk_config.json | 526 ++ .../test/config_converter/spdk_config_virtio.json | 133 + src/spdk/test/config_converter/test_converter.sh | 26 + src/spdk/test/cpp_headers/.gitignore | 1 + src/spdk/test/cpp_headers/Makefile | 59 + src/spdk/test/dd/basic_rw.sh | 107 + src/spdk/test/dd/bdev_to_bdev.sh | 111 + src/spdk/test/dd/common.sh | 154 + src/spdk/test/dd/dd.sh | 13 + src/spdk/test/dd/posix.sh | 122 + .../test/dpdk_memory_utility/test_dpdk_mem_info.sh | 25 + src/spdk/test/env/Makefile | 50 + src/spdk/test/env/env.sh | 27 + src/spdk/test/env/env_dpdk_post_init/.gitignore | 1 + src/spdk/test/env/env_dpdk_post_init/Makefile | 39 + .../env/env_dpdk_post_init/env_dpdk_post_init.c | 126 + src/spdk/test/env/mem_callbacks/.gitignore | 1 + src/spdk/test/env/mem_callbacks/Makefile | 41 + src/spdk/test/env/mem_callbacks/mem_callbacks.c | 217 + src/spdk/test/env/memory/.gitignore | 1 + src/spdk/test/env/memory/Makefile | 40 + src/spdk/test/env/memory/memory_ut.c | 524 ++ src/spdk/test/env/pci/.gitignore | 1 + src/spdk/test/env/pci/Makefile | 40 + src/spdk/test/env/pci/pci_ut.c | 238 + src/spdk/test/env/vtophys/.gitignore | 1 + src/spdk/test/env/vtophys/Makefile | 39 + src/spdk/test/env/vtophys/vtophys.c | 196 + src/spdk/test/event/Makefile | 48 + src/spdk/test/event/app_repeat/.gitignore | 1 + src/spdk/test/event/app_repeat/Makefile | 54 + src/spdk/test/event/app_repeat/app_repeat.c | 115 + src/spdk/test/event/event.sh | 44 + src/spdk/test/event/event_perf/.gitignore | 1 + src/spdk/test/event/event_perf/Makefile | 42 + src/spdk/test/event/event_perf/event_perf.c | 184 + src/spdk/test/event/reactor/.gitignore | 1 + src/spdk/test/event/reactor/Makefile | 42 + src/spdk/test/event/reactor/reactor.c | 144 + src/spdk/test/event/reactor_perf/.gitignore | 1 + src/spdk/test/event/reactor_perf/Makefile | 42 + src/spdk/test/event/reactor_perf/reactor_perf.c | 151 + src/spdk/test/external_code/Makefile | 80 + src/spdk/test/external_code/README.md | 17 + src/spdk/test/external_code/hello_world/.gitignore | 1 + src/spdk/test/external_code/hello_world/Makefile | 73 + src/spdk/test/external_code/hello_world/bdev.conf | 17 + .../external_code/hello_world/bdev_external.conf | 24 + .../test/external_code/hello_world/hello_bdev.c | 300 + src/spdk/test/external_code/passthru/Makefile | 43 + .../test/external_code/passthru/vbdev_passthru.c | 748 +++ .../test/external_code/passthru/vbdev_passthru.h | 65 + .../external_code/passthru/vbdev_passthru_rpc.c | 142 + src/spdk/test/external_code/test_make.sh | 63 + src/spdk/test/ftl/bdevperf.sh | 31 + src/spdk/test/ftl/common.sh | 68 + src/spdk/test/ftl/config/.gitignore | 2 + src/spdk/test/ftl/config/fio/drive-prep.fio | 15 + src/spdk/test/ftl/config/fio/randr.fio | 19 + src/spdk/test/ftl/config/fio/randrw.fio | 20 + .../test/ftl/config/fio/randw-verify-depth128.fio | 20 + src/spdk/test/ftl/config/fio/randw-verify-j2.fio | 25 + .../test/ftl/config/fio/randw-verify-qd128-ext.fio | 20 + src/spdk/test/ftl/config/fio/randw-verify.fio | 20 + src/spdk/test/ftl/config/fio/randw.fio | 18 + src/spdk/test/ftl/dirty_shutdown.sh | 93 + src/spdk/test/ftl/fio.sh | 68 + src/spdk/test/ftl/ftl.sh | 80 + src/spdk/test/ftl/json.sh | 38 + src/spdk/test/ftl/restore.sh | 99 + src/spdk/test/fuzz/autofuzz.sh | 74 + src/spdk/test/fuzz/autofuzz_iscsi.sh | 75 + src/spdk/test/fuzz/autofuzz_nvmf.sh | 52 + src/spdk/test/fuzz/autofuzz_vhost.sh | 75 + src/spdk/test/ioat/ioat.sh | 9 + .../test/iscsi_tgt/bdev_io_wait/bdev_io_wait.sh | 50 + src/spdk/test/iscsi_tgt/calsoft/calsoft.py | 121 + src/spdk/test/iscsi_tgt/calsoft/calsoft.sh | 63 + src/spdk/test/iscsi_tgt/calsoft/iscsi.json | 15 + src/spdk/test/iscsi_tgt/calsoft/its.conf | 7 + src/spdk/test/iscsi_tgt/common.sh | 209 + src/spdk/test/iscsi_tgt/digests/digests.sh | 94 + src/spdk/test/iscsi_tgt/ext4test/ext4test.sh | 131 + src/spdk/test/iscsi_tgt/filesystem/filesystem.sh | 145 + src/spdk/test/iscsi_tgt/fio/fio.sh | 150 + src/spdk/test/iscsi_tgt/fio/iscsi.json | 32 + src/spdk/test/iscsi_tgt/fuzz/fuzz.sh | 65 + src/spdk/test/iscsi_tgt/initiator/initiator.sh | 52 + .../test/iscsi_tgt/ip_migration/ip_migration.sh | 131 + src/spdk/test/iscsi_tgt/iscsi_tgt.sh | 97 + src/spdk/test/iscsi_tgt/lvol/iscsi_lvol.sh | 85 + .../iscsi_tgt/multiconnection/multiconnection.sh | 84 + .../test/iscsi_tgt/nvme_remote/fio_remote_nvme.sh | 99 + src/spdk/test/iscsi_tgt/perf/iscsi_initiator.sh | 37 + src/spdk/test/iscsi_tgt/perf/iscsi_target.sh | 134 + src/spdk/test/iscsi_tgt/perf/perf.job | 19 + src/spdk/test/iscsi_tgt/pmem/iscsi_pmem.sh | 74 + src/spdk/test/iscsi_tgt/qos/qos.sh | 145 + src/spdk/test/iscsi_tgt/rbd/rbd.sh | 72 + src/spdk/test/iscsi_tgt/reset/reset.sh | 77 + src/spdk/test/iscsi_tgt/rpc_config/rpc_config.py | 481 ++ src/spdk/test/iscsi_tgt/rpc_config/rpc_config.sh | 63 + src/spdk/test/iscsi_tgt/sock/sock.sh | 142 + .../test/iscsi_tgt/trace_record/trace_record.sh | 135 + src/spdk/test/json_config/alias_rpc/alias_rpc.sh | 20 + src/spdk/test/json_config/alias_rpc/conf.json | 44 + src/spdk/test/json_config/clear_config.py | 215 + src/spdk/test/json_config/config_filter.py | 96 + src/spdk/test/json_config/json_config.sh | 475 ++ src/spdk/test/json_config/json_diff.sh | 41 + src/spdk/test/lvol/basic.sh | 568 ++ src/spdk/test/lvol/common.sh | 53 + src/spdk/test/lvol/hotremove.sh | 216 + src/spdk/test/lvol/lvol2.sh | 19 + src/spdk/test/lvol/rename.sh | 219 + src/spdk/test/lvol/resize.sh | 219 + src/spdk/test/lvol/snapshot_clone.sh | 617 ++ src/spdk/test/lvol/tasting.sh | 171 + src/spdk/test/lvol/thin_provisioning.sh | 236 + src/spdk/test/make/check_so_deps.sh | 508 ++ src/spdk/test/nvme/Makefile | 46 + src/spdk/test/nvme/aer/.gitignore | 1 + src/spdk/test/nvme/aer/Makefile | 38 + src/spdk/test/nvme/aer/aer.c | 610 ++ src/spdk/test/nvme/cuse/.gitignore | 1 + src/spdk/test/nvme/cuse/Makefile | 38 + src/spdk/test/nvme/cuse/cuse.c | 189 + src/spdk/test/nvme/cuse/nvme_cuse.sh | 46 + src/spdk/test/nvme/cuse/nvme_cuse_rpc.sh | 58 + src/spdk/test/nvme/cuse/nvme_ns_manage_cuse.sh | 164 + src/spdk/test/nvme/cuse/spdk_nvme_cli_cuse.sh | 109 + src/spdk/test/nvme/cuse/spdk_smartctl_cuse.sh | 79 + src/spdk/test/nvme/deallocated_value/.gitignore | 1 + src/spdk/test/nvme/deallocated_value/Makefile | 38 + .../nvme/deallocated_value/deallocated_value.c | 447 ++ src/spdk/test/nvme/e2edp/.gitignore | 1 + src/spdk/test/nvme/e2edp/Makefile | 38 + src/spdk/test/nvme/e2edp/nvme_dp.c | 652 ++ src/spdk/test/nvme/err_injection/.gitignore | 1 + src/spdk/test/nvme/err_injection/Makefile | 38 + src/spdk/test/nvme/err_injection/err_injection.c | 279 + src/spdk/test/nvme/hotplug.sh | 134 + src/spdk/test/nvme/hw_hotplug.sh | 79 + src/spdk/test/nvme/nvme.sh | 134 + src/spdk/test/nvme/nvme_opal.sh | 133 + src/spdk/test/nvme/nvme_rpc.sh | 37 + src/spdk/test/nvme/overhead/.gitignore | 1 + src/spdk/test/nvme/overhead/Makefile | 43 + src/spdk/test/nvme/overhead/README | 24 + src/spdk/test/nvme/overhead/overhead.c | 730 +++ src/spdk/test/nvme/perf/README.md | 103 + src/spdk/test/nvme/perf/common.sh | 471 ++ src/spdk/test/nvme/perf/config.fio.tmp | 6 + src/spdk/test/nvme/perf/run_perf.sh | 374 ++ src/spdk/test/nvme/reserve/.gitignore | 1 + src/spdk/test/nvme/reserve/Makefile | 38 + src/spdk/test/nvme/reserve/reserve.c | 457 ++ src/spdk/test/nvme/reset/.gitignore | 1 + src/spdk/test/nvme/reset/Makefile | 38 + src/spdk/test/nvme/reset/reset.c | 716 +++ src/spdk/test/nvme/sgl/.gitignore | 1 + src/spdk/test/nvme/sgl/Makefile | 38 + src/spdk/test/nvme/sgl/sgl.c | 545 ++ src/spdk/test/nvme/spdk_nvme_cli.sh | 40 + src/spdk/test/nvme/startup/.gitignore | 1 + src/spdk/test/nvme/startup/Makefile | 38 + src/spdk/test/nvme/startup/startup.c | 218 + src/spdk/test/nvmf/README.md | 5 + src/spdk/test/nvmf/common.sh | 292 + src/spdk/test/nvmf/host/aer.sh | 50 + src/spdk/test/nvmf/host/bdevperf.sh | 50 + src/spdk/test/nvmf/host/fio.sh | 82 + src/spdk/test/nvmf/host/identify.sh | 54 + src/spdk/test/nvmf/host/identify_kernel_nvmf.sh | 71 + src/spdk/test/nvmf/host/perf.sh | 93 + src/spdk/test/nvmf/host/target_disconnect.sh | 89 + src/spdk/test/nvmf/nvmf.sh | 60 + src/spdk/test/nvmf/target/abort.sh | 35 + src/spdk/test/nvmf/target/bdev_io_wait.sh | 45 + src/spdk/test/nvmf/target/bdevio.sh | 29 + src/spdk/test/nvmf/target/connect_disconnect.sh | 43 + src/spdk/test/nvmf/target/create_transport.sh | 52 + src/spdk/test/nvmf/target/discovery.sh | 49 + src/spdk/test/nvmf/target/filesystem.sh | 92 + src/spdk/test/nvmf/target/fio.sh | 77 + src/spdk/test/nvmf/target/fuzz.sh | 43 + src/spdk/test/nvmf/target/identify_passthru.sh | 76 + src/spdk/test/nvmf/target/initiator_timeout.sh | 71 + src/spdk/test/nvmf/target/invalid.sh | 63 + src/spdk/test/nvmf/target/multiconnection.sh | 53 + src/spdk/test/nvmf/target/multitarget.sh | 37 + src/spdk/test/nvmf/target/multitarget_rpc.py | 84 + src/spdk/test/nvmf/target/nmic.sh | 56 + src/spdk/test/nvmf/target/nvme_cli.sh | 75 + src/spdk/test/nvmf/target/nvmf_example.sh | 59 + src/spdk/test/nvmf/target/nvmf_lvol.sh | 60 + src/spdk/test/nvmf/target/nvmf_vhost.sh | 69 + src/spdk/test/nvmf/target/nvmf_vhost_fio.job | 19 + src/spdk/test/nvmf/target/rpc.sh | 124 + src/spdk/test/nvmf/target/shutdown.sh | 155 + src/spdk/test/nvmf/target/srq_overwhelm.sh | 50 + src/spdk/test/ocf/common.sh | 27 + src/spdk/test/ocf/integrity/bdevperf-iotypes.sh | 13 + src/spdk/test/ocf/integrity/fio-modes.sh | 90 + src/spdk/test/ocf/integrity/mallocs.conf | 59 + src/spdk/test/ocf/integrity/stats.sh | 17 + src/spdk/test/ocf/integrity/test.fio | 39 + src/spdk/test/ocf/management/create-destruct.sh | 88 + src/spdk/test/ocf/management/multicore.sh | 82 + .../test/ocf/management/persistent-metadata.sh | 88 + src/spdk/test/ocf/management/remove.sh | 81 + src/spdk/test/ocf/ocf.sh | 14 + src/spdk/test/openstack/install_devstack.sh | 51 + src/spdk/test/openstack/run_openstack_tests.sh | 77 + src/spdk/test/pmem/common.sh | 91 + src/spdk/test/pmem/pmem.sh | 683 ++ src/spdk/test/rpc/rpc.sh | 56 + src/spdk/test/rpc/rpc_plugin.py | 24 + src/spdk/test/rpc_client/.gitignore | 1 + src/spdk/test/rpc_client/Makefile | 44 + src/spdk/test/rpc_client/rpc_client.sh | 9 + src/spdk/test/rpc_client/rpc_client_test.c | 461 ++ src/spdk/test/spdk_cunit.h | 56 + src/spdk/test/spdkcli/common.sh | 45 + src/spdk/test/spdkcli/iscsi.sh | 72 + .../match_files/spdkcli_details_lvs.test.match | 9 + .../match_files/spdkcli_details_vhost.test.match | 32 + .../spdkcli_details_vhost_ctrl.test.match | 22 + .../spdkcli_details_vhost_target.test.match | 11 + .../spdkcli/match_files/spdkcli_iscsi.test.match | 55 + .../spdkcli/match_files/spdkcli_nvmf.test.match | 34 + .../spdkcli/match_files/spdkcli_pmem.test.match | 3 + .../match_files/spdkcli_pmem_info.test.match | 12 + .../spdkcli/match_files/spdkcli_raid.test.match | 17 + .../spdkcli/match_files/spdkcli_rbd.test.match | 3 + .../spdkcli/match_files/spdkcli_vhost.test.match | 54 + .../match_files/spdkcli_virtio_pci.test.match | 19 + .../match_files/spdkcli_virtio_user.test.match | 8 + src/spdk/test/spdkcli/nvmf.sh | 85 + src/spdk/test/spdkcli/pmem.sh | 47 + src/spdk/test/spdkcli/raid.sh | 46 + src/spdk/test/spdkcli/rbd.sh | 32 + src/spdk/test/spdkcli/spdkcli_job.py | 59 + src/spdk/test/spdkcli/tcp.sh | 35 + src/spdk/test/spdkcli/vhost.sh | 147 + src/spdk/test/spdkcli/virtio.sh | 78 + src/spdk/test/unit/Makefile | 44 + src/spdk/test/unit/include/Makefile | 44 + src/spdk/test/unit/include/spdk/Makefile | 44 + .../unit/include/spdk/histogram_data.h/.gitignore | 1 + .../unit/include/spdk/histogram_data.h/Makefile | 37 + .../include/spdk/histogram_data.h/histogram_ut.c | 161 + src/spdk/test/unit/lib/Makefile | 51 + src/spdk/test/unit/lib/bdev/Makefile | 51 + src/spdk/test/unit/lib/bdev/bdev.c/.gitignore | 1 + src/spdk/test/unit/lib/bdev/bdev.c/Makefile | 37 + src/spdk/test/unit/lib/bdev/bdev.c/bdev_ut.c | 3417 ++++++++++ .../test/unit/lib/bdev/bdev_ocssd.c/.gitignore | 1 + src/spdk/test/unit/lib/bdev/bdev_ocssd.c/Makefile | 38 + .../unit/lib/bdev/bdev_ocssd.c/bdev_ocssd_ut.c | 1195 ++++ src/spdk/test/unit/lib/bdev/bdev_zone.c/.gitignore | 1 + src/spdk/test/unit/lib/bdev/bdev_zone.c/Makefile | 38 + .../test/unit/lib/bdev/bdev_zone.c/bdev_zone_ut.c | 429 ++ src/spdk/test/unit/lib/bdev/compress.c/.gitignore | 1 + src/spdk/test/unit/lib/bdev/compress.c/Makefile | 39 + .../test/unit/lib/bdev/compress.c/compress_ut.c | 1140 ++++ src/spdk/test/unit/lib/bdev/crypto.c/.gitignore | 1 + src/spdk/test/unit/lib/bdev/crypto.c/Makefile | 39 + src/spdk/test/unit/lib/bdev/crypto.c/crypto_ut.c | 1084 ++++ src/spdk/test/unit/lib/bdev/gpt/Makefile | 44 + src/spdk/test/unit/lib/bdev/gpt/gpt.c/.gitignore | 1 + src/spdk/test/unit/lib/bdev/gpt/gpt.c/Makefile | 38 + src/spdk/test/unit/lib/bdev/gpt/gpt.c/gpt_ut.c | 363 ++ src/spdk/test/unit/lib/bdev/mt/Makefile | 44 + src/spdk/test/unit/lib/bdev/mt/bdev.c/.gitignore | 1 + src/spdk/test/unit/lib/bdev/mt/bdev.c/Makefile | 38 + src/spdk/test/unit/lib/bdev/mt/bdev.c/bdev_ut.c | 1994 ++++++ src/spdk/test/unit/lib/bdev/part.c/.gitignore | 1 + src/spdk/test/unit/lib/bdev/part.c/Makefile | 38 + src/spdk/test/unit/lib/bdev/part.c/part_ut.c | 173 + src/spdk/test/unit/lib/bdev/pmem/.gitignore | 1 + src/spdk/test/unit/lib/bdev/pmem/Makefile | 38 + src/spdk/test/unit/lib/bdev/pmem/bdev_pmem_ut.c | 772 +++ src/spdk/test/unit/lib/bdev/raid/Makefile | 46 + .../test/unit/lib/bdev/raid/bdev_raid.c/.gitignore | 1 + .../test/unit/lib/bdev/raid/bdev_raid.c/Makefile | 38 + .../unit/lib/bdev/raid/bdev_raid.c/bdev_raid_ut.c | 2258 +++++++ .../test/unit/lib/bdev/raid/raid5.c/.gitignore | 1 + src/spdk/test/unit/lib/bdev/raid/raid5.c/Makefile | 38 + .../test/unit/lib/bdev/raid/raid5.c/raid5_ut.c | 214 + src/spdk/test/unit/lib/bdev/scsi_nvme.c/.gitignore | 1 + src/spdk/test/unit/lib/bdev/scsi_nvme.c/Makefile | 37 + .../test/unit/lib/bdev/scsi_nvme.c/scsi_nvme_ut.c | 131 + .../test/unit/lib/bdev/vbdev_lvol.c/.gitignore | 1 + src/spdk/test/unit/lib/bdev/vbdev_lvol.c/Makefile | 38 + .../unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c | 1440 +++++ .../unit/lib/bdev/vbdev_zone_block.c/.gitignore | 1 + .../test/unit/lib/bdev/vbdev_zone_block.c/Makefile | 38 + .../bdev/vbdev_zone_block.c/vbdev_zone_block_ut.c | 1502 +++++ src/spdk/test/unit/lib/blob/Makefile | 49 + src/spdk/test/unit/lib/blob/blob.c/.gitignore | 1 + src/spdk/test/unit/lib/blob/blob.c/Makefile | 38 + src/spdk/test/unit/lib/blob/blob.c/blob_ut.c | 6693 ++++++++++++++++++++ src/spdk/test/unit/lib/blob/bs_dev_common.c | 395 ++ src/spdk/test/unit/lib/blob/bs_scheduler.c | 87 + src/spdk/test/unit/lib/blobfs/Makefile | 44 + .../unit/lib/blobfs/blobfs_async_ut/.gitignore | 1 + .../test/unit/lib/blobfs/blobfs_async_ut/Makefile | 39 + .../lib/blobfs/blobfs_async_ut/blobfs_async_ut.c | 704 ++ .../test/unit/lib/blobfs/blobfs_bdev.c/.gitignore | 1 + .../test/unit/lib/blobfs/blobfs_bdev.c/Makefile | 38 + .../unit/lib/blobfs/blobfs_bdev.c/blobfs_bdev_ut.c | 348 + .../test/unit/lib/blobfs/blobfs_sync_ut/.gitignore | 1 + .../test/unit/lib/blobfs/blobfs_sync_ut/Makefile | 39 + .../lib/blobfs/blobfs_sync_ut/blobfs_sync_ut.c | 703 ++ src/spdk/test/unit/lib/blobfs/tree.c/.gitignore | 1 + src/spdk/test/unit/lib/blobfs/tree.c/Makefile | 38 + src/spdk/test/unit/lib/blobfs/tree.c/tree_ut.c | 150 + src/spdk/test/unit/lib/event/Makefile | 44 + src/spdk/test/unit/lib/event/app.c/.gitignore | 1 + src/spdk/test/unit/lib/event/app.c/Makefile | 39 + src/spdk/test/unit/lib/event/app.c/app_ut.c | 193 + src/spdk/test/unit/lib/event/reactor.c/.gitignore | 1 + src/spdk/test/unit/lib/event/reactor.c/Makefile | 39 + .../test/unit/lib/event/reactor.c/reactor_ut.c | 455 ++ .../test/unit/lib/event/subsystem.c/.gitignore | 1 + src/spdk/test/unit/lib/event/subsystem.c/Makefile | 38 + .../test/unit/lib/event/subsystem.c/subsystem_ut.c | 255 + src/spdk/test/unit/lib/ftl/Makefile | 44 + src/spdk/test/unit/lib/ftl/common/utils.c | 173 + src/spdk/test/unit/lib/ftl/ftl_band.c/.gitignore | 1 + src/spdk/test/unit/lib/ftl/ftl_band.c/Makefile | 38 + .../test/unit/lib/ftl/ftl_band.c/ftl_band_ut.c | 307 + src/spdk/test/unit/lib/ftl/ftl_io.c/.gitignore | 1 + src/spdk/test/unit/lib/ftl/ftl_io.c/Makefile | 38 + src/spdk/test/unit/lib/ftl/ftl_io.c/ftl_io_ut.c | 1068 ++++ src/spdk/test/unit/lib/ftl/ftl_md/.gitignore | 1 + src/spdk/test/unit/lib/ftl/ftl_md/Makefile | 38 + src/spdk/test/unit/lib/ftl/ftl_md/ftl_md_ut.c | 150 + src/spdk/test/unit/lib/ftl/ftl_ppa/.gitignore | 1 + src/spdk/test/unit/lib/ftl/ftl_ppa/Makefile | 38 + src/spdk/test/unit/lib/ftl/ftl_ppa/ftl_ppa_ut.c | 226 + src/spdk/test/unit/lib/ftl/ftl_reloc.c/.gitignore | 1 + src/spdk/test/unit/lib/ftl/ftl_reloc.c/Makefile | 38 + .../test/unit/lib/ftl/ftl_reloc.c/ftl_reloc_ut.c | 508 ++ src/spdk/test/unit/lib/ftl/ftl_wptr/.gitignore | 1 + src/spdk/test/unit/lib/ftl/ftl_wptr/Makefile | 38 + src/spdk/test/unit/lib/ftl/ftl_wptr/ftl_wptr_ut.c | 223 + src/spdk/test/unit/lib/idxd/Makefile | 44 + src/spdk/test/unit/lib/idxd/idxd.c/.gitignore | 1 + src/spdk/test/unit/lib/idxd/idxd.c/Makefile | 38 + src/spdk/test/unit/lib/idxd/idxd.c/idxd_ut.c | 300 + src/spdk/test/unit/lib/ioat/Makefile | 44 + src/spdk/test/unit/lib/ioat/ioat.c/.gitignore | 1 + src/spdk/test/unit/lib/ioat/ioat.c/Makefile | 38 + src/spdk/test/unit/lib/ioat/ioat.c/ioat_ut.c | 144 + src/spdk/test/unit/lib/iscsi/Makefile | 44 + src/spdk/test/unit/lib/iscsi/common.c | 209 + src/spdk/test/unit/lib/iscsi/conn.c/.gitignore | 1 + src/spdk/test/unit/lib/iscsi/conn.c/Makefile | 38 + src/spdk/test/unit/lib/iscsi/conn.c/conn_ut.c | 927 +++ src/spdk/test/unit/lib/iscsi/init_grp.c/.gitignore | 1 + src/spdk/test/unit/lib/iscsi/init_grp.c/Makefile | 39 + .../test/unit/lib/iscsi/init_grp.c/init_grp.conf | 31 + .../test/unit/lib/iscsi/init_grp.c/init_grp_ut.c | 674 ++ src/spdk/test/unit/lib/iscsi/iscsi.c/.gitignore | 1 + src/spdk/test/unit/lib/iscsi/iscsi.c/Makefile | 46 + src/spdk/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c | 2024 ++++++ src/spdk/test/unit/lib/iscsi/param.c/.gitignore | 1 + src/spdk/test/unit/lib/iscsi/param.c/Makefile | 38 + src/spdk/test/unit/lib/iscsi/param.c/param_ut.c | 400 ++ .../test/unit/lib/iscsi/portal_grp.c/.gitignore | 1 + src/spdk/test/unit/lib/iscsi/portal_grp.c/Makefile | 40 + .../unit/lib/iscsi/portal_grp.c/portal_grp_ut.c | 419 ++ src/spdk/test/unit/lib/iscsi/tgt_node.c/.gitignore | 1 + src/spdk/test/unit/lib/iscsi/tgt_node.c/Makefile | 39 + .../test/unit/lib/iscsi/tgt_node.c/tgt_node.conf | 95 + .../test/unit/lib/iscsi/tgt_node.c/tgt_node_ut.c | 832 +++ src/spdk/test/unit/lib/json/Makefile | 44 + .../test/unit/lib/json/json_parse.c/.gitignore | 1 + src/spdk/test/unit/lib/json/json_parse.c/Makefile | 38 + .../unit/lib/json/json_parse.c/json_parse_ut.c | 931 +++ src/spdk/test/unit/lib/json/json_util.c/.gitignore | 1 + src/spdk/test/unit/lib/json/json_util.c/Makefile | 38 + .../test/unit/lib/json/json_util.c/json_util_ut.c | 954 +++ .../test/unit/lib/json/json_write.c/.gitignore | 1 + src/spdk/test/unit/lib/json/json_write.c/Makefile | 38 + .../unit/lib/json/json_write.c/json_write_ut.c | 736 +++ src/spdk/test/unit/lib/json_mock.c | 81 + src/spdk/test/unit/lib/jsonrpc/Makefile | 44 + .../unit/lib/jsonrpc/jsonrpc_server.c/.gitignore | 1 + .../unit/lib/jsonrpc/jsonrpc_server.c/Makefile | 39 + .../jsonrpc/jsonrpc_server.c/jsonrpc_server_ut.c | 410 ++ src/spdk/test/unit/lib/log/Makefile | 44 + src/spdk/test/unit/lib/log/log.c/.gitignore | 1 + src/spdk/test/unit/lib/log/log.c/Makefile | 38 + src/spdk/test/unit/lib/log/log.c/log_ut.c | 106 + src/spdk/test/unit/lib/lvol/Makefile | 44 + src/spdk/test/unit/lib/lvol/lvol.c/.gitignore | 1 + src/spdk/test/unit/lib/lvol/lvol.c/Makefile | 38 + src/spdk/test/unit/lib/lvol/lvol.c/lvol_ut.c | 2096 ++++++ src/spdk/test/unit/lib/notify/Makefile | 44 + src/spdk/test/unit/lib/notify/notify.c/.gitignore | 1 + src/spdk/test/unit/lib/notify/notify.c/Makefile | 38 + src/spdk/test/unit/lib/notify/notify.c/notify_ut.c | 111 + src/spdk/test/unit/lib/nvme/Makefile | 47 + src/spdk/test/unit/lib/nvme/nvme.c/.gitignore | 1 + src/spdk/test/unit/lib/nvme/nvme.c/Makefile | 38 + src/spdk/test/unit/lib/nvme/nvme.c/nvme_ut.c | 1376 ++++ .../test/unit/lib/nvme/nvme_ctrlr.c/.gitignore | 1 + src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/Makefile | 38 + .../unit/lib/nvme/nvme_ctrlr.c/nvme_ctrlr_ut.c | 2150 +++++++ .../test/unit/lib/nvme/nvme_ctrlr_cmd.c/.gitignore | 1 + .../test/unit/lib/nvme/nvme_ctrlr_cmd.c/Makefile | 38 + .../lib/nvme/nvme_ctrlr_cmd.c/nvme_ctrlr_cmd_ut.c | 751 +++ .../lib/nvme/nvme_ctrlr_ocssd_cmd.c/.gitignore | 1 + .../unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/Makefile | 38 + .../nvme_ctrlr_ocssd_cmd_ut.c | 106 + src/spdk/test/unit/lib/nvme/nvme_ns.c/.gitignore | 1 + src/spdk/test/unit/lib/nvme/nvme_ns.c/Makefile | 38 + src/spdk/test/unit/lib/nvme/nvme_ns.c/nvme_ns_ut.c | 153 + .../test/unit/lib/nvme/nvme_ns_cmd.c/.gitignore | 1 + src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/Makefile | 38 + .../unit/lib/nvme/nvme_ns_cmd.c/nvme_ns_cmd_ut.c | 1739 +++++ .../unit/lib/nvme/nvme_ns_ocssd_cmd.c/.gitignore | 1 + .../unit/lib/nvme/nvme_ns_ocssd_cmd.c/Makefile | 38 + .../nvme_ns_ocssd_cmd.c/nvme_ns_ocssd_cmd_ut.c | 650 ++ src/spdk/test/unit/lib/nvme/nvme_pcie.c/.gitignore | 1 + src/spdk/test/unit/lib/nvme/nvme_pcie.c/Makefile | 38 + .../test/unit/lib/nvme/nvme_pcie.c/nvme_pcie_ut.c | 498 ++ .../unit/lib/nvme/nvme_poll_group.c/.gitignore | 1 + .../test/unit/lib/nvme/nvme_poll_group.c/Makefile | 38 + .../nvme/nvme_poll_group.c/nvme_poll_group_ut.c | 484 ++ .../test/unit/lib/nvme/nvme_qpair.c/.gitignore | 1 + src/spdk/test/unit/lib/nvme/nvme_qpair.c/Makefile | 38 + .../unit/lib/nvme/nvme_qpair.c/nvme_qpair_ut.c | 625 ++ .../test/unit/lib/nvme/nvme_quirks.c/.gitignore | 1 + src/spdk/test/unit/lib/nvme/nvme_quirks.c/Makefile | 38 + .../unit/lib/nvme/nvme_quirks.c/nvme_quirks_ut.c | 92 + src/spdk/test/unit/lib/nvme/nvme_rdma.c/.gitignore | 1 + src/spdk/test/unit/lib/nvme/nvme_rdma.c/Makefile | 38 + .../test/unit/lib/nvme/nvme_rdma.c/nvme_rdma_ut.c | 406 ++ src/spdk/test/unit/lib/nvme/nvme_tcp.c/.gitignore | 1 + src/spdk/test/unit/lib/nvme/nvme_tcp.c/Makefile | 38 + .../test/unit/lib/nvme/nvme_tcp.c/nvme_tcp_ut.c | 459 ++ .../test/unit/lib/nvme/nvme_uevent.c/.gitignore | 1 + src/spdk/test/unit/lib/nvme/nvme_uevent.c/Makefile | 38 + .../unit/lib/nvme/nvme_uevent.c/nvme_uevent_ut.c | 165 + src/spdk/test/unit/lib/nvmf/Makefile | 48 + src/spdk/test/unit/lib/nvmf/ctrlr.c/.gitignore | 1 + src/spdk/test/unit/lib/nvmf/ctrlr.c/Makefile | 38 + src/spdk/test/unit/lib/nvmf/ctrlr.c/ctrlr_ut.c | 1711 +++++ .../test/unit/lib/nvmf/ctrlr_bdev.c/.gitignore | 1 + src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/Makefile | 38 + .../unit/lib/nvmf/ctrlr_bdev.c/ctrlr_bdev_ut.c | 415 ++ .../unit/lib/nvmf/ctrlr_discovery.c/.gitignore | 1 + .../test/unit/lib/nvmf/ctrlr_discovery.c/Makefile | 39 + .../nvmf/ctrlr_discovery.c/ctrlr_discovery_ut.c | 303 + src/spdk/test/unit/lib/nvmf/fc.c/.gitignore | 1 + src/spdk/test/unit/lib/nvmf/fc.c/Makefile | 58 + src/spdk/test/unit/lib/nvmf/fc.c/fc_ut.c | 505 ++ src/spdk/test/unit/lib/nvmf/fc_ls.c/.gitignore | 1 + src/spdk/test/unit/lib/nvmf/fc_ls.c/Makefile | 45 + src/spdk/test/unit/lib/nvmf/fc_ls.c/fc_ls_ut.c | 1070 ++++ src/spdk/test/unit/lib/nvmf/rdma.c/.gitignore | 1 + src/spdk/test/unit/lib/nvmf/rdma.c/Makefile | 38 + src/spdk/test/unit/lib/nvmf/rdma.c/rdma_ut.c | 1283 ++++ src/spdk/test/unit/lib/nvmf/subsystem.c/.gitignore | 1 + src/spdk/test/unit/lib/nvmf/subsystem.c/Makefile | 39 + .../test/unit/lib/nvmf/subsystem.c/subsystem_ut.c | 1342 ++++ src/spdk/test/unit/lib/nvmf/tcp.c/.gitignore | 1 + src/spdk/test/unit/lib/nvmf/tcp.c/Makefile | 38 + src/spdk/test/unit/lib/nvmf/tcp.c/tcp_ut.c | 722 +++ src/spdk/test/unit/lib/reduce/Makefile | 44 + src/spdk/test/unit/lib/reduce/reduce.c/.gitignore | 1 + src/spdk/test/unit/lib/reduce/reduce.c/Makefile | 39 + src/spdk/test/unit/lib/reduce/reduce.c/reduce_ut.c | 1300 ++++ src/spdk/test/unit/lib/scsi/Makefile | 44 + src/spdk/test/unit/lib/scsi/dev.c/.gitignore | 1 + src/spdk/test/unit/lib/scsi/dev.c/Makefile | 38 + src/spdk/test/unit/lib/scsi/dev.c/dev_ut.c | 682 ++ src/spdk/test/unit/lib/scsi/lun.c/.gitignore | 1 + src/spdk/test/unit/lib/scsi/lun.c/Makefile | 38 + src/spdk/test/unit/lib/scsi/lun.c/lun_ut.c | 750 +++ src/spdk/test/unit/lib/scsi/scsi.c/.gitignore | 1 + src/spdk/test/unit/lib/scsi/scsi.c/Makefile | 39 + src/spdk/test/unit/lib/scsi/scsi.c/scsi_ut.c | 69 + src/spdk/test/unit/lib/scsi/scsi_bdev.c/.gitignore | 1 + src/spdk/test/unit/lib/scsi/scsi_bdev.c/Makefile | 38 + .../test/unit/lib/scsi/scsi_bdev.c/scsi_bdev_ut.c | 1037 +++ src/spdk/test/unit/lib/scsi/scsi_pr.c/.gitignore | 1 + src/spdk/test/unit/lib/scsi/scsi_pr.c/Makefile | 39 + src/spdk/test/unit/lib/scsi/scsi_pr.c/scsi_pr_ut.c | 673 ++ src/spdk/test/unit/lib/sock/Makefile | 48 + src/spdk/test/unit/lib/sock/posix.c/.gitignore | 1 + src/spdk/test/unit/lib/sock/posix.c/Makefile | 38 + src/spdk/test/unit/lib/sock/posix.c/posix_ut.c | 174 + src/spdk/test/unit/lib/sock/sock.c/.gitignore | 1 + src/spdk/test/unit/lib/sock/sock.c/Makefile | 38 + src/spdk/test/unit/lib/sock/sock.c/sock_ut.c | 982 +++ src/spdk/test/unit/lib/sock/uring.c/.gitignore | 1 + src/spdk/test/unit/lib/sock/uring.c/Makefile | 38 + src/spdk/test/unit/lib/sock/uring.c/uring_ut.c | 272 + src/spdk/test/unit/lib/thread/Makefile | 44 + src/spdk/test/unit/lib/thread/thread.c/.gitignore | 1 + src/spdk/test/unit/lib/thread/thread.c/Makefile | 38 + src/spdk/test/unit/lib/thread/thread.c/thread_ut.c | 1270 ++++ src/spdk/test/unit/lib/util/Makefile | 45 + src/spdk/test/unit/lib/util/base64.c/.gitignore | 1 + src/spdk/test/unit/lib/util/base64.c/Makefile | 38 + src/spdk/test/unit/lib/util/base64.c/base64_ut.c | 381 ++ src/spdk/test/unit/lib/util/bit_array.c/.gitignore | 1 + src/spdk/test/unit/lib/util/bit_array.c/Makefile | 38 + .../test/unit/lib/util/bit_array.c/bit_array_ut.c | 376 ++ src/spdk/test/unit/lib/util/cpuset.c/.gitignore | 1 + src/spdk/test/unit/lib/util/cpuset.c/Makefile | 38 + src/spdk/test/unit/lib/util/cpuset.c/cpuset_ut.c | 262 + src/spdk/test/unit/lib/util/crc16.c/.gitignore | 1 + src/spdk/test/unit/lib/util/crc16.c/Makefile | 38 + src/spdk/test/unit/lib/util/crc16.c/crc16_ut.c | 104 + .../test/unit/lib/util/crc32_ieee.c/.gitignore | 1 + src/spdk/test/unit/lib/util/crc32_ieee.c/Makefile | 38 + .../unit/lib/util/crc32_ieee.c/crc32_ieee_ut.c | 74 + src/spdk/test/unit/lib/util/crc32c.c/.gitignore | 1 + src/spdk/test/unit/lib/util/crc32c.c/Makefile | 38 + src/spdk/test/unit/lib/util/crc32c.c/crc32c_ut.c | 145 + src/spdk/test/unit/lib/util/dif.c/.gitignore | 1 + src/spdk/test/unit/lib/util/dif.c/Makefile | 38 + src/spdk/test/unit/lib/util/dif.c/dif_ut.c | 2669 ++++++++ src/spdk/test/unit/lib/util/iov.c/.gitignore | 1 + src/spdk/test/unit/lib/util/iov.c/Makefile | 38 + src/spdk/test/unit/lib/util/iov.c/iov_ut.c | 249 + src/spdk/test/unit/lib/util/math.c/.gitignore | 1 + src/spdk/test/unit/lib/util/math.c/Makefile | 39 + src/spdk/test/unit/lib/util/math.c/math_ut.c | 81 + src/spdk/test/unit/lib/util/pipe.c/.gitignore | 1 + src/spdk/test/unit/lib/util/pipe.c/Makefile | 38 + src/spdk/test/unit/lib/util/pipe.c/pipe_ut.c | 653 ++ src/spdk/test/unit/lib/util/string.c/.gitignore | 1 + src/spdk/test/unit/lib/util/string.c/Makefile | 38 + src/spdk/test/unit/lib/util/string.c/string_ut.c | 407 ++ src/spdk/test/unit/lib/vhost/Makefile | 44 + src/spdk/test/unit/lib/vhost/vhost.c/.gitignore | 1 + src/spdk/test/unit/lib/vhost/vhost.c/Makefile | 44 + src/spdk/test/unit/lib/vhost/vhost.c/vhost_ut.c | 547 ++ src/spdk/test/unit/unittest.sh | 253 + src/spdk/test/vhost/common.sh | 1266 ++++ src/spdk/test/vhost/common/autotest.config | 38 + .../vhost/common/fio_jobs/default_initiator.job | 11 + .../vhost/common/fio_jobs/default_integrity.job | 19 + .../common/fio_jobs/default_integrity_nightly.job | 23 + .../vhost/common/fio_jobs/default_performance.job | 16 + src/spdk/test/vhost/fio/fio.sh | 58 + src/spdk/test/vhost/fio/vhost_fio.job | 19 + src/spdk/test/vhost/fiotest/fio.sh | 288 + src/spdk/test/vhost/fuzz/fuzz.sh | 66 + src/spdk/test/vhost/hotplug/blk_hotremove.sh | 235 + src/spdk/test/vhost/hotplug/common.sh | 230 + .../vhost/hotplug/fio_jobs/default_integrity.job | 16 + src/spdk/test/vhost/hotplug/scsi_hotattach.sh | 103 + src/spdk/test/vhost/hotplug/scsi_hotdetach.sh | 212 + src/spdk/test/vhost/hotplug/scsi_hotplug.sh | 92 + src/spdk/test/vhost/hotplug/scsi_hotremove.sh | 233 + src/spdk/test/vhost/initiator/autotest.config | 5 + src/spdk/test/vhost/initiator/bdev.fio | 51 + src/spdk/test/vhost/initiator/bdev_pci.conf | 2 + src/spdk/test/vhost/initiator/blockdev.sh | 82 + src/spdk/test/vhost/integrity/integrity_start.sh | 106 + src/spdk/test/vhost/integrity/integrity_vm.sh | 83 + src/spdk/test/vhost/lvol/autotest.config | 74 + src/spdk/test/vhost/lvol/lvol_test.sh | 289 + src/spdk/test/vhost/manual.sh | 86 + src/spdk/test/vhost/migration/autotest.config | 14 + src/spdk/test/vhost/migration/migration-tc1.job | 25 + src/spdk/test/vhost/migration/migration-tc1.sh | 119 + src/spdk/test/vhost/migration/migration-tc2.job | 20 + src/spdk/test/vhost/migration/migration-tc2.sh | 203 + src/spdk/test/vhost/migration/migration-tc3.job | 20 + src/spdk/test/vhost/migration/migration-tc3a.sh | 218 + src/spdk/test/vhost/migration/migration-tc3b.sh | 77 + src/spdk/test/vhost/migration/migration.sh | 143 + src/spdk/test/vhost/other/invalid.config | 18 + src/spdk/test/vhost/other/negative.sh | 209 + src/spdk/test/vhost/perf_bench/vhost_perf.sh | 473 ++ .../test/vhost/readonly/delete_partition_vm.sh | 42 + .../test/vhost/readonly/disabled_readonly_vm.sh | 47 + .../test/vhost/readonly/enabled_readonly_vm.sh | 72 + src/spdk/test/vhost/readonly/readonly.sh | 136 + src/spdk/test/vhost/shared/bdev.json | 20 + src/spdk/test/vhost/shared/shared.sh | 32 + src/spdk/test/vhost/vhost.sh | 107 + src/spdk/test/vhost/vhost_boot/vhost_boot.sh | 126 + src/spdk/test/vhost/windows/windows.sh | 141 + src/spdk/test/vhost/windows/windows_fs_test.ps1 | 78 + .../test/vhost/windows/windows_scsi_compliance.ps1 | 73 + .../test/vhost/windows/windows_scsi_compliance.py | 147 + .../test/vhost/windows/windows_scsi_compliance.sh | 89 + src/spdk/test/vmd/config/config.fio | 18 + src/spdk/test/vmd/vmd.sh | 78 + 688 files changed, 122799 insertions(+) create mode 100644 src/spdk/test/Makefile create mode 100644 src/spdk/test/app/Makefile create mode 100644 src/spdk/test/app/bdev_svc/.gitignore create mode 100644 src/spdk/test/app/bdev_svc/Makefile create mode 100644 src/spdk/test/app/bdev_svc/bdev_svc.c create mode 100644 src/spdk/test/app/fuzz/Makefile create mode 100644 src/spdk/test/app/fuzz/common/fuzz_common.h create mode 100755 src/spdk/test/app/fuzz/common/fuzz_rpc.py create mode 100644 src/spdk/test/app/fuzz/iscsi_fuzz/.gitignore create mode 100644 src/spdk/test/app/fuzz/iscsi_fuzz/Makefile create mode 100644 src/spdk/test/app/fuzz/iscsi_fuzz/README.md create mode 100644 src/spdk/test/app/fuzz/iscsi_fuzz/iscsi_fuzz.c create mode 100644 src/spdk/test/app/fuzz/nvme_fuzz/.gitignore create mode 100644 src/spdk/test/app/fuzz/nvme_fuzz/Makefile create mode 100644 src/spdk/test/app/fuzz/nvme_fuzz/README.md create mode 100644 src/spdk/test/app/fuzz/nvme_fuzz/example.json create mode 100644 src/spdk/test/app/fuzz/nvme_fuzz/nvme_fuzz.c create mode 100644 src/spdk/test/app/fuzz/vhost_fuzz/.gitignore create mode 100644 src/spdk/test/app/fuzz/vhost_fuzz/Makefile create mode 100644 src/spdk/test/app/fuzz/vhost_fuzz/README.md create mode 100644 src/spdk/test/app/fuzz/vhost_fuzz/example.json create mode 100644 src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.c create mode 100644 src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.h create mode 100644 src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz_rpc.c create mode 100644 src/spdk/test/app/histogram_perf/.gitignore create mode 100644 src/spdk/test/app/histogram_perf/Makefile create mode 100644 src/spdk/test/app/histogram_perf/histogram_perf.c create mode 100644 src/spdk/test/app/jsoncat/.gitignore create mode 100644 src/spdk/test/app/jsoncat/Makefile create mode 100644 src/spdk/test/app/jsoncat/jsoncat.c create mode 100755 src/spdk/test/app/match/match create mode 100644 src/spdk/test/app/stub/.gitignore create mode 100644 src/spdk/test/app/stub/Makefile create mode 100644 src/spdk/test/app/stub/stub.c create mode 100644 src/spdk/test/bdev/Makefile create mode 100755 src/spdk/test/bdev/bdev_raid.sh create mode 100644 src/spdk/test/bdev/bdevio/.gitignore create mode 100644 src/spdk/test/bdev/bdevio/Makefile create mode 100644 src/spdk/test/bdev/bdevio/bdevio.c create mode 100755 src/spdk/test/bdev/bdevio/tests.py create mode 100644 src/spdk/test/bdev/bdevperf/.gitignore create mode 100644 src/spdk/test/bdev/bdevperf/Makefile create mode 100644 src/spdk/test/bdev/bdevperf/bdevperf.c create mode 100755 src/spdk/test/bdev/bdevperf/bdevperf.py create mode 100644 src/spdk/test/bdev/bdevperf/common.sh create mode 100644 src/spdk/test/bdev/bdevperf/conf.json create mode 100755 src/spdk/test/bdev/bdevperf/test_config.sh create mode 100755 src/spdk/test/bdev/blockdev.sh create mode 100644 src/spdk/test/bdev/nbd_common.sh create mode 100644 src/spdk/test/blobfs/Makefile create mode 100755 src/spdk/test/blobfs/blobfs.sh create mode 100644 src/spdk/test/blobfs/fuse/.gitignore create mode 100644 src/spdk/test/blobfs/fuse/Makefile create mode 100644 src/spdk/test/blobfs/fuse/fuse.c create mode 100644 src/spdk/test/blobfs/mkfs/.gitignore create mode 100644 src/spdk/test/blobfs/mkfs/Makefile create mode 100644 src/spdk/test/blobfs/mkfs/mkfs.c create mode 100644 src/spdk/test/blobfs/rocksdb/.gitignore create mode 100644 src/spdk/test/blobfs/rocksdb/common_flags.txt create mode 100755 src/spdk/test/blobfs/rocksdb/postprocess.py create mode 100755 src/spdk/test/blobfs/rocksdb/rocksdb.sh create mode 100644 src/spdk/test/blobfs/rocksdb/rocksdb_commit_id create mode 100755 src/spdk/test/blobstore/blob_io_wait/blob_io_wait.sh create mode 100755 src/spdk/test/blobstore/blobstore.sh create mode 100644 src/spdk/test/blobstore/btest.out.ignore create mode 100644 src/spdk/test/blobstore/btest.out.match create mode 100644 src/spdk/test/blobstore/test.bs create mode 100644 src/spdk/test/common/applications.sh create mode 100755 src/spdk/test/common/autotest_common.sh create mode 100644 src/spdk/test/common/config/README.md create mode 100644 src/spdk/test/common/config/pkgdep/apt-get create mode 100644 src/spdk/test/common/config/pkgdep/dnf create mode 100644 src/spdk/test/common/config/pkgdep/git create mode 100644 src/spdk/test/common/config/pkgdep/pacman create mode 100644 src/spdk/test/common/config/pkgdep/pkg create mode 100644 src/spdk/test/common/config/pkgdep/swupd create mode 100644 src/spdk/test/common/config/pkgdep/yum create mode 100644 src/spdk/test/common/config/vm_setup.conf create mode 100755 src/spdk/test/common/config/vm_setup.sh create mode 100644 src/spdk/test/common/lib/nvme/common_stubs.h create mode 100644 src/spdk/test/common/lib/test_env.c create mode 100644 src/spdk/test/common/lib/test_rdma.c create mode 100644 src/spdk/test/common/lib/test_sock.c create mode 100644 src/spdk/test/common/lib/ut_multithread.c create mode 100644 src/spdk/test/common/skipped_build_files.txt create mode 100644 src/spdk/test/common/skipped_tests.txt create mode 100755 src/spdk/test/compress/compress.sh create mode 100644 src/spdk/test/config_converter/config.ini create mode 100644 src/spdk/test/config_converter/config_virtio.ini create mode 100644 src/spdk/test/config_converter/spdk_config.json create mode 100644 src/spdk/test/config_converter/spdk_config_virtio.json create mode 100755 src/spdk/test/config_converter/test_converter.sh create mode 100644 src/spdk/test/cpp_headers/.gitignore create mode 100644 src/spdk/test/cpp_headers/Makefile create mode 100755 src/spdk/test/dd/basic_rw.sh create mode 100755 src/spdk/test/dd/bdev_to_bdev.sh create mode 100644 src/spdk/test/dd/common.sh create mode 100755 src/spdk/test/dd/dd.sh create mode 100755 src/spdk/test/dd/posix.sh create mode 100755 src/spdk/test/dpdk_memory_utility/test_dpdk_mem_info.sh create mode 100644 src/spdk/test/env/Makefile create mode 100755 src/spdk/test/env/env.sh create mode 100644 src/spdk/test/env/env_dpdk_post_init/.gitignore create mode 100644 src/spdk/test/env/env_dpdk_post_init/Makefile create mode 100644 src/spdk/test/env/env_dpdk_post_init/env_dpdk_post_init.c create mode 100644 src/spdk/test/env/mem_callbacks/.gitignore create mode 100644 src/spdk/test/env/mem_callbacks/Makefile create mode 100644 src/spdk/test/env/mem_callbacks/mem_callbacks.c create mode 100644 src/spdk/test/env/memory/.gitignore create mode 100644 src/spdk/test/env/memory/Makefile create mode 100644 src/spdk/test/env/memory/memory_ut.c create mode 100644 src/spdk/test/env/pci/.gitignore create mode 100644 src/spdk/test/env/pci/Makefile create mode 100644 src/spdk/test/env/pci/pci_ut.c create mode 100644 src/spdk/test/env/vtophys/.gitignore create mode 100644 src/spdk/test/env/vtophys/Makefile create mode 100644 src/spdk/test/env/vtophys/vtophys.c create mode 100644 src/spdk/test/event/Makefile create mode 100644 src/spdk/test/event/app_repeat/.gitignore create mode 100644 src/spdk/test/event/app_repeat/Makefile create mode 100644 src/spdk/test/event/app_repeat/app_repeat.c create mode 100755 src/spdk/test/event/event.sh create mode 100644 src/spdk/test/event/event_perf/.gitignore create mode 100644 src/spdk/test/event/event_perf/Makefile create mode 100644 src/spdk/test/event/event_perf/event_perf.c create mode 100644 src/spdk/test/event/reactor/.gitignore create mode 100644 src/spdk/test/event/reactor/Makefile create mode 100644 src/spdk/test/event/reactor/reactor.c create mode 100644 src/spdk/test/event/reactor_perf/.gitignore create mode 100644 src/spdk/test/event/reactor_perf/Makefile create mode 100644 src/spdk/test/event/reactor_perf/reactor_perf.c create mode 100644 src/spdk/test/external_code/Makefile create mode 100644 src/spdk/test/external_code/README.md create mode 100644 src/spdk/test/external_code/hello_world/.gitignore create mode 100644 src/spdk/test/external_code/hello_world/Makefile create mode 100644 src/spdk/test/external_code/hello_world/bdev.conf create mode 100644 src/spdk/test/external_code/hello_world/bdev_external.conf create mode 100644 src/spdk/test/external_code/hello_world/hello_bdev.c create mode 100644 src/spdk/test/external_code/passthru/Makefile create mode 100644 src/spdk/test/external_code/passthru/vbdev_passthru.c create mode 100644 src/spdk/test/external_code/passthru/vbdev_passthru.h create mode 100644 src/spdk/test/external_code/passthru/vbdev_passthru_rpc.c create mode 100755 src/spdk/test/external_code/test_make.sh create mode 100755 src/spdk/test/ftl/bdevperf.sh create mode 100644 src/spdk/test/ftl/common.sh create mode 100644 src/spdk/test/ftl/config/.gitignore create mode 100644 src/spdk/test/ftl/config/fio/drive-prep.fio create mode 100644 src/spdk/test/ftl/config/fio/randr.fio create mode 100644 src/spdk/test/ftl/config/fio/randrw.fio create mode 100644 src/spdk/test/ftl/config/fio/randw-verify-depth128.fio create mode 100644 src/spdk/test/ftl/config/fio/randw-verify-j2.fio create mode 100644 src/spdk/test/ftl/config/fio/randw-verify-qd128-ext.fio create mode 100644 src/spdk/test/ftl/config/fio/randw-verify.fio create mode 100644 src/spdk/test/ftl/config/fio/randw.fio create mode 100755 src/spdk/test/ftl/dirty_shutdown.sh create mode 100755 src/spdk/test/ftl/fio.sh create mode 100755 src/spdk/test/ftl/ftl.sh create mode 100755 src/spdk/test/ftl/json.sh create mode 100755 src/spdk/test/ftl/restore.sh create mode 100755 src/spdk/test/fuzz/autofuzz.sh create mode 100755 src/spdk/test/fuzz/autofuzz_iscsi.sh create mode 100755 src/spdk/test/fuzz/autofuzz_nvmf.sh create mode 100755 src/spdk/test/fuzz/autofuzz_vhost.sh create mode 100755 src/spdk/test/ioat/ioat.sh create mode 100755 src/spdk/test/iscsi_tgt/bdev_io_wait/bdev_io_wait.sh create mode 100755 src/spdk/test/iscsi_tgt/calsoft/calsoft.py create mode 100755 src/spdk/test/iscsi_tgt/calsoft/calsoft.sh create mode 100644 src/spdk/test/iscsi_tgt/calsoft/iscsi.json create mode 100644 src/spdk/test/iscsi_tgt/calsoft/its.conf create mode 100644 src/spdk/test/iscsi_tgt/common.sh create mode 100755 src/spdk/test/iscsi_tgt/digests/digests.sh create mode 100755 src/spdk/test/iscsi_tgt/ext4test/ext4test.sh create mode 100755 src/spdk/test/iscsi_tgt/filesystem/filesystem.sh create mode 100755 src/spdk/test/iscsi_tgt/fio/fio.sh create mode 100644 src/spdk/test/iscsi_tgt/fio/iscsi.json create mode 100755 src/spdk/test/iscsi_tgt/fuzz/fuzz.sh create mode 100755 src/spdk/test/iscsi_tgt/initiator/initiator.sh create mode 100755 src/spdk/test/iscsi_tgt/ip_migration/ip_migration.sh create mode 100755 src/spdk/test/iscsi_tgt/iscsi_tgt.sh create mode 100755 src/spdk/test/iscsi_tgt/lvol/iscsi_lvol.sh create mode 100755 src/spdk/test/iscsi_tgt/multiconnection/multiconnection.sh create mode 100755 src/spdk/test/iscsi_tgt/nvme_remote/fio_remote_nvme.sh create mode 100755 src/spdk/test/iscsi_tgt/perf/iscsi_initiator.sh create mode 100755 src/spdk/test/iscsi_tgt/perf/iscsi_target.sh create mode 100644 src/spdk/test/iscsi_tgt/perf/perf.job create mode 100755 src/spdk/test/iscsi_tgt/pmem/iscsi_pmem.sh create mode 100755 src/spdk/test/iscsi_tgt/qos/qos.sh create mode 100755 src/spdk/test/iscsi_tgt/rbd/rbd.sh create mode 100755 src/spdk/test/iscsi_tgt/reset/reset.sh create mode 100755 src/spdk/test/iscsi_tgt/rpc_config/rpc_config.py create mode 100755 src/spdk/test/iscsi_tgt/rpc_config/rpc_config.sh create mode 100755 src/spdk/test/iscsi_tgt/sock/sock.sh create mode 100755 src/spdk/test/iscsi_tgt/trace_record/trace_record.sh create mode 100755 src/spdk/test/json_config/alias_rpc/alias_rpc.sh create mode 100644 src/spdk/test/json_config/alias_rpc/conf.json create mode 100755 src/spdk/test/json_config/clear_config.py create mode 100755 src/spdk/test/json_config/config_filter.py create mode 100755 src/spdk/test/json_config/json_config.sh create mode 100755 src/spdk/test/json_config/json_diff.sh create mode 100755 src/spdk/test/lvol/basic.sh create mode 100644 src/spdk/test/lvol/common.sh create mode 100755 src/spdk/test/lvol/hotremove.sh create mode 100755 src/spdk/test/lvol/lvol2.sh create mode 100755 src/spdk/test/lvol/rename.sh create mode 100755 src/spdk/test/lvol/resize.sh create mode 100755 src/spdk/test/lvol/snapshot_clone.sh create mode 100755 src/spdk/test/lvol/tasting.sh create mode 100755 src/spdk/test/lvol/thin_provisioning.sh create mode 100755 src/spdk/test/make/check_so_deps.sh create mode 100644 src/spdk/test/nvme/Makefile create mode 100644 src/spdk/test/nvme/aer/.gitignore create mode 100644 src/spdk/test/nvme/aer/Makefile create mode 100644 src/spdk/test/nvme/aer/aer.c create mode 100644 src/spdk/test/nvme/cuse/.gitignore create mode 100644 src/spdk/test/nvme/cuse/Makefile create mode 100644 src/spdk/test/nvme/cuse/cuse.c create mode 100755 src/spdk/test/nvme/cuse/nvme_cuse.sh create mode 100755 src/spdk/test/nvme/cuse/nvme_cuse_rpc.sh create mode 100755 src/spdk/test/nvme/cuse/nvme_ns_manage_cuse.sh create mode 100755 src/spdk/test/nvme/cuse/spdk_nvme_cli_cuse.sh create mode 100755 src/spdk/test/nvme/cuse/spdk_smartctl_cuse.sh create mode 100644 src/spdk/test/nvme/deallocated_value/.gitignore create mode 100644 src/spdk/test/nvme/deallocated_value/Makefile create mode 100644 src/spdk/test/nvme/deallocated_value/deallocated_value.c create mode 100644 src/spdk/test/nvme/e2edp/.gitignore create mode 100644 src/spdk/test/nvme/e2edp/Makefile create mode 100644 src/spdk/test/nvme/e2edp/nvme_dp.c create mode 100644 src/spdk/test/nvme/err_injection/.gitignore create mode 100644 src/spdk/test/nvme/err_injection/Makefile create mode 100644 src/spdk/test/nvme/err_injection/err_injection.c create mode 100755 src/spdk/test/nvme/hotplug.sh create mode 100755 src/spdk/test/nvme/hw_hotplug.sh create mode 100755 src/spdk/test/nvme/nvme.sh create mode 100755 src/spdk/test/nvme/nvme_opal.sh create mode 100755 src/spdk/test/nvme/nvme_rpc.sh create mode 100644 src/spdk/test/nvme/overhead/.gitignore create mode 100644 src/spdk/test/nvme/overhead/Makefile create mode 100644 src/spdk/test/nvme/overhead/README create mode 100644 src/spdk/test/nvme/overhead/overhead.c create mode 100644 src/spdk/test/nvme/perf/README.md create mode 100755 src/spdk/test/nvme/perf/common.sh create mode 100644 src/spdk/test/nvme/perf/config.fio.tmp create mode 100755 src/spdk/test/nvme/perf/run_perf.sh create mode 100644 src/spdk/test/nvme/reserve/.gitignore create mode 100644 src/spdk/test/nvme/reserve/Makefile create mode 100644 src/spdk/test/nvme/reserve/reserve.c create mode 100644 src/spdk/test/nvme/reset/.gitignore create mode 100644 src/spdk/test/nvme/reset/Makefile create mode 100644 src/spdk/test/nvme/reset/reset.c create mode 100644 src/spdk/test/nvme/sgl/.gitignore create mode 100644 src/spdk/test/nvme/sgl/Makefile create mode 100644 src/spdk/test/nvme/sgl/sgl.c create mode 100755 src/spdk/test/nvme/spdk_nvme_cli.sh create mode 100644 src/spdk/test/nvme/startup/.gitignore create mode 100644 src/spdk/test/nvme/startup/Makefile create mode 100644 src/spdk/test/nvme/startup/startup.c create mode 100644 src/spdk/test/nvmf/README.md create mode 100644 src/spdk/test/nvmf/common.sh create mode 100755 src/spdk/test/nvmf/host/aer.sh create mode 100755 src/spdk/test/nvmf/host/bdevperf.sh create mode 100755 src/spdk/test/nvmf/host/fio.sh create mode 100755 src/spdk/test/nvmf/host/identify.sh create mode 100755 src/spdk/test/nvmf/host/identify_kernel_nvmf.sh create mode 100755 src/spdk/test/nvmf/host/perf.sh create mode 100755 src/spdk/test/nvmf/host/target_disconnect.sh create mode 100755 src/spdk/test/nvmf/nvmf.sh create mode 100755 src/spdk/test/nvmf/target/abort.sh create mode 100755 src/spdk/test/nvmf/target/bdev_io_wait.sh create mode 100755 src/spdk/test/nvmf/target/bdevio.sh create mode 100755 src/spdk/test/nvmf/target/connect_disconnect.sh create mode 100755 src/spdk/test/nvmf/target/create_transport.sh create mode 100755 src/spdk/test/nvmf/target/discovery.sh create mode 100755 src/spdk/test/nvmf/target/filesystem.sh create mode 100755 src/spdk/test/nvmf/target/fio.sh create mode 100755 src/spdk/test/nvmf/target/fuzz.sh create mode 100755 src/spdk/test/nvmf/target/identify_passthru.sh create mode 100755 src/spdk/test/nvmf/target/initiator_timeout.sh create mode 100755 src/spdk/test/nvmf/target/invalid.sh create mode 100755 src/spdk/test/nvmf/target/multiconnection.sh create mode 100755 src/spdk/test/nvmf/target/multitarget.sh create mode 100755 src/spdk/test/nvmf/target/multitarget_rpc.py create mode 100755 src/spdk/test/nvmf/target/nmic.sh create mode 100755 src/spdk/test/nvmf/target/nvme_cli.sh create mode 100755 src/spdk/test/nvmf/target/nvmf_example.sh create mode 100755 src/spdk/test/nvmf/target/nvmf_lvol.sh create mode 100755 src/spdk/test/nvmf/target/nvmf_vhost.sh create mode 100644 src/spdk/test/nvmf/target/nvmf_vhost_fio.job create mode 100755 src/spdk/test/nvmf/target/rpc.sh create mode 100755 src/spdk/test/nvmf/target/shutdown.sh create mode 100755 src/spdk/test/nvmf/target/srq_overwhelm.sh create mode 100644 src/spdk/test/ocf/common.sh create mode 100755 src/spdk/test/ocf/integrity/bdevperf-iotypes.sh create mode 100755 src/spdk/test/ocf/integrity/fio-modes.sh create mode 100644 src/spdk/test/ocf/integrity/mallocs.conf create mode 100755 src/spdk/test/ocf/integrity/stats.sh create mode 100644 src/spdk/test/ocf/integrity/test.fio create mode 100755 src/spdk/test/ocf/management/create-destruct.sh create mode 100755 src/spdk/test/ocf/management/multicore.sh create mode 100755 src/spdk/test/ocf/management/persistent-metadata.sh create mode 100755 src/spdk/test/ocf/management/remove.sh create mode 100755 src/spdk/test/ocf/ocf.sh create mode 100755 src/spdk/test/openstack/install_devstack.sh create mode 100755 src/spdk/test/openstack/run_openstack_tests.sh create mode 100644 src/spdk/test/pmem/common.sh create mode 100755 src/spdk/test/pmem/pmem.sh create mode 100755 src/spdk/test/rpc/rpc.sh create mode 100644 src/spdk/test/rpc/rpc_plugin.py create mode 100644 src/spdk/test/rpc_client/.gitignore create mode 100644 src/spdk/test/rpc_client/Makefile create mode 100755 src/spdk/test/rpc_client/rpc_client.sh create mode 100644 src/spdk/test/rpc_client/rpc_client_test.c create mode 100644 src/spdk/test/spdk_cunit.h create mode 100644 src/spdk/test/spdkcli/common.sh create mode 100755 src/spdk/test/spdkcli/iscsi.sh create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_details_lvs.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_details_vhost.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_details_vhost_ctrl.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_details_vhost_target.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_iscsi.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_nvmf.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_pmem.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_pmem_info.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_raid.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_rbd.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_vhost.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_virtio_pci.test.match create mode 100644 src/spdk/test/spdkcli/match_files/spdkcli_virtio_user.test.match create mode 100755 src/spdk/test/spdkcli/nvmf.sh create mode 100755 src/spdk/test/spdkcli/pmem.sh create mode 100755 src/spdk/test/spdkcli/raid.sh create mode 100755 src/spdk/test/spdkcli/rbd.sh create mode 100755 src/spdk/test/spdkcli/spdkcli_job.py create mode 100755 src/spdk/test/spdkcli/tcp.sh create mode 100755 src/spdk/test/spdkcli/vhost.sh create mode 100755 src/spdk/test/spdkcli/virtio.sh create mode 100644 src/spdk/test/unit/Makefile create mode 100644 src/spdk/test/unit/include/Makefile create mode 100644 src/spdk/test/unit/include/spdk/Makefile create mode 100644 src/spdk/test/unit/include/spdk/histogram_data.h/.gitignore create mode 100644 src/spdk/test/unit/include/spdk/histogram_data.h/Makefile create mode 100644 src/spdk/test/unit/include/spdk/histogram_data.h/histogram_ut.c create mode 100644 src/spdk/test/unit/lib/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/bdev.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/bdev.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/bdev.c/bdev_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/bdev_ocssd.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/bdev_ocssd.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/bdev_ocssd.c/bdev_ocssd_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/bdev_zone.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/bdev_zone.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/bdev_zone.c/bdev_zone_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/compress.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/compress.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/compress.c/compress_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/crypto.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/crypto.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/crypto.c/crypto_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/gpt/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/gpt/gpt.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/gpt/gpt.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/gpt/gpt.c/gpt_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/mt/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/mt/bdev.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/mt/bdev.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/mt/bdev.c/bdev_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/part.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/part.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/part.c/part_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/pmem/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/pmem/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/pmem/bdev_pmem_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/raid/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/raid/bdev_raid.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/raid/bdev_raid.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/raid/bdev_raid.c/bdev_raid_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/raid/raid5.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/raid/raid5.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/raid/raid5.c/raid5_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/scsi_nvme.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/scsi_nvme.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/scsi_nvme.c/scsi_nvme_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/vbdev_lvol.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/vbdev_lvol.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c create mode 100644 src/spdk/test/unit/lib/bdev/vbdev_zone_block.c/.gitignore create mode 100644 src/spdk/test/unit/lib/bdev/vbdev_zone_block.c/Makefile create mode 100644 src/spdk/test/unit/lib/bdev/vbdev_zone_block.c/vbdev_zone_block_ut.c create mode 100644 src/spdk/test/unit/lib/blob/Makefile create mode 100644 src/spdk/test/unit/lib/blob/blob.c/.gitignore create mode 100644 src/spdk/test/unit/lib/blob/blob.c/Makefile create mode 100644 src/spdk/test/unit/lib/blob/blob.c/blob_ut.c create mode 100644 src/spdk/test/unit/lib/blob/bs_dev_common.c create mode 100644 src/spdk/test/unit/lib/blob/bs_scheduler.c create mode 100644 src/spdk/test/unit/lib/blobfs/Makefile create mode 100644 src/spdk/test/unit/lib/blobfs/blobfs_async_ut/.gitignore create mode 100644 src/spdk/test/unit/lib/blobfs/blobfs_async_ut/Makefile create mode 100644 src/spdk/test/unit/lib/blobfs/blobfs_async_ut/blobfs_async_ut.c create mode 100644 src/spdk/test/unit/lib/blobfs/blobfs_bdev.c/.gitignore create mode 100644 src/spdk/test/unit/lib/blobfs/blobfs_bdev.c/Makefile create mode 100644 src/spdk/test/unit/lib/blobfs/blobfs_bdev.c/blobfs_bdev_ut.c create mode 100644 src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/.gitignore create mode 100644 src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/Makefile create mode 100644 src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/blobfs_sync_ut.c create mode 100644 src/spdk/test/unit/lib/blobfs/tree.c/.gitignore create mode 100644 src/spdk/test/unit/lib/blobfs/tree.c/Makefile create mode 100644 src/spdk/test/unit/lib/blobfs/tree.c/tree_ut.c create mode 100644 src/spdk/test/unit/lib/event/Makefile create mode 100644 src/spdk/test/unit/lib/event/app.c/.gitignore create mode 100644 src/spdk/test/unit/lib/event/app.c/Makefile create mode 100644 src/spdk/test/unit/lib/event/app.c/app_ut.c create mode 100644 src/spdk/test/unit/lib/event/reactor.c/.gitignore create mode 100644 src/spdk/test/unit/lib/event/reactor.c/Makefile create mode 100644 src/spdk/test/unit/lib/event/reactor.c/reactor_ut.c create mode 100644 src/spdk/test/unit/lib/event/subsystem.c/.gitignore create mode 100644 src/spdk/test/unit/lib/event/subsystem.c/Makefile create mode 100644 src/spdk/test/unit/lib/event/subsystem.c/subsystem_ut.c create mode 100644 src/spdk/test/unit/lib/ftl/Makefile create mode 100644 src/spdk/test/unit/lib/ftl/common/utils.c create mode 100644 src/spdk/test/unit/lib/ftl/ftl_band.c/.gitignore create mode 100644 src/spdk/test/unit/lib/ftl/ftl_band.c/Makefile create mode 100644 src/spdk/test/unit/lib/ftl/ftl_band.c/ftl_band_ut.c create mode 100644 src/spdk/test/unit/lib/ftl/ftl_io.c/.gitignore create mode 100644 src/spdk/test/unit/lib/ftl/ftl_io.c/Makefile create mode 100644 src/spdk/test/unit/lib/ftl/ftl_io.c/ftl_io_ut.c create mode 100644 src/spdk/test/unit/lib/ftl/ftl_md/.gitignore create mode 100644 src/spdk/test/unit/lib/ftl/ftl_md/Makefile create mode 100644 src/spdk/test/unit/lib/ftl/ftl_md/ftl_md_ut.c create mode 100644 src/spdk/test/unit/lib/ftl/ftl_ppa/.gitignore create mode 100644 src/spdk/test/unit/lib/ftl/ftl_ppa/Makefile create mode 100644 src/spdk/test/unit/lib/ftl/ftl_ppa/ftl_ppa_ut.c create mode 100644 src/spdk/test/unit/lib/ftl/ftl_reloc.c/.gitignore create mode 100644 src/spdk/test/unit/lib/ftl/ftl_reloc.c/Makefile create mode 100644 src/spdk/test/unit/lib/ftl/ftl_reloc.c/ftl_reloc_ut.c create mode 100644 src/spdk/test/unit/lib/ftl/ftl_wptr/.gitignore create mode 100644 src/spdk/test/unit/lib/ftl/ftl_wptr/Makefile create mode 100644 src/spdk/test/unit/lib/ftl/ftl_wptr/ftl_wptr_ut.c create mode 100644 src/spdk/test/unit/lib/idxd/Makefile create mode 100644 src/spdk/test/unit/lib/idxd/idxd.c/.gitignore create mode 100644 src/spdk/test/unit/lib/idxd/idxd.c/Makefile create mode 100644 src/spdk/test/unit/lib/idxd/idxd.c/idxd_ut.c create mode 100644 src/spdk/test/unit/lib/ioat/Makefile create mode 100644 src/spdk/test/unit/lib/ioat/ioat.c/.gitignore create mode 100644 src/spdk/test/unit/lib/ioat/ioat.c/Makefile create mode 100644 src/spdk/test/unit/lib/ioat/ioat.c/ioat_ut.c create mode 100644 src/spdk/test/unit/lib/iscsi/Makefile create mode 100644 src/spdk/test/unit/lib/iscsi/common.c create mode 100644 src/spdk/test/unit/lib/iscsi/conn.c/.gitignore create mode 100644 src/spdk/test/unit/lib/iscsi/conn.c/Makefile create mode 100644 src/spdk/test/unit/lib/iscsi/conn.c/conn_ut.c create mode 100644 src/spdk/test/unit/lib/iscsi/init_grp.c/.gitignore create mode 100644 src/spdk/test/unit/lib/iscsi/init_grp.c/Makefile create mode 100644 src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp.conf create mode 100644 src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp_ut.c create mode 100644 src/spdk/test/unit/lib/iscsi/iscsi.c/.gitignore create mode 100644 src/spdk/test/unit/lib/iscsi/iscsi.c/Makefile create mode 100644 src/spdk/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c create mode 100644 src/spdk/test/unit/lib/iscsi/param.c/.gitignore create mode 100644 src/spdk/test/unit/lib/iscsi/param.c/Makefile create mode 100644 src/spdk/test/unit/lib/iscsi/param.c/param_ut.c create mode 100644 src/spdk/test/unit/lib/iscsi/portal_grp.c/.gitignore create mode 100644 src/spdk/test/unit/lib/iscsi/portal_grp.c/Makefile create mode 100644 src/spdk/test/unit/lib/iscsi/portal_grp.c/portal_grp_ut.c create mode 100644 src/spdk/test/unit/lib/iscsi/tgt_node.c/.gitignore create mode 100644 src/spdk/test/unit/lib/iscsi/tgt_node.c/Makefile create mode 100644 src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node.conf create mode 100644 src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node_ut.c create mode 100644 src/spdk/test/unit/lib/json/Makefile create mode 100644 src/spdk/test/unit/lib/json/json_parse.c/.gitignore create mode 100644 src/spdk/test/unit/lib/json/json_parse.c/Makefile create mode 100644 src/spdk/test/unit/lib/json/json_parse.c/json_parse_ut.c create mode 100644 src/spdk/test/unit/lib/json/json_util.c/.gitignore create mode 100644 src/spdk/test/unit/lib/json/json_util.c/Makefile create mode 100644 src/spdk/test/unit/lib/json/json_util.c/json_util_ut.c create mode 100644 src/spdk/test/unit/lib/json/json_write.c/.gitignore create mode 100644 src/spdk/test/unit/lib/json/json_write.c/Makefile create mode 100644 src/spdk/test/unit/lib/json/json_write.c/json_write_ut.c create mode 100644 src/spdk/test/unit/lib/json_mock.c create mode 100644 src/spdk/test/unit/lib/jsonrpc/Makefile create mode 100644 src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/.gitignore create mode 100644 src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/Makefile create mode 100644 src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/jsonrpc_server_ut.c create mode 100644 src/spdk/test/unit/lib/log/Makefile create mode 100644 src/spdk/test/unit/lib/log/log.c/.gitignore create mode 100644 src/spdk/test/unit/lib/log/log.c/Makefile create mode 100644 src/spdk/test/unit/lib/log/log.c/log_ut.c create mode 100644 src/spdk/test/unit/lib/lvol/Makefile create mode 100644 src/spdk/test/unit/lib/lvol/lvol.c/.gitignore create mode 100644 src/spdk/test/unit/lib/lvol/lvol.c/Makefile create mode 100644 src/spdk/test/unit/lib/lvol/lvol.c/lvol_ut.c create mode 100644 src/spdk/test/unit/lib/notify/Makefile create mode 100644 src/spdk/test/unit/lib/notify/notify.c/.gitignore create mode 100644 src/spdk/test/unit/lib/notify/notify.c/Makefile create mode 100644 src/spdk/test/unit/lib/notify/notify.c/notify_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme.c/nvme_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/nvme_ctrlr_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/nvme_ctrlr_cmd_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/nvme_ctrlr_ocssd_cmd_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ns.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ns.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ns.c/nvme_ns_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/nvme_ns_cmd_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/nvme_ns_ocssd_cmd_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_pcie.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_pcie.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_pcie.c/nvme_pcie_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_poll_group.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_poll_group.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_poll_group.c/nvme_poll_group_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_qpair.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_qpair.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_qpair.c/nvme_qpair_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_quirks.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_quirks.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_quirks.c/nvme_quirks_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_rdma.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_rdma.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_rdma.c/nvme_rdma_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_tcp.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_tcp.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_tcp.c/nvme_tcp_ut.c create mode 100644 src/spdk/test/unit/lib/nvme/nvme_uevent.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvme/nvme_uevent.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvme/nvme_uevent.c/nvme_uevent_ut.c create mode 100644 src/spdk/test/unit/lib/nvmf/Makefile create mode 100644 src/spdk/test/unit/lib/nvmf/ctrlr.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvmf/ctrlr.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvmf/ctrlr.c/ctrlr_ut.c create mode 100644 src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/ctrlr_bdev_ut.c create mode 100644 src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/ctrlr_discovery_ut.c create mode 100644 src/spdk/test/unit/lib/nvmf/fc.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvmf/fc.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvmf/fc.c/fc_ut.c create mode 100644 src/spdk/test/unit/lib/nvmf/fc_ls.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvmf/fc_ls.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvmf/fc_ls.c/fc_ls_ut.c create mode 100644 src/spdk/test/unit/lib/nvmf/rdma.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvmf/rdma.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvmf/rdma.c/rdma_ut.c create mode 100644 src/spdk/test/unit/lib/nvmf/subsystem.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvmf/subsystem.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvmf/subsystem.c/subsystem_ut.c create mode 100644 src/spdk/test/unit/lib/nvmf/tcp.c/.gitignore create mode 100644 src/spdk/test/unit/lib/nvmf/tcp.c/Makefile create mode 100644 src/spdk/test/unit/lib/nvmf/tcp.c/tcp_ut.c create mode 100644 src/spdk/test/unit/lib/reduce/Makefile create mode 100644 src/spdk/test/unit/lib/reduce/reduce.c/.gitignore create mode 100644 src/spdk/test/unit/lib/reduce/reduce.c/Makefile create mode 100644 src/spdk/test/unit/lib/reduce/reduce.c/reduce_ut.c create mode 100644 src/spdk/test/unit/lib/scsi/Makefile create mode 100644 src/spdk/test/unit/lib/scsi/dev.c/.gitignore create mode 100644 src/spdk/test/unit/lib/scsi/dev.c/Makefile create mode 100644 src/spdk/test/unit/lib/scsi/dev.c/dev_ut.c create mode 100644 src/spdk/test/unit/lib/scsi/lun.c/.gitignore create mode 100644 src/spdk/test/unit/lib/scsi/lun.c/Makefile create mode 100644 src/spdk/test/unit/lib/scsi/lun.c/lun_ut.c create mode 100644 src/spdk/test/unit/lib/scsi/scsi.c/.gitignore create mode 100644 src/spdk/test/unit/lib/scsi/scsi.c/Makefile create mode 100644 src/spdk/test/unit/lib/scsi/scsi.c/scsi_ut.c create mode 100644 src/spdk/test/unit/lib/scsi/scsi_bdev.c/.gitignore create mode 100644 src/spdk/test/unit/lib/scsi/scsi_bdev.c/Makefile create mode 100644 src/spdk/test/unit/lib/scsi/scsi_bdev.c/scsi_bdev_ut.c create mode 100644 src/spdk/test/unit/lib/scsi/scsi_pr.c/.gitignore create mode 100644 src/spdk/test/unit/lib/scsi/scsi_pr.c/Makefile create mode 100644 src/spdk/test/unit/lib/scsi/scsi_pr.c/scsi_pr_ut.c create mode 100644 src/spdk/test/unit/lib/sock/Makefile create mode 100644 src/spdk/test/unit/lib/sock/posix.c/.gitignore create mode 100644 src/spdk/test/unit/lib/sock/posix.c/Makefile create mode 100644 src/spdk/test/unit/lib/sock/posix.c/posix_ut.c create mode 100644 src/spdk/test/unit/lib/sock/sock.c/.gitignore create mode 100644 src/spdk/test/unit/lib/sock/sock.c/Makefile create mode 100644 src/spdk/test/unit/lib/sock/sock.c/sock_ut.c create mode 100644 src/spdk/test/unit/lib/sock/uring.c/.gitignore create mode 100644 src/spdk/test/unit/lib/sock/uring.c/Makefile create mode 100644 src/spdk/test/unit/lib/sock/uring.c/uring_ut.c create mode 100644 src/spdk/test/unit/lib/thread/Makefile create mode 100644 src/spdk/test/unit/lib/thread/thread.c/.gitignore create mode 100644 src/spdk/test/unit/lib/thread/thread.c/Makefile create mode 100644 src/spdk/test/unit/lib/thread/thread.c/thread_ut.c create mode 100644 src/spdk/test/unit/lib/util/Makefile create mode 100644 src/spdk/test/unit/lib/util/base64.c/.gitignore create mode 100644 src/spdk/test/unit/lib/util/base64.c/Makefile create mode 100644 src/spdk/test/unit/lib/util/base64.c/base64_ut.c create mode 100644 src/spdk/test/unit/lib/util/bit_array.c/.gitignore create mode 100644 src/spdk/test/unit/lib/util/bit_array.c/Makefile create mode 100644 src/spdk/test/unit/lib/util/bit_array.c/bit_array_ut.c create mode 100644 src/spdk/test/unit/lib/util/cpuset.c/.gitignore create mode 100644 src/spdk/test/unit/lib/util/cpuset.c/Makefile create mode 100644 src/spdk/test/unit/lib/util/cpuset.c/cpuset_ut.c create mode 100644 src/spdk/test/unit/lib/util/crc16.c/.gitignore create mode 100644 src/spdk/test/unit/lib/util/crc16.c/Makefile create mode 100644 src/spdk/test/unit/lib/util/crc16.c/crc16_ut.c create mode 100644 src/spdk/test/unit/lib/util/crc32_ieee.c/.gitignore create mode 100644 src/spdk/test/unit/lib/util/crc32_ieee.c/Makefile create mode 100644 src/spdk/test/unit/lib/util/crc32_ieee.c/crc32_ieee_ut.c create mode 100644 src/spdk/test/unit/lib/util/crc32c.c/.gitignore create mode 100644 src/spdk/test/unit/lib/util/crc32c.c/Makefile create mode 100644 src/spdk/test/unit/lib/util/crc32c.c/crc32c_ut.c create mode 100644 src/spdk/test/unit/lib/util/dif.c/.gitignore create mode 100644 src/spdk/test/unit/lib/util/dif.c/Makefile create mode 100644 src/spdk/test/unit/lib/util/dif.c/dif_ut.c create mode 100644 src/spdk/test/unit/lib/util/iov.c/.gitignore create mode 100644 src/spdk/test/unit/lib/util/iov.c/Makefile create mode 100644 src/spdk/test/unit/lib/util/iov.c/iov_ut.c create mode 100644 src/spdk/test/unit/lib/util/math.c/.gitignore create mode 100644 src/spdk/test/unit/lib/util/math.c/Makefile create mode 100644 src/spdk/test/unit/lib/util/math.c/math_ut.c create mode 100644 src/spdk/test/unit/lib/util/pipe.c/.gitignore create mode 100644 src/spdk/test/unit/lib/util/pipe.c/Makefile create mode 100644 src/spdk/test/unit/lib/util/pipe.c/pipe_ut.c create mode 100644 src/spdk/test/unit/lib/util/string.c/.gitignore create mode 100644 src/spdk/test/unit/lib/util/string.c/Makefile create mode 100644 src/spdk/test/unit/lib/util/string.c/string_ut.c create mode 100644 src/spdk/test/unit/lib/vhost/Makefile create mode 100644 src/spdk/test/unit/lib/vhost/vhost.c/.gitignore create mode 100644 src/spdk/test/unit/lib/vhost/vhost.c/Makefile create mode 100644 src/spdk/test/unit/lib/vhost/vhost.c/vhost_ut.c create mode 100755 src/spdk/test/unit/unittest.sh create mode 100644 src/spdk/test/vhost/common.sh create mode 100644 src/spdk/test/vhost/common/autotest.config create mode 100644 src/spdk/test/vhost/common/fio_jobs/default_initiator.job create mode 100644 src/spdk/test/vhost/common/fio_jobs/default_integrity.job create mode 100644 src/spdk/test/vhost/common/fio_jobs/default_integrity_nightly.job create mode 100644 src/spdk/test/vhost/common/fio_jobs/default_performance.job create mode 100755 src/spdk/test/vhost/fio/fio.sh create mode 100644 src/spdk/test/vhost/fio/vhost_fio.job create mode 100755 src/spdk/test/vhost/fiotest/fio.sh create mode 100755 src/spdk/test/vhost/fuzz/fuzz.sh create mode 100644 src/spdk/test/vhost/hotplug/blk_hotremove.sh create mode 100644 src/spdk/test/vhost/hotplug/common.sh create mode 100644 src/spdk/test/vhost/hotplug/fio_jobs/default_integrity.job create mode 100755 src/spdk/test/vhost/hotplug/scsi_hotattach.sh create mode 100755 src/spdk/test/vhost/hotplug/scsi_hotdetach.sh create mode 100755 src/spdk/test/vhost/hotplug/scsi_hotplug.sh create mode 100644 src/spdk/test/vhost/hotplug/scsi_hotremove.sh create mode 100644 src/spdk/test/vhost/initiator/autotest.config create mode 100644 src/spdk/test/vhost/initiator/bdev.fio create mode 100644 src/spdk/test/vhost/initiator/bdev_pci.conf create mode 100755 src/spdk/test/vhost/initiator/blockdev.sh create mode 100755 src/spdk/test/vhost/integrity/integrity_start.sh create mode 100755 src/spdk/test/vhost/integrity/integrity_vm.sh create mode 100644 src/spdk/test/vhost/lvol/autotest.config create mode 100755 src/spdk/test/vhost/lvol/lvol_test.sh create mode 100755 src/spdk/test/vhost/manual.sh create mode 100644 src/spdk/test/vhost/migration/autotest.config create mode 100644 src/spdk/test/vhost/migration/migration-tc1.job create mode 100644 src/spdk/test/vhost/migration/migration-tc1.sh create mode 100644 src/spdk/test/vhost/migration/migration-tc2.job create mode 100644 src/spdk/test/vhost/migration/migration-tc2.sh create mode 100644 src/spdk/test/vhost/migration/migration-tc3.job create mode 100644 src/spdk/test/vhost/migration/migration-tc3a.sh create mode 100644 src/spdk/test/vhost/migration/migration-tc3b.sh create mode 100755 src/spdk/test/vhost/migration/migration.sh create mode 100644 src/spdk/test/vhost/other/invalid.config create mode 100755 src/spdk/test/vhost/other/negative.sh create mode 100755 src/spdk/test/vhost/perf_bench/vhost_perf.sh create mode 100755 src/spdk/test/vhost/readonly/delete_partition_vm.sh create mode 100755 src/spdk/test/vhost/readonly/disabled_readonly_vm.sh create mode 100755 src/spdk/test/vhost/readonly/enabled_readonly_vm.sh create mode 100755 src/spdk/test/vhost/readonly/readonly.sh create mode 100644 src/spdk/test/vhost/shared/bdev.json create mode 100755 src/spdk/test/vhost/shared/shared.sh create mode 100755 src/spdk/test/vhost/vhost.sh create mode 100755 src/spdk/test/vhost/vhost_boot/vhost_boot.sh create mode 100755 src/spdk/test/vhost/windows/windows.sh create mode 100644 src/spdk/test/vhost/windows/windows_fs_test.ps1 create mode 100644 src/spdk/test/vhost/windows/windows_scsi_compliance.ps1 create mode 100755 src/spdk/test/vhost/windows/windows_scsi_compliance.py create mode 100755 src/spdk/test/vhost/windows/windows_scsi_compliance.sh create mode 100644 src/spdk/test/vmd/config/config.fio create mode 100755 src/spdk/test/vmd/vmd.sh (limited to 'src/spdk/test') diff --git a/src/spdk/test/Makefile b/src/spdk/test/Makefile new file mode 100644 index 000000000..ec88de032 --- /dev/null +++ b/src/spdk/test/Makefile @@ -0,0 +1,48 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +# These directories contain tests. +TESTDIRS = app bdev blobfs cpp_headers env event nvme rpc_client + +DIRS-$(CONFIG_TESTS) += $(TESTDIRS) +DIRS-$(CONFIG_UNIT_TESTS) += unit + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/app/Makefile b/src/spdk/test/app/Makefile new file mode 100644 index 000000000..2eb259018 --- /dev/null +++ b/src/spdk/test/app/Makefile @@ -0,0 +1,44 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y += bdev_svc fuzz histogram_perf jsoncat stub + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/app/bdev_svc/.gitignore b/src/spdk/test/app/bdev_svc/.gitignore new file mode 100644 index 000000000..77ddb987e --- /dev/null +++ b/src/spdk/test/app/bdev_svc/.gitignore @@ -0,0 +1 @@ +bdev_svc diff --git a/src/spdk/test/app/bdev_svc/Makefile b/src/spdk/test/app/bdev_svc/Makefile new file mode 100644 index 000000000..1736d57f0 --- /dev/null +++ b/src/spdk/test/app/bdev_svc/Makefile @@ -0,0 +1,63 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = bdev_svc + +C_SRCS := bdev_svc.c + +SPDK_LIB_LIST = $(ALL_MODULES_LIST) +SPDK_LIB_LIST += $(EVENT_BDEV_SUBSYSTEM) +SPDK_LIB_LIST += nvmf event log trace conf thread util bdev accel rpc jsonrpc json sock blobfs_bdev +SPDK_LIB_LIST += app_rpc log_rpc bdev_rpc notify + +ifeq ($(OS),Linux) +SPDK_LIB_LIST += event_nbd nbd +endif + +ifeq ($(CONFIG_FC),y) +ifneq ($(strip $(CONFIG_FC_PATH)),) +SYS_LIBS += -L$(CONFIG_FC_PATH) +endif +SYS_LIBS += -lufc +endif + +# libfuse3 is required internally by blobfs_bdev +ifeq ($(CONFIG_FUSE),y) +LIBS+= -L/usr/local/lib -lfuse3 +endif + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/app/bdev_svc/bdev_svc.c b/src/spdk/test/app/bdev_svc/bdev_svc.c new file mode 100644 index 000000000..84580d3f6 --- /dev/null +++ b/src/spdk/test/app/bdev_svc/bdev_svc.c @@ -0,0 +1,112 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk/env.h" +#include "spdk/event.h" + +static char g_path[256]; +static bool g_unaffinitize_thread = false; + +static void +bdev_svc_usage(void) +{ +} + +static int +bdev_svc_parse_arg(int ch, char *arg) +{ + return 0; +} + +static void +bdev_svc_start(void *arg1) +{ + int fd; + int shm_id = (intptr_t)arg1; + + if (g_unaffinitize_thread) { + spdk_unaffinitize_thread(); + } + + snprintf(g_path, sizeof(g_path), "/var/run/spdk_bdev%d", shm_id); + fd = open(g_path, O_CREAT | O_EXCL | O_RDWR, S_IFREG); + if (fd < 0) { + fprintf(stderr, "could not create sentinel file %s\n", g_path); + exit(1); + } + close(fd); +} + +static void +bdev_svc_shutdown(void) +{ + unlink(g_path); + spdk_app_stop(0); +} + +int +main(int argc, char **argv) +{ + int rc; + struct spdk_app_opts opts = {}; + + /* default value in opts structure */ + spdk_app_opts_init(&opts); + + opts.name = "bdev_svc"; + opts.shutdown_cb = bdev_svc_shutdown; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "", NULL, + bdev_svc_parse_arg, bdev_svc_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + exit(rc); + } + + /* User did not specify a reactor mask. Test scripts may do this when using + * bdev_svc as a primary process to speed up nvme test programs by running + * them as secondary processes. In that case, we will unaffinitize the thread + * in the bdev_svc_start routine, which will allow the scheduler to move this + * thread so it doesn't conflict with pinned threads in the secondary processes. + */ + if (opts.reactor_mask == NULL) { + g_unaffinitize_thread = true; + } + + rc = spdk_app_start(&opts, bdev_svc_start, (void *)(intptr_t)opts.shm_id); + + spdk_app_fini(); + + return rc; +} diff --git a/src/spdk/test/app/fuzz/Makefile b/src/spdk/test/app/fuzz/Makefile new file mode 100644 index 000000000..1f0a81b92 --- /dev/null +++ b/src/spdk/test/app/fuzz/Makefile @@ -0,0 +1,48 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y += nvme_fuzz +DIRS-y += iscsi_fuzz + +ifeq ($(OS),Linux) +DIRS-$(CONFIG_VIRTIO) += vhost_fuzz +endif +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/app/fuzz/common/fuzz_common.h b/src/spdk/test/app/fuzz/common/fuzz_common.h new file mode 100644 index 000000000..7619f4fb1 --- /dev/null +++ b/src/spdk/test/app/fuzz/common/fuzz_common.h @@ -0,0 +1,303 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" +#include "spdk/env.h" +#include "spdk/file.h" +#include "spdk/base64.h" +#include "spdk/json.h" + +#define DEFAULT_RUNTIME 30 /* seconds */ +#define MAX_RUNTIME_S 86400 /* 24 hours */ +#define IO_TIMEOUT_S 5 + +#define UNSIGNED_2BIT_MAX ((1 << 2) - 1) +#define UNSIGNED_4BIT_MAX ((1 << 4) - 1) +#define UNSIGNED_8BIT_MAX ((1 << 8) - 1) + +typedef bool (*json_parse_fn)(void *ele, struct spdk_json_val *val, size_t num_vals); + +static void +fuzz_fill_random_bytes(char *character_repr, size_t len, unsigned int *rand_seed) +{ + size_t i; + + for (i = 0; i < len; i++) { + character_repr[i] = rand_r(rand_seed) % UINT8_MAX; + } +} + +static uint64_t +fuzz_refresh_timeout(void) +{ + uint64_t current_ticks; + uint64_t new_timeout_ticks; + + current_ticks = spdk_get_ticks(); + + new_timeout_ticks = current_ticks + IO_TIMEOUT_S * spdk_get_ticks_hz(); + assert(new_timeout_ticks > current_ticks); + + return new_timeout_ticks; +} + +static char * +fuzz_get_value_base_64_buffer(void *item, size_t len) +{ + char *value_string; + size_t total_size; + int rc; + + /* Null pointer */ + total_size = spdk_base64_get_encoded_strlen(len) + 1; + + value_string = calloc(1, total_size); + if (value_string == NULL) { + return NULL; + } + + rc = spdk_base64_encode(value_string, item, len); + if (rc < 0) { + free(value_string); + return NULL; + } + + return value_string; +} + +static int +fuzz_get_base_64_buffer_value(void *item, size_t len, char *buf, size_t buf_len) +{ + size_t size_of_data; + char *new_buf; + int rc; + + new_buf = malloc(buf_len + 1); + if (new_buf == NULL) { + return -ENOMEM; + } + + snprintf(new_buf, buf_len + 1, "%s", buf); + + size_of_data = spdk_base64_get_decoded_len(buf_len); + + if (size_of_data < len) { + free(new_buf); + return -EINVAL; + } + + rc = spdk_base64_decode(item, &size_of_data, new_buf); + + if (rc || size_of_data != len) { + free(new_buf); + return -EINVAL; + } + + free(new_buf); + return 0; +} + +static ssize_t +read_json_into_buffer(const char *filename, struct spdk_json_val **values, void **file_data) +{ + FILE *file = fopen(filename, "r"); + size_t file_data_size; + ssize_t num_json_values = 0, rc; + + if (file == NULL) { + /* errno is set by fopen */ + return 0; + } + + *file_data = spdk_posix_file_load(file, &file_data_size); + if (*file_data == NULL) { + fclose(file); + return 0; + } + + fclose(file); + + num_json_values = spdk_json_parse(*file_data, file_data_size, NULL, 0, NULL, + SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + + *values = calloc(num_json_values, sizeof(**values)); + if (values == NULL) { + free(*file_data); + *file_data = NULL; + return 0; + } + + rc = spdk_json_parse(*file_data, file_data_size, *values, num_json_values, NULL, + SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + if (num_json_values != rc) { + free(*values); + *values = NULL; + free(*file_data); + *file_data = NULL; + return 0; + } + + return num_json_values; +} + +static size_t +double_arr_size(void **buffer, size_t num_ele, size_t ele_size) +{ + void *tmp; + size_t new_num_ele, allocation_size; + + if (num_ele > SIZE_MAX / 2) { + return 0; + } + + new_num_ele = num_ele * 2; + + if (new_num_ele > SIZE_MAX / ele_size) { + return 0; + } + + allocation_size = new_num_ele * ele_size; + + tmp = realloc(*buffer, allocation_size); + if (tmp != NULL) { + *buffer = tmp; + return new_num_ele; + } + + return 0; +} + +static uint64_t +fuzz_parse_args_into_array(const char *file, void **arr, size_t ele_size, const char *obj_name, + json_parse_fn cb_fn) +{ + ssize_t i, num_json_values; + struct spdk_json_val *values = NULL, *values_head = NULL, *obj_start; + void *file_data = NULL;; + char *arr_idx_pointer; + size_t num_arr_elements, arr_elements_used, values_in_obj; + bool rc; + + num_json_values = read_json_into_buffer(file, &values_head, &file_data); + values = values_head; + if (num_json_values == 0 || values == NULL) { + if (file_data != NULL) { + free(file_data); + } + fprintf(stderr, "The file provided does not exist or we were unable to parse it.\n"); + return 0; + } + + num_arr_elements = 10; + arr_elements_used = 0; + *arr = calloc(num_arr_elements, ele_size); + arr_idx_pointer = (char *)*arr; + if (arr_idx_pointer == NULL) { + free(values); + free(file_data); + return 0; + } + + i = 0; + while (i < num_json_values) { + if (values->type != SPDK_JSON_VAL_NAME) { + i++; + values++; + continue; + } + + if (!strncmp(values->start, obj_name, values->len)) { + i++; + values++; + assert(values->type == SPDK_JSON_VAL_OBJECT_BEGIN); + obj_start = values; + values_in_obj = spdk_json_val_len(obj_start); + values += values_in_obj; + i += values_in_obj; + + rc = cb_fn((void *)arr_idx_pointer, obj_start, values_in_obj); + if (rc == false) { + fprintf(stderr, "failed to parse file after %lu elements.\n", arr_elements_used); + goto fail; + } + + arr_idx_pointer += ele_size; + arr_elements_used++; + if (arr_elements_used == num_arr_elements) { + num_arr_elements = double_arr_size(arr, num_arr_elements, ele_size); + if (num_arr_elements == 0) { + fprintf(stderr, "failed to allocate enough space for all json elements in your file.\n"); + goto fail; + } else { + /* reset the array element position in case the pointer changed. */ + arr_idx_pointer = ((char *)*arr) + arr_elements_used * ele_size; + } + } + + continue; + } else { + i++; + values++; + continue; + } + } + + if (arr_elements_used == 0) { + goto fail; + } + + free(values_head); + free(file_data); + return arr_elements_used; +fail: + free(values_head); + free(file_data); + free(*arr); + *arr = NULL; + return 0; +} + +static int +fuzz_parse_json_num(struct spdk_json_val *val, uint64_t max_val, uint64_t *val_ptr) +{ + uint64_t tmp_val; + int rc; + + rc = spdk_json_number_to_uint64(val, &tmp_val); + if (rc || tmp_val > max_val) { + return -EINVAL; + } else { + *val_ptr = tmp_val; + return 0; + } +} diff --git a/src/spdk/test/app/fuzz/common/fuzz_rpc.py b/src/spdk/test/app/fuzz/common/fuzz_rpc.py new file mode 100755 index 000000000..05cb67ed8 --- /dev/null +++ b/src/spdk/test/app/fuzz/common/fuzz_rpc.py @@ -0,0 +1,106 @@ +#!/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 _fuzz_vhost_create_dev(client, socket, is_blk, use_bogus_buffer, use_valid_buffer, test_scsi_tmf, valid_lun): + """Create a new device in the vhost fuzzer. + + Args: + socket: A valid unix domain socket for the dev to bind to. + is_blk: if set, create a virtio_blk device, otherwise use scsi. + use_bogus_buffer: if set, pass an invalid memory address as a buffer accompanying requests. + use_valid_buffer: if set, pass in a valid memory buffer with requests. Overrides use_bogus_buffer. + test_scsi_tmf: Test scsi management commands on the given device. Valid if and only if is_blk is false. + valid_lun: Supply only a valid lun number when submitting commands to the given device. Valid if and only if is_blk is false. + + Returns: + True or False + """ + + params = {"socket": socket, + "is_blk": is_blk, + "use_bogus_buffer": use_bogus_buffer, + "use_valid_buffer": use_valid_buffer, + "test_scsi_tmf": test_scsi_tmf, + "valid_lun": valid_lun} + + return client.call("fuzz_vhost_create_dev", 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 fuzz_vhost_create_dev(args): + _fuzz_vhost_create_dev( + args.client, + args.socket, + args.is_blk, + args.use_bogus_buffer, + args.use_valid_buffer, + args.test_scsi_tmf, + args.valid_lun) + + p = subparsers.add_parser('fuzz_vhost_create_dev', help="Add a new device to the vhost fuzzer.") + p.add_argument('-s', '--socket', help="Path to a valid unix domain socket for dev binding.") + p.add_argument('-b', '--is-blk', help='The specified socket corresponds to a vhost-blk dev.', action='store_true') + p.add_argument('-u', '--use-bogus-buffer', help='Pass bogus buffer addresses with requests when fuzzing.', action='store_true') + p.add_argument('-v', '--use-valid-buffer', help='Pass valid buffers when fuzzing. overrides use-bogus-buffer.', action='store_true') + p.add_argument('-m', '--test-scsi-tmf', help='for a scsi device, test scsi management commands.', action='store_true') + p.add_argument('-l', '--valid-lun', help='for a scsi device, test only using valid lun IDs.', action='store_true') + p.set_defaults(func=fuzz_vhost_create_dev) + + 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) diff --git a/src/spdk/test/app/fuzz/iscsi_fuzz/.gitignore b/src/spdk/test/app/fuzz/iscsi_fuzz/.gitignore new file mode 100644 index 000000000..3982bd220 --- /dev/null +++ b/src/spdk/test/app/fuzz/iscsi_fuzz/.gitignore @@ -0,0 +1 @@ +iscsi_fuzz diff --git a/src/spdk/test/app/fuzz/iscsi_fuzz/Makefile b/src/spdk/test/app/fuzz/iscsi_fuzz/Makefile new file mode 100644 index 000000000..0131e089a --- /dev/null +++ b/src/spdk/test/app/fuzz/iscsi_fuzz/Makefile @@ -0,0 +1,51 @@ +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = iscsi_fuzz + +CFLAGS += -I$(SPDK_ROOT_DIR)/lib +CFLAGS += -I$(SPDK_ROOT_DIR)/include + +C_SRCS := iscsi_fuzz.c + +ISCSI_OBJS = md5 param conn tgt_node init_grp portal_grp + +LIBS += $(SCSI_OBJS:%=$(SPDK_ROOT_DIR)/lib/scsi/%.o) +LIBS += $(ISCSI_OBJS:%=$(SPDK_ROOT_DIR)/lib/iscsi/%.o) + +SPDK_LIB_LIST += $(SOCK_MODULES_LIST) +SPDK_LIB_LIST += conf event json jsonrpc log scsi bdev notify rpc sock thread trace util + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/app/fuzz/iscsi_fuzz/README.md b/src/spdk/test/app/fuzz/iscsi_fuzz/README.md new file mode 100644 index 000000000..c9ef02e6a --- /dev/null +++ b/src/spdk/test/app/fuzz/iscsi_fuzz/README.md @@ -0,0 +1,27 @@ +# Overview + +This application is intended to fuzz test the iSCSI target by submitting +randomized PDU commands through a simulated iSCSI initiator. + +# Input + +1. iSCSI initiator send a login request PDU to iSCSI Target. Once a session is connected, +2. iSCSI initiator send huge amount and random PDUs continuously to iSCSI Target. +3. iSCSI initiator send a logout request PDU to iSCSI Target in the end. +Especially, iSCSI initiator need to build different bhs according to different bhs opcode. +Then iSCSI initiator will receive all kinds of responsed opcodes from iSCSI Target. +The application will terminate when run time expires (see the -t flag). + +# Output + +By default, the fuzzer will print commands that: +1. Complete successfully back from the target, or +2. Are outstanding at the time of a connection error occurs. +Commands are dumped as named objects in json format which can then be supplied back to the +script for targeted debugging on a subsequent run. + +At the end of each test run, a summary is printed in the following format: + +~~~ +device 0x11c3b90 stats: Sent 1543 valid opcode PDUs, 16215 invalid opcode PDUs. +~~~ diff --git a/src/spdk/test/app/fuzz/iscsi_fuzz/iscsi_fuzz.c b/src/spdk/test/app/fuzz/iscsi_fuzz/iscsi_fuzz.c new file mode 100644 index 000000000..359b95981 --- /dev/null +++ b/src/spdk/test/app/fuzz/iscsi_fuzz/iscsi_fuzz.c @@ -0,0 +1,1092 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" +#include "spdk/conf.h" +#include "spdk/event.h" +#include "spdk/util.h" +#include "spdk/string.h" +#include "spdk/likely.h" +#include "spdk/json.h" +#include "spdk/endian.h" +#include "spdk/bdev.h" +#include "spdk/notify.h" +#include "spdk/scsi.h" +#include "spdk_internal/mock.h" +#include "spdk/scsi_spec.h" +#include "iscsi/conn.h" +#include "iscsi/iscsi.c" +#include "scsi/scsi_internal.h" +#include "spdk/sock.h" + +#define GET_PDU_LOOP_COUNT 16 +#define DEFAULT_RUNTIME 30 /* seconds */ +#define MAX_RUNTIME_S 86400 /* 24 hours */ + +/* Global run state */ +uint64_t g_runtime_ticks; +int g_runtime; +int g_num_active_threads; +bool g_run = true; +bool g_is_valid_opcode = true; + +struct spdk_log_flag SPDK_LOG_ISCSI = { + .name = "iscsi", + .enabled = false, +}; + +/* Global resources */ +TAILQ_HEAD(, spdk_iscsi_pdu) g_get_pdu_list; +TAILQ_HEAD(, fuzz_iscsi_dev_ctx) g_dev_list = TAILQ_HEAD_INITIALIZER(g_dev_list); +struct spdk_poller *g_app_completion_poller; +void *g_valid_buffer; +unsigned int g_random_seed; +char *g_tgt_ip = "127.0.0.1"; +char *g_tgt_port = "3260"; +/* TBD: Discovery login to get target information. We use fixed IQN for target for now. */ +char *g_tgt_name = "iqn.2016-06.io.spdk:disk1"; +char *g_init_name = "iqn.2016-06.io.spdk:fuzzinit"; + +struct fuzz_iscsi_iov_ctx { + struct iovec iov_req; + struct iovec iov_data; + struct iovec iov_resp; +}; + +struct fuzz_iscsi_io_ctx { + struct fuzz_iscsi_iov_ctx iov_ctx; + union { + struct iscsi_bhs *bhs; + struct iscsi_bhs_nop_out *nop_out_req; + struct iscsi_bhs_scsi_req *scsi_req; + struct iscsi_bhs_task_req *task_req; + struct iscsi_bhs_login_req *login_req; + struct iscsi_bhs_text_req *text_req; + struct iscsi_bhs_data_out *data_out_req; + struct iscsi_bhs_logout_req *logout_req; + struct iscsi_bhs_snack_req *snack_req; + } req; +}; + +struct fuzz_iscsi_dev_ctx { + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn *conn; + struct fuzz_iscsi_io_ctx io_ctx; + + struct spdk_thread *thread; + struct spdk_poller *poller; + unsigned int random_seed, current_cmd_sn; + uint64_t num_sent_pdus; + uint64_t num_valid_pdus; + + TAILQ_ENTRY(fuzz_iscsi_dev_ctx) link; +}; + +static void +fuzz_fill_random_bytes(char *character_repr, size_t len, unsigned int *rand_seed) +{ + size_t i; + + for (i = 0; i < len; i++) { + character_repr[i] = rand_r(rand_seed) % UINT8_MAX; + } +} + +static char * +fuzz_get_value_base_64_buffer(void *item, size_t len) +{ + char *value_string; + size_t total_size; + int rc; + + /* Null pointer */ + total_size = spdk_base64_get_encoded_strlen(len) + 1; + + value_string = calloc(1, total_size); + if (value_string == NULL) { + return NULL; + } + + rc = spdk_base64_encode(value_string, item, len); + if (rc < 0) { + free(value_string); + return NULL; + } + + return value_string; +} + +int +iscsi_chap_get_authinfo(struct iscsi_chap_auth *auth, const char *authuser, + int ag_tag) +{ + return 0; +} + +void +shutdown_iscsi_conns_done(void) +{ + return; +} + +void +iscsi_put_pdu(struct spdk_iscsi_pdu *pdu) +{ + if (!pdu) { + return; + } + + pdu->ref--; + if (pdu->ref < 0) { + pdu->ref = 0; + } + + if (pdu->ref == 0) { + if (pdu->data) { + free(pdu->data); + } + free(pdu); + } +} + +struct spdk_iscsi_pdu * +iscsi_get_pdu(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_pdu *pdu; + + pdu = calloc(1, sizeof(*pdu)); + if (!pdu) { + return NULL; + } + + pdu->ref = 1; + pdu->conn = conn; + + return pdu; +} + +static void +iscsi_task_free(struct spdk_scsi_task *scsi_task) +{ + struct spdk_iscsi_task *task = iscsi_task_from_scsi_task(scsi_task); + + assert(task->parent == NULL); + + iscsi_task_disassociate_pdu(task); + assert(task->conn->pending_task_cnt > 0); + task->conn->pending_task_cnt--; + free(task); +} + +struct spdk_iscsi_task * +iscsi_task_get(struct spdk_iscsi_conn *conn, struct spdk_iscsi_task *parent, + spdk_scsi_task_cpl cpl_fn) +{ + struct spdk_iscsi_task *task; + + /* iSCSI subtask is not necessary for now. */ + assert(parent == NULL); + + task = calloc(1, sizeof(*task)); + if (!task) { + printf("Unable to get task\n"); + abort(); + } + + task->conn = conn; + assert(conn->pending_task_cnt < UINT32_MAX); + conn->pending_task_cnt++; + spdk_scsi_task_construct(&task->scsi, cpl_fn, iscsi_task_free); + + return task; +} + +static void +cleanup(void) +{ + struct fuzz_iscsi_dev_ctx *dev_ctx, *tmp; + + TAILQ_FOREACH_SAFE(dev_ctx, &g_dev_list, link, tmp) { + printf("device %p stats: Sent %lu valid opcode PDUs, %lu invalid opcode PDUs.\n", + dev_ctx, dev_ctx->num_valid_pdus, + dev_ctx->num_sent_pdus - dev_ctx->num_valid_pdus); + free(dev_ctx); + } + + spdk_free(g_valid_buffer); +} + +/* data dumping functions begin */ +static int +dump_iscsi_cmd(void *ctx, const void *data, size_t size) +{ + fprintf(stderr, "%s\n", (const char *)data); + return 0; +} + +static void +print_scsi_io_data(struct spdk_json_write_ctx *w, struct fuzz_iscsi_io_ctx *io_ctx) +{ + char *data_segment_len; + + data_segment_len = fuzz_get_value_base_64_buffer((void *)io_ctx->req.bhs->data_segment_len, + sizeof(io_ctx->req.bhs->data_segment_len)); + + spdk_json_write_named_uint32(w, "opcode", io_ctx->req.bhs->opcode); + spdk_json_write_named_uint32(w, "immediate", io_ctx->req.bhs->immediate); + spdk_json_write_named_uint32(w, "reserved", io_ctx->req.bhs->reserved); + spdk_json_write_named_uint32(w, "total_ahs_len", io_ctx->req.bhs->total_ahs_len); + spdk_json_write_named_string(w, "data_segment_len", data_segment_len); + spdk_json_write_named_uint32(w, "itt", io_ctx->req.bhs->itt); + spdk_json_write_named_uint32(w, "exp_stat_sn", io_ctx->req.bhs->exp_stat_sn); + + free(data_segment_len); +} + +static void +print_req_obj(struct fuzz_iscsi_dev_ctx *dev_ctx, struct fuzz_iscsi_io_ctx *io_ctx) +{ + struct spdk_json_write_ctx *w; + + w = spdk_json_write_begin(dump_iscsi_cmd, NULL, SPDK_JSON_WRITE_FLAG_FORMATTED); + spdk_json_write_named_object_begin(w, "bhs"); + print_scsi_io_data(w, io_ctx); + spdk_json_write_object_end(w); + spdk_json_write_end(w); +} + +/* data dumping functions end */ + +/* dev initialization begin */ +static int +fuzz_iscsi_dev_init(void) +{ + struct fuzz_iscsi_dev_ctx *dev_ctx; + int rc = 0; + + dev_ctx = calloc(1, sizeof(*dev_ctx)); + if (dev_ctx == NULL) { + return -ENOMEM; + } + + dev_ctx->thread = spdk_get_thread(); + if (dev_ctx->thread == NULL) { + fprintf(stderr, "Unable to get a thread for a fuzz device.\n"); + rc = -EINVAL; + goto error_out; + } + + dev_ctx->current_cmd_sn = 0; + + TAILQ_INSERT_TAIL(&g_dev_list, dev_ctx, link); + return 0; + +error_out: + free(dev_ctx); + return rc; +} +/* dev initialization end */ + +/* build requests begin */ +static void +prep_iscsi_pdu_bhs_opcode_cmd(struct fuzz_iscsi_dev_ctx *dev_ctx, struct fuzz_iscsi_io_ctx *io_ctx) +{ + io_ctx->iov_ctx.iov_req.iov_len = sizeof(struct iscsi_bhs); + fuzz_fill_random_bytes((char *)io_ctx->req.bhs, sizeof(struct iscsi_bhs), + &dev_ctx->random_seed); +} +/* build requests end */ + +static int +iscsi_pdu_hdr_op_login_rsp(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + return 0; +} + +static int +iscsi_fuzz_pdu_hdr_handle(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + int opcode; + int rc = 0; + + opcode = pdu->bhs.opcode; + if (opcode == ISCSI_OP_LOGIN_RSP) { + return iscsi_pdu_hdr_op_login_rsp(conn, pdu); + } + + switch (opcode) { + case ISCSI_OP_LOGOUT_RSP: + fprintf(stderr, "Received logout hdr_handle response opcode(0x26) from Target.\n"); + conn->is_logged_out = true; + break; + case ISCSI_OP_NOPIN: + case ISCSI_OP_SCSI_RSP: + case ISCSI_OP_TASK_RSP: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_SCSI_DATAIN: + case ISCSI_OP_R2T: + case ISCSI_OP_ASYNC: + case ISCSI_OP_VENDOR_3C: + case ISCSI_OP_VENDOR_3D: + case ISCSI_OP_VENDOR_3E: + fprintf(stderr, "Received hdr_handle response opcode from Target is 0x%x.\n", pdu->bhs.opcode); + break; + case ISCSI_OP_REJECT: + fprintf(stderr, "Received rejected hdr_handle response opcode(0x3f) from Target.\n"); + break; + default: + rc = -1; + break; + } + + return rc; +} + +static int +iscsi_pdu_payload_op_login_rsp(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct iscsi_bhs_login_rsp *rsph; + + rsph = (struct iscsi_bhs_login_rsp *)&pdu->bhs; + if (rsph == NULL) { + return -1; + } + + assert(rsph->tsih != 0); + assert(rsph->status_class == 0); + assert(ISCSI_BHS_LOGIN_GET_TBIT(rsph->flags)); + assert(!(rsph->flags & ISCSI_LOGIN_CONTINUE)); + assert((rsph->flags & ISCSI_LOGIN_NEXT_STAGE_MASK) == ISCSI_LOGIN_NEXT_STAGE_3); + + /* We got the Login Final Response and move to Full-Feature Phase. */ + conn->full_feature = 1; + return 0; +} + +static int +iscsi_fuzz_pdu_payload_handle(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + int opcode; + int rc = 0; + + opcode = pdu->bhs.opcode; + fprintf(stderr, "Received payload_handle response opcode from Target is 0x%x.\n", opcode); + + switch (opcode) { + case ISCSI_OP_LOGIN_RSP: + rc = iscsi_pdu_payload_op_login_rsp(conn, pdu); + break; + case ISCSI_OP_NOPIN: + case ISCSI_OP_SCSI_RSP: + case ISCSI_OP_TASK_RSP: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_SCSI_DATAIN: + case ISCSI_OP_R2T: + case ISCSI_OP_ASYNC: + case ISCSI_OP_VENDOR_3C: + case ISCSI_OP_VENDOR_3D: + case ISCSI_OP_VENDOR_3E: + case ISCSI_OP_REJECT: + break; + default: + rc = -1; + break; + } + + return rc; +} + +static int +iscsi_fuzz_read_pdu(struct spdk_iscsi_conn *conn) +{ + enum iscsi_pdu_recv_state prev_state; + struct spdk_iscsi_pdu *pdu; + uint32_t data_len; + int rc; + + do { + prev_state = conn->pdu_recv_state; + pdu = conn->pdu_in_progress; + + switch (conn->pdu_recv_state) { + case ISCSI_PDU_RECV_STATE_AWAIT_PDU_READY: + assert(conn->pdu_in_progress == NULL); + + conn->pdu_in_progress = iscsi_get_pdu(conn); + if (conn->pdu_in_progress == NULL) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + TAILQ_INSERT_TAIL(&g_get_pdu_list, conn->pdu_in_progress, tailq); + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_HDR; + break; + case ISCSI_PDU_RECV_STATE_AWAIT_PDU_HDR: + if (pdu->bhs_valid_bytes < ISCSI_BHS_LEN) { + rc = iscsi_conn_read_data(conn, + ISCSI_BHS_LEN - pdu->bhs_valid_bytes, + (uint8_t *)&pdu->bhs + pdu->bhs_valid_bytes); + if (rc < 0) { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + pdu->bhs_valid_bytes += rc; + if (pdu->bhs_valid_bytes < ISCSI_BHS_LEN) { + return 0; + } + } + + pdu->data_segment_len = ISCSI_ALIGN(DGET24(pdu->bhs.data_segment_len)); + + rc = iscsi_fuzz_pdu_hdr_handle(conn, pdu); + if (rc < 0) { + printf("Critical error is detected. Close the connection\n"); + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + + if (conn->is_logged_out) { + printf("pdu received after logout\n"); + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_PAYLOAD; + break; + case ISCSI_PDU_RECV_STATE_AWAIT_PDU_PAYLOAD: + data_len = pdu->data_segment_len; + if (data_len != 0 && pdu->data_buf == NULL) { + pdu->data_buf = calloc(1, data_len); + if (pdu->data_buf == NULL) { + return 0; + } + pdu->data = pdu->data_buf; + } + + /* copy the actual data into local buffer */ + if (pdu->data_valid_bytes < data_len) { + rc = iscsi_conn_read_data_segment(conn, pdu, data_len); + if (rc < 0) { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + pdu->data_valid_bytes += rc; + if (pdu->data_valid_bytes < data_len) { + return 0; + } + } + + /* All data for this PDU has now been read from the socket. */ + spdk_trace_record(TRACE_ISCSI_READ_PDU, conn->id, pdu->data_valid_bytes, + (uintptr_t)pdu, pdu->bhs.opcode); + + if (!pdu->is_rejected) { + rc = iscsi_fuzz_pdu_payload_handle(conn, pdu); + } else { + rc = 0; + } + if (rc == 0) { + spdk_trace_record(TRACE_ISCSI_TASK_EXECUTED, 0, 0, (uintptr_t)pdu, 0); + conn->pdu_in_progress = NULL; + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_READY; + return 1; + } else { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + } + break; + case ISCSI_PDU_RECV_STATE_ERROR: + return SPDK_ISCSI_CONNECTION_FATAL; + default: + assert(false); + printf("code should not come here\n"); + break; + } + } while (prev_state != conn->pdu_recv_state); + + return 0; +} + +#define GET_PDU_LOOP_COUNT 16 + +static int +fuzz_iscsi_handle_incoming_pdus(struct spdk_iscsi_conn *conn) +{ + int i, rc; + + /* Read new PDUs from network */ + for (i = 0; i < GET_PDU_LOOP_COUNT; i++) { + rc = iscsi_fuzz_read_pdu(conn); + if (rc == 0) { + break; + } else if (rc < 0) { + return rc; + } + } + + return i; +} + +static void +fuzz_iscsi_send_login_request(struct fuzz_iscsi_dev_ctx *dev_ctx, uint8_t session_type) +{ + struct fuzz_iscsi_io_ctx *io_ctx = NULL; + struct spdk_iscsi_pdu *req_pdu; + struct iscsi_bhs_login_req *login_req; + struct spdk_iscsi_conn *conn = dev_ctx->conn; + + req_pdu = iscsi_get_pdu(conn); + req_pdu->writev_offset = 0; + req_pdu->hdigest_valid_bytes = 0; + req_pdu->ahs_valid_bytes = 0; + req_pdu->data_buf_len = 8192; + req_pdu->data = calloc(1, 8192); + assert(req_pdu->data != NULL); + req_pdu->data_segment_len = 0; + + login_req = (struct iscsi_bhs_login_req *)&req_pdu->bhs; + io_ctx = &dev_ctx->io_ctx; + io_ctx->req.login_req = login_req; + io_ctx->req.login_req->version_min = 0; + /* a new session */ + io_ctx->req.login_req->tsih = 0; + + req_pdu->bhs.opcode = ISCSI_OP_LOGIN; + req_pdu->bhs.immediate = 1; + req_pdu->bhs.reserved = 0; + req_pdu->bhs_valid_bytes = ISCSI_BHS_LEN; + req_pdu->bhs.total_ahs_len = 0; + + /* An initiator that chooses to operate without iSCSI security and with + * all the operational parameters taking the default values issues the + * Login with the T bit set to 1, the CSG set to + * LoginOperationalNegotiation, and the NSG set to FullFeaturePhase. + * + * Byte / 0 | 1 | 2 | 3 | + * |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| + * +---------------+---------------+---------------+---------------+ + * 0|.|1| 0x03 |T|C|.|.|CSG|NSG| Version-max | Version-min | + */ + req_pdu->bhs.flags = ISCSI_LOGIN_TRANSIT | (ISCSI_OPERATIONAL_NEGOTIATION_PHASE << 2) | + ISCSI_FULL_FEATURE_PHASE; + + req_pdu->data_segment_len = iscsi_append_text(conn, "InitiatorName", g_init_name, + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "HeaderDigest", "None", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "DataDigest", "None", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "DefaultTime2Wait", "2", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "DefaultTime2Retain", "0", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "IFMarker", "No", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "OFMarker", "No", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "ErrorRecoveryLevel", "0", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + + if (session_type == SESSION_TYPE_DISCOVERY) { + /* Discovery PDU */ + conn->sess->session_type = SESSION_TYPE_DISCOVERY; + req_pdu->data_segment_len = iscsi_append_text(conn, "SessionType", "Discovery", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "MaxRecvDataSegmentLength", "32768", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + } else { + /* Login PDU */ + conn->sess->session_type = SESSION_TYPE_NORMAL; + req_pdu->data_segment_len = iscsi_append_text(conn, "SessionType", "Normal", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "TargetName", g_tgt_name, + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "InitialR2T", "No", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "ImmediateData", "Yes", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "MaxBurstLength", "16776192", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "FirstBurstLength", "262144", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "MaxOutstandingR2T", "1", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "MaxConnections", "1", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "DataPDUInOrder", "Yes", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "DataSequenceInOrder", "Yes", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "MaxRecvDataSegmentLength", "262144", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + } + + DSET24(req_pdu->bhs.data_segment_len, req_pdu->data_segment_len); + iscsi_conn_write_pdu(conn, req_pdu, iscsi_conn_pdu_generic_complete, NULL); +} + +static void +fuzz_iscsi_send_logout_request(struct fuzz_iscsi_dev_ctx *dev_ctx) +{ + struct fuzz_iscsi_io_ctx *io_ctx = NULL; + struct spdk_iscsi_pdu *req_pdu; + struct iscsi_bhs_logout_req *logout_req; + struct spdk_iscsi_conn *conn = dev_ctx->conn; + + conn->is_logged_out = true; + + req_pdu = iscsi_get_pdu(conn); + req_pdu->writev_offset = 0; + req_pdu->hdigest_valid_bytes = 0; + req_pdu->ahs_valid_bytes = 0; + req_pdu->data_buf_len = 0; + + logout_req = (struct iscsi_bhs_logout_req *)&req_pdu->bhs; + io_ctx = &dev_ctx->io_ctx; + io_ctx->req.logout_req = logout_req; + + req_pdu->bhs.opcode = ISCSI_OP_LOGOUT; + req_pdu->bhs.immediate = 1; + req_pdu->bhs.reserved = 0; + req_pdu->bhs_valid_bytes = ISCSI_BHS_LEN; + req_pdu->bhs.total_ahs_len = 0; + req_pdu->bhs.flags = 0; + + DSET24(req_pdu->bhs.data_segment_len, 0); + iscsi_conn_write_pdu(conn, req_pdu, iscsi_conn_pdu_generic_complete, conn); +} + +static void +iscsi_fuzz_conn_reset(struct spdk_iscsi_conn *conn, struct spdk_iscsi_sess *sess) +{ + conn->sess = sess; + conn->data_in_cnt = 0; + conn->params = NULL; + conn->header_digest = true; + conn->data_digest = false; + conn->header_digest = 0; + conn->MaxRecvDataSegmentLength = 8192; + conn->full_feature = 0; + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_READY; + conn->pdu_in_progress = NULL; + conn->is_logged_out = 0; +} + +static void +iscsi_fuzz_sock_connect(struct spdk_iscsi_conn *conn) +{ + const char *host = g_tgt_ip; + const char *port = g_tgt_port; + char saddr[INET6_ADDRSTRLEN], caddr[INET6_ADDRSTRLEN]; + uint16_t cport, sport; + int rc = 0; + + conn->sock = spdk_sock_connect(host, spdk_strtol(port, 10), NULL); + if (conn->sock == NULL) { + fprintf(stderr, "connect error(%d): %s\n", errno, spdk_strerror(errno)); + spdk_sock_close(&conn->sock); + return; + } + fprintf(stderr, "\nConnecting to the server on %s:%s\n", host, port); + + rc = spdk_sock_getaddr(conn->sock, saddr, sizeof(saddr), &sport, caddr, sizeof(caddr), &cport); + if (rc < 0) { + fprintf(stderr, "Cannot get connection addresses\n"); + spdk_sock_close(&conn->sock); + return; + } + + fprintf(stderr, "Connection accepted from (%s, %hu) to (%s, %hu)\n", caddr, cport, saddr, sport); + +} + +static void +check_successful_op(struct fuzz_iscsi_dev_ctx *dev_ctx, struct fuzz_iscsi_io_ctx *io_ctx) +{ + if (g_is_valid_opcode) { + fprintf(stderr, "Sent a valid opcode PDU.\n"); + dev_ctx->num_valid_pdus++; + } else { + fprintf(stderr, "Sent an invalid opcode PDU.\n"); + } +} + +/* submit requests begin */ +static void +dev_submit_requests(struct fuzz_iscsi_dev_ctx *dev_ctx) +{ + struct fuzz_iscsi_io_ctx *io_ctx = NULL; + uint8_t opcode; + struct spdk_iscsi_pdu *req_pdu; + struct iscsi_bhs *bhs; + struct iscsi_bhs_nop_out *nop_out_req; + struct iscsi_bhs_scsi_req *scsi_req; + struct iscsi_bhs_task_req *task_req; + struct iscsi_bhs_text_req *text_req; + struct iscsi_bhs_data_out *data_out_req; + struct iscsi_bhs_snack_req *snack_req; + unsigned int rand_seed; + bool is_p99; + + g_is_valid_opcode = true; + + /* Random PDU */ + opcode = rand() % 0x3f; + fprintf(stderr, "Random request bhs.opcode of Initiator is 0x%x.\n", opcode); + + if ((opcode == ISCSI_OP_LOGIN) || (opcode == ISCSI_OP_LOGOUT)) { + /* only need send next */ + fprintf(stderr, "LOGIN and LOGOUT opcodes are ignored here.\n"); + return; + } + + req_pdu = iscsi_get_pdu(dev_ctx->conn); + req_pdu->writev_offset = 0; + req_pdu->hdigest_valid_bytes = 0; + req_pdu->ahs_valid_bytes = 0; + req_pdu->data_buf_len = 0; + + dev_ctx->conn->sess->session_type = SESSION_TYPE_NORMAL; + + io_ctx = &dev_ctx->io_ctx; + + switch (opcode) { + case ISCSI_OP_NOPOUT: + nop_out_req = (struct iscsi_bhs_nop_out *)&req_pdu->bhs; + io_ctx->req.nop_out_req = nop_out_req; + break; + case ISCSI_OP_SCSI: + scsi_req = (struct iscsi_bhs_scsi_req *)&req_pdu->bhs; + io_ctx->req.scsi_req = scsi_req; + break; + case ISCSI_OP_TASK: + task_req = (struct iscsi_bhs_task_req *)&req_pdu->bhs; + io_ctx->req.task_req = task_req; + break; + case ISCSI_OP_TEXT: + text_req = (struct iscsi_bhs_text_req *)&req_pdu->bhs; + io_ctx->req.text_req = text_req; + break; + case ISCSI_OP_SCSI_DATAOUT: + data_out_req = (struct iscsi_bhs_data_out *)&req_pdu->bhs; + io_ctx->req.data_out_req = data_out_req; + break; + case ISCSI_OP_SNACK: + snack_req = (struct iscsi_bhs_snack_req *)&req_pdu->bhs; + io_ctx->req.snack_req = snack_req; + break; + default: + bhs = (struct iscsi_bhs *)&req_pdu->bhs; + io_ctx->req.bhs = bhs; + g_is_valid_opcode = false; + break; + } + + prep_iscsi_pdu_bhs_opcode_cmd(dev_ctx, io_ctx); + io_ctx->req.bhs->opcode = opcode; + req_pdu->bhs.opcode = opcode; + req_pdu->bhs.immediate = 1; + req_pdu->bhs.reserved = 0; + req_pdu->bhs_valid_bytes = ISCSI_BHS_LEN; + req_pdu->bhs.total_ahs_len = 0; + req_pdu->bhs.stat_sn = 0; + DSET24(req_pdu->bhs.data_segment_len, 0); + + if (opcode <= ISCSI_OP_TEXT) { + rand_seed = time(NULL); + is_p99 = rand_r(&rand_seed) % 100 == 0 ? true : false; + if (!is_p99) { /* Remaining 1% */ + switch (opcode) { + case ISCSI_OP_NOPOUT: + if (req_pdu->bhs.immediate) { + io_ctx->req.nop_out_req->cmd_sn = dev_ctx->current_cmd_sn; + } else { + io_ctx->req.nop_out_req->cmd_sn = dev_ctx->current_cmd_sn++; + } + break; + case ISCSI_OP_SCSI: + if (req_pdu->bhs.immediate) { + io_ctx->req.scsi_req->cmd_sn = dev_ctx->current_cmd_sn; + } else { + io_ctx->req.scsi_req->cmd_sn = dev_ctx->current_cmd_sn++; + } + break; + case ISCSI_OP_TASK: + if (req_pdu->bhs.immediate) { + io_ctx->req.task_req->cmd_sn = dev_ctx->current_cmd_sn; + } else { + io_ctx->req.task_req->cmd_sn = dev_ctx->current_cmd_sn++; + } + break; + case ISCSI_OP_TEXT: + if (req_pdu->bhs.immediate) { + io_ctx->req.text_req->cmd_sn = dev_ctx->current_cmd_sn; + } else { + io_ctx->req.text_req->cmd_sn = dev_ctx->current_cmd_sn++; + } + break; + default: + break; + } + } + } + + if (opcode == ISCSI_OP_SCSI) { + /* avoid ((R_bit != 0) && (W_bit != 0)) is true */ + io_ctx->req.scsi_req->read_bit = 0; + io_ctx->req.scsi_req->write_bit = 0; + } + + if (opcode == ISCSI_OP_TEXT) { + /* avoid: (F_bit && C_bit) is true */ + io_ctx->req.text_req->flags = 0; + /* avoid: correct itt is not equal to the current itt */ + io_ctx->req.text_req->itt = 0; + } + + fprintf(stderr, "Dumping this request bhs contents now.\n"); + print_req_obj(dev_ctx, io_ctx); + + check_successful_op(dev_ctx, io_ctx); + dev_ctx->num_sent_pdus++; + + iscsi_conn_write_pdu(dev_ctx->conn, req_pdu, + iscsi_conn_pdu_generic_complete, NULL); +} +/* submit requests end */ + +static int +poll_dev(void *ctx) +{ + struct fuzz_iscsi_dev_ctx *dev_ctx = ctx; + struct spdk_iscsi_conn *conn = dev_ctx->conn; + uint64_t current_ticks; + struct spdk_iscsi_pdu *pdu, *tmp; + + current_ticks = spdk_get_ticks(); + if (current_ticks > g_runtime_ticks) { + g_run = false; + } + + if (!g_run) { + /* Logout PDU */ + fuzz_iscsi_send_logout_request(dev_ctx); + fuzz_iscsi_handle_incoming_pdus(conn); + + TAILQ_FOREACH_SAFE(pdu, &g_get_pdu_list, tailq, tmp) { + TAILQ_REMOVE(&g_get_pdu_list, pdu, tailq); + iscsi_put_pdu(pdu); + } + + spdk_sock_close(&conn->sock); + + TAILQ_FOREACH_SAFE(pdu, &conn->write_pdu_list, tailq, tmp) { + TAILQ_REMOVE(&conn->write_pdu_list, pdu, tailq); + iscsi_put_pdu(pdu); + } + + free(conn); + + spdk_poller_unregister(&dev_ctx->poller); + __sync_sub_and_fetch(&g_num_active_threads, 1); + + return -1; + } + + if (conn->is_logged_out) { + spdk_sock_close(&conn->sock); + iscsi_fuzz_conn_reset(conn, &dev_ctx->sess); + iscsi_fuzz_sock_connect(conn); + usleep(1000); + + /* Login PDU */ + fuzz_iscsi_send_login_request(dev_ctx, SESSION_TYPE_NORMAL); + } else if (conn->full_feature == 1) { + dev_submit_requests(dev_ctx); + } + + spdk_sock_flush(conn->sock); + + fuzz_iscsi_handle_incoming_pdus(conn); + + return 0; +} + +static void +start_io(void *ctx) +{ + struct fuzz_iscsi_dev_ctx *dev_ctx = ctx; + + dev_ctx->sess.ExpCmdSN = 0; + dev_ctx->sess.MaxCmdSN = 64; + dev_ctx->sess.MaxBurstLength = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + dev_ctx->sess.MaxOutstandingR2T = 1; + dev_ctx->sess.tag = 1; + dev_ctx->sess.tsih = 256; + + dev_ctx->conn = calloc(1, sizeof(*dev_ctx->conn)); + assert(dev_ctx->conn != NULL); + TAILQ_INIT(&dev_ctx->conn->write_pdu_list); + + iscsi_fuzz_conn_reset(dev_ctx->conn, &dev_ctx->sess); + iscsi_fuzz_sock_connect(dev_ctx->conn); + usleep(1000); + + /* Login PDU */ + fuzz_iscsi_send_login_request(dev_ctx, SESSION_TYPE_NORMAL); + + if (g_random_seed) { + dev_ctx->random_seed = g_random_seed; + } else { + dev_ctx->random_seed = spdk_get_ticks(); + } + + dev_ctx->poller = SPDK_POLLER_REGISTER(poll_dev, dev_ctx, 0); + if (dev_ctx->poller == NULL) { + return; + } +} + +static int +check_app_completion(void *ctx) +{ + if (g_num_active_threads == 0) { + spdk_poller_unregister(&g_app_completion_poller); + printf("Fuzzing completed. Shutting down the fuzz application.\n\n"); + cleanup(); + spdk_app_stop(0); + } + return 0; +} + +static void +begin_iscsi_fuzz(void *ctx) +{ + struct fuzz_iscsi_dev_ctx *dev_ctx; + int rc; + + g_runtime_ticks = spdk_get_ticks() + g_runtime * spdk_get_ticks_hz(); + + g_valid_buffer = spdk_malloc(0x1000, 0x200, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_SHARE); + if (g_valid_buffer == NULL) { + fprintf(stderr, "Failed to allocate a valid buffer for random PDUs\n"); + goto out; + } + + rc = fuzz_iscsi_dev_init(); + if (rc) { + fprintf(stderr, "fuzz_iscsi_dev_init() failed.\n"); + goto out; + } + + TAILQ_FOREACH(dev_ctx, &g_dev_list, link) { + assert(dev_ctx->thread != NULL); + spdk_thread_send_msg(dev_ctx->thread, start_io, dev_ctx); + __sync_add_and_fetch(&g_num_active_threads, 1); + } + + g_app_completion_poller = SPDK_POLLER_REGISTER(check_app_completion, NULL, 1000000); + if (g_app_completion_poller == NULL) { + fprintf(stderr, "Failed to register a poller for test completion checking.\n"); + goto out; + } + + return; +out: + cleanup(); + spdk_app_stop(0); +} + +static void +iscsi_fuzz_usage(void) +{ + fprintf(stderr, " -T iSCSI Target IP address.\n"); + fprintf(stderr, " -S Seed value for test.\n"); + fprintf(stderr, + " -t Time in seconds to run the fuzz test. Only valid if -j is not specified.\n"); +} + +static int +iscsi_fuzz_parse(int ch, char *arg) +{ + int64_t error_test; + + switch (ch) { + case 'T': + g_tgt_ip = optarg; + break; + case 'S': + error_test = spdk_strtol(arg, 10); + if (error_test < 0) { + fprintf(stderr, "Invalid value supplied for the random seed.\n"); + return -1; + } else { + g_random_seed = error_test; + } + break; + case 't': + g_runtime = spdk_strtol(optarg, 10); + if (g_runtime <= 0 || g_runtime > MAX_RUNTIME_S) { + fprintf(stderr, "You must supply a positive runtime value less than %d.\n", MAX_RUNTIME_S); + return -1; + } + break; + case '?': + default: + iscsi_fuzz_usage(); + return -EINVAL; + } + + return 0; +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc; + + g_runtime = DEFAULT_RUNTIME; + srand((unsigned)time(0)); + + TAILQ_INIT(&g_get_pdu_list); + + spdk_app_opts_init(&opts); + opts.name = "iscsi_fuzz"; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "T:S:t:", NULL, iscsi_fuzz_parse, + iscsi_fuzz_usage) != SPDK_APP_PARSE_ARGS_SUCCESS)) { + return rc; + } + + rc = spdk_app_start(&opts, begin_iscsi_fuzz, NULL); + + return rc; +} diff --git a/src/spdk/test/app/fuzz/nvme_fuzz/.gitignore b/src/spdk/test/app/fuzz/nvme_fuzz/.gitignore new file mode 100644 index 000000000..801146458 --- /dev/null +++ b/src/spdk/test/app/fuzz/nvme_fuzz/.gitignore @@ -0,0 +1 @@ +nvme_fuzz diff --git a/src/spdk/test/app/fuzz/nvme_fuzz/Makefile b/src/spdk/test/app/fuzz/nvme_fuzz/Makefile new file mode 100644 index 000000000..b7ad5e172 --- /dev/null +++ b/src/spdk/test/app/fuzz/nvme_fuzz/Makefile @@ -0,0 +1,49 @@ +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = nvme_fuzz + +CFLAGS += -I$(SPDK_ROOT_DIR)/test/app/fuzz/common + +C_SRCS := nvme_fuzz.c + +SPDK_LIB_LIST += $(SOCK_MODULES_LIST) +SPDK_LIB_LIST += conf event json jsonrpc log nvme rpc sock thread trace util + +ifeq ($(CONFIG_RDMA),y) +SPDK_LIB_LIST += rdma +endif + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/app/fuzz/nvme_fuzz/README.md b/src/spdk/test/app/fuzz/nvme_fuzz/README.md new file mode 100644 index 000000000..2f188b5b8 --- /dev/null +++ b/src/spdk/test/app/fuzz/nvme_fuzz/README.md @@ -0,0 +1,52 @@ +# Overview + +This application is intended to fuzz test the NVMe-oF target or a physical NVMe drive by +submitting randomized NVMe commands through the SPDK NVMe initiator. Both local and remote +drives are configured through a .ini style config file (See the -C option on the application). +Multiple controllers and namespaces can be exposed to the fuzzer at a time. In order to +handle multiple namespaces, the fuzzer will round robin assign a thread to each namespace and +submit commands to that thread at a set queue depth. (currently 128 for I/O, 16 for Admin). The +application will terminate under three conditions: + +1. The user specified run time expires (see the -t flag). +2. One of the target controllers stops completing I/O operations back to the fuzzer i.e. controller timeout. +3. The user specified a json file containing operations to run and the fuzzer has received valid completions for all of them. + +# Output + +By default, the fuzzer will print commands that: + +1. Complete successfully back from the target, or +2. Are outstanding at the time of a controller timeout. + +Commands are dumped as named objects in json format which can then be supplied back to the +script for targeted debugging on a subsequent run. See `Debugging` below. +By default no output is generated when a specific command is returned with a failed status. +This can be overridden with the -V flag. if -V is specified, each command will be dumped as +it is completed in the JSON format specified above. +At the end of each test run, a summary is printed for each namespace in the following format: + +~~~ +NS: 0x200079262300 admin qp, Total commands completed: 462459, total successful commands: 1960, random_seed: 4276918833 +~~~ + +# Debugging + +If a controller hangs when processing I/O generated by the fuzzer, the fuzzer will stop +submitting I/O and dump out all outstanding I/O on the qpair that timed out. The I/O are +dumped as valid json. You can combine the dumped commands from the fuzzer into a json +array in a file and then pass them to the fuzzer using the -j option. Please see the +example.json file in this directory for an example of a properly formed array of command +structures. + +Please note that you can also craft your own custom command values by using the output +from the fuzzer as a template. + +# JSON Format + +Most of the variables in the spdk_nvme_cmd structure are represented as numbers in JSON. +The only exception to this rule is the dptr union. This is a 16 byte union structure that +is represented as a base64 string. If writing custom commands for input, please note this +distinction or the application will be unable to load your custom input. + +Happy Fuzzing! diff --git a/src/spdk/test/app/fuzz/nvme_fuzz/example.json b/src/spdk/test/app/fuzz/nvme_fuzz/example.json new file mode 100644 index 000000000..95540746e --- /dev/null +++ b/src/spdk/test/app/fuzz/nvme_fuzz/example.json @@ -0,0 +1,290 @@ +{ +"struct spdk_nvme_cmd": { + "opc": 7, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 24, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 43, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 12, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 7, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 24, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 43, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 12, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 7, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 24, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 43, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 12, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 7, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 24, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 43, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 12, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + } +} diff --git a/src/spdk/test/app/fuzz/nvme_fuzz/nvme_fuzz.c b/src/spdk/test/app/fuzz/nvme_fuzz/nvme_fuzz.c new file mode 100644 index 000000000..127bc1bff --- /dev/null +++ b/src/spdk/test/app/fuzz/nvme_fuzz/nvme_fuzz.c @@ -0,0 +1,931 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. All rights reserved. + * Copyright (c) 2019 Mellanox Technologies LTD. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" +#include "spdk/conf.h" +#include "spdk/env.h" +#include "spdk/event.h" +#include "spdk/util.h" +#include "spdk/string.h" +#include "spdk/nvme_spec.h" +#include "spdk/nvme.h" +#include "spdk/likely.h" +#include "spdk/json.h" +#include "fuzz_common.h" + +#define UNIQUE_OPCODES 256 + +const char g_nvme_cmd_json_name[] = "struct spdk_nvme_cmd"; +char *g_conf_file; +char *g_json_file = NULL; +uint64_t g_runtime_ticks; +unsigned int g_seed_value = 0; +int g_runtime; + +int g_num_active_threads = 0; +uint32_t g_admin_depth = 16; +uint32_t g_io_depth = 128; + +bool g_valid_ns_only = false; +bool g_verbose_mode = false; +bool g_run_admin_commands = false; +bool g_run; + +struct spdk_poller *g_app_completion_poller; +bool g_successful_io_opcodes[UNIQUE_OPCODES] = {0}; +bool g_successful_admin_opcodes[UNIQUE_OPCODES] = {0}; + +struct spdk_nvme_cmd *g_cmd_array; +size_t g_cmd_array_size; + +/* I need context objects here because I need to keep track of all I/O that are in flight. */ +struct nvme_fuzz_request { + struct spdk_nvme_cmd cmd; + struct nvme_fuzz_qp *qp; + TAILQ_ENTRY(nvme_fuzz_request) link; +}; + +struct nvme_fuzz_trid { + struct spdk_nvme_transport_id trid; + TAILQ_ENTRY(nvme_fuzz_trid) tailq; +}; + +struct nvme_fuzz_ctrlr { + struct spdk_nvme_ctrlr *ctrlr; + TAILQ_ENTRY(nvme_fuzz_ctrlr) tailq; +}; + +struct nvme_fuzz_qp { + struct spdk_nvme_qpair *qpair; + /* array of context objects equal in length to the queue depth */ + struct nvme_fuzz_request *req_ctx; + TAILQ_HEAD(, nvme_fuzz_request) free_ctx_objs; + TAILQ_HEAD(, nvme_fuzz_request) outstanding_ctx_objs; + unsigned int random_seed; + uint64_t completed_cmd_counter; + uint64_t submitted_cmd_counter; + uint64_t successful_completed_cmd_counter; + uint64_t timeout_tsc; + uint32_t num_cmds_outstanding; + bool timed_out; + bool is_admin; +}; + +struct nvme_fuzz_ns { + struct spdk_nvme_ns *ns; + struct spdk_nvme_ctrlr *ctrlr; + struct spdk_thread *thread; + struct spdk_poller *req_poller; + struct nvme_fuzz_qp io_qp; + struct nvme_fuzz_qp a_qp; + uint32_t nsid; + TAILQ_ENTRY(nvme_fuzz_ns) tailq; +}; + +static TAILQ_HEAD(, nvme_fuzz_ns) g_ns_list = TAILQ_HEAD_INITIALIZER(g_ns_list); +static TAILQ_HEAD(, nvme_fuzz_ctrlr) g_ctrlr_list = TAILQ_HEAD_INITIALIZER(g_ctrlr_list); +static TAILQ_HEAD(, nvme_fuzz_trid) g_trid_list = TAILQ_HEAD_INITIALIZER(g_trid_list); + +static bool +parse_nvme_cmd_obj(void *item, struct spdk_json_val *value, size_t num_values) +{ + struct spdk_nvme_cmd *cmd = item; + struct spdk_json_val *next_val; + uint64_t tmp_val; + size_t i = 0; + + while (i < num_values) { + if (value->type == SPDK_JSON_VAL_NAME) { + next_val = value + 1; + if (!strncmp(value->start, "opc", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UNSIGNED_8BIT_MAX, &tmp_val)) { + goto invalid; + } + cmd->opc = tmp_val; + } + } else if (!strncmp(value->start, "fuse", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UNSIGNED_2BIT_MAX, &tmp_val)) { + goto invalid; + } + cmd->fuse = tmp_val; + } + } else if (!strncmp(value->start, "rsvd1", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UNSIGNED_4BIT_MAX, &tmp_val)) { + goto invalid; + } + cmd->rsvd1 = tmp_val; + } + } else if (!strncmp(value->start, "psdt", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UNSIGNED_2BIT_MAX, &tmp_val)) { + goto invalid; + } + cmd->psdt = tmp_val; + } + } else if (!strncmp(value->start, "cid", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT16_MAX, &tmp_val)) { + goto invalid; + } + cmd->cid = tmp_val; + } + } else if (!strncmp(value->start, "nsid", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->nsid = tmp_val; + } + } else if (!strncmp(value->start, "rsvd2", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->rsvd2 = tmp_val; + } + } else if (!strncmp(value->start, "rsvd3", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->rsvd3 = tmp_val; + } + } else if (!strncmp(value->start, "mptr", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT64_MAX, &tmp_val)) { + goto invalid; + } + cmd->mptr = tmp_val; + } + } else if (!strncmp(value->start, "dptr", value->len)) { + if (next_val->type == SPDK_JSON_VAL_STRING) { + if (fuzz_get_base_64_buffer_value(&cmd->dptr, sizeof(cmd->dptr), (char *)next_val->start, + next_val->len)) { + goto invalid; + } + } + } else if (!strncmp(value->start, "cdw10", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw10 = tmp_val; + } + } else if (!strncmp(value->start, "cdw11", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw11 = tmp_val; + } + } else if (!strncmp(value->start, "cdw12", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw12 = tmp_val; + } + } else if (!strncmp(value->start, "cdw13", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw13 = tmp_val; + } + } else if (!strncmp(value->start, "cdw14", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw14 = tmp_val; + } + } else if (!strncmp(value->start, "cdw15", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw15 = tmp_val; + } + } + } + i++; + value++; + } + return true; + +invalid: + fprintf(stderr, "Invalid value supplied for cmd->%.*s: %.*s\n", value->len, (char *)value->start, + next_val->len, (char *)next_val->start); + return false; +} + +static void +report_successful_opcodes(bool *array, int length) +{ + int i; + + for (i = 0; i < length; i++) { + if (array[i] == true) { + printf("%d, ", i); + } + } + printf("\n"); +} + +static int +print_nvme_cmd(void *cb_ctx, const void *data, size_t size) +{ + fprintf(stderr, "%s\n", (const char *)data); + return 0; +} + +static void +json_dump_nvme_cmd(struct spdk_nvme_cmd *cmd) +{ + struct spdk_json_write_ctx *w; + char *dptr_value; + + dptr_value = fuzz_get_value_base_64_buffer(&cmd->dptr, sizeof(cmd->dptr)); + if (dptr_value == NULL) { + fprintf(stderr, "Unable to allocate buffer context for printing command.\n"); + return; + } + + w = spdk_json_write_begin(print_nvme_cmd, cmd, SPDK_JSON_WRITE_FLAG_FORMATTED); + if (w == NULL) { + fprintf(stderr, "Unable to allocate json context for printing command.\n"); + free(dptr_value); + return; + } + + spdk_json_write_named_object_begin(w, g_nvme_cmd_json_name); + spdk_json_write_named_uint32(w, "opc", cmd->opc); + spdk_json_write_named_uint32(w, "fuse", cmd->fuse); + spdk_json_write_named_uint32(w, "rsvd1", cmd->rsvd1); + spdk_json_write_named_uint32(w, "psdt", cmd->psdt); + spdk_json_write_named_uint32(w, "cid", cmd->cid); + spdk_json_write_named_uint32(w, "nsid", cmd->nsid); + spdk_json_write_named_uint32(w, "rsvd2", cmd->rsvd2); + spdk_json_write_named_uint32(w, "rsvd3", cmd->rsvd3); + spdk_json_write_named_uint32(w, "mptr", cmd->mptr); + spdk_json_write_named_string(w, "dptr", dptr_value); + spdk_json_write_named_uint32(w, "cdw10", cmd->cdw10); + spdk_json_write_named_uint32(w, "cdw11", cmd->cdw11); + spdk_json_write_named_uint32(w, "cdw12", cmd->cdw12); + spdk_json_write_named_uint32(w, "cdw13", cmd->cdw13); + spdk_json_write_named_uint32(w, "cdw14", cmd->cdw14); + spdk_json_write_named_uint32(w, "cdw15", cmd->cdw15); + spdk_json_write_object_end(w); + + free(dptr_value); + spdk_json_write_end(w); +} + +static void +json_dump_nvme_cmd_list(struct nvme_fuzz_qp *qp) +{ + struct nvme_fuzz_request *ctx; + + TAILQ_FOREACH(ctx, &qp->outstanding_ctx_objs, link) { + json_dump_nvme_cmd(&ctx->cmd); + } +} + +static void +handle_timeout(struct nvme_fuzz_qp *qp, bool is_admin) +{ + fprintf(stderr, "An %s queue has timed out. Dumping all outstanding commands from that queue\n", + is_admin ? "Admin" : "I/O"); + json_dump_nvme_cmd_list(qp); + qp->timed_out = true; +} + +static void submit_ns_cmds(struct nvme_fuzz_ns *ns_entry); + +static void +nvme_fuzz_cpl_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + struct nvme_fuzz_request *ctx = cb_arg; + struct nvme_fuzz_qp *qp = ctx->qp; + + qp->completed_cmd_counter++; + if (spdk_unlikely(cpl->status.sc == SPDK_NVME_SC_SUCCESS)) { + fprintf(stderr, "The following %s command (command num %lu) completed successfully\n", + qp->is_admin ? "Admin" : "I/O", qp->completed_cmd_counter); + qp->successful_completed_cmd_counter++; + json_dump_nvme_cmd(&ctx->cmd); + + if (qp->is_admin) { + __sync_bool_compare_and_swap(&g_successful_admin_opcodes[ctx->cmd.opc], false, true); + } else { + __sync_bool_compare_and_swap(&g_successful_io_opcodes[ctx->cmd.opc], false, true); + } + } else if (g_verbose_mode == true) { + fprintf(stderr, "The following %s command (command num %lu) failed as expected.\n", + qp->is_admin ? "Admin" : "I/O", qp->completed_cmd_counter); + json_dump_nvme_cmd(&ctx->cmd); + } + + qp->timeout_tsc = fuzz_refresh_timeout(); + TAILQ_REMOVE(&qp->outstanding_ctx_objs, ctx, link); + TAILQ_INSERT_HEAD(&qp->free_ctx_objs, ctx, link); + assert(qp->num_cmds_outstanding > 0); + qp->num_cmds_outstanding--; +} + +static int +poll_for_completions(void *arg) +{ + struct nvme_fuzz_ns *ns_entry = arg; + uint64_t current_ticks = spdk_get_ticks(); + uint64_t *counter; + if (!ns_entry->io_qp.timed_out) { + spdk_nvme_qpair_process_completions(ns_entry->io_qp.qpair, 0); + /* SAlways have to process admin completions for the purposes of keep alive. */ + spdk_nvme_ctrlr_process_admin_completions(ns_entry->ctrlr); + } + + if (g_cmd_array) { + if (g_run_admin_commands) { + counter = &ns_entry->a_qp.submitted_cmd_counter; + } else { + counter = &ns_entry->io_qp.submitted_cmd_counter; + } + + if (*counter >= g_cmd_array_size) { + g_run = false; + } + } else { + if (current_ticks > g_runtime_ticks) { + g_run = false; + } + } + + if (ns_entry->a_qp.timeout_tsc < current_ticks && !ns_entry->a_qp.timed_out && + ns_entry->a_qp.num_cmds_outstanding > 0) { + handle_timeout(&ns_entry->a_qp, true); + } + + if (ns_entry->io_qp.timeout_tsc < current_ticks && !ns_entry->io_qp.timed_out && + ns_entry->io_qp.num_cmds_outstanding > 0) { + handle_timeout(&ns_entry->io_qp, false); + } + + submit_ns_cmds(ns_entry); + + if (g_run) { + return 0; + } + /* + * We either processed all I/O properly and can shut down normally, or we + * had a qp time out and we need to exit without reducing the values to 0. + */ + if (ns_entry->io_qp.num_cmds_outstanding == 0 && + ns_entry->a_qp.num_cmds_outstanding == 0) { + goto exit_handler; + } else if (ns_entry->io_qp.timed_out && (!g_run_admin_commands || ns_entry->a_qp.timed_out)) { + goto exit_handler; + } else { + return 0; + } + +exit_handler: + spdk_poller_unregister(&ns_entry->req_poller); + __sync_sub_and_fetch(&g_num_active_threads, 1); + spdk_thread_exit(ns_entry->thread); + return 0; +} + +static void +prep_nvme_cmd(struct nvme_fuzz_ns *ns_entry, struct nvme_fuzz_qp *qp, struct nvme_fuzz_request *ctx) +{ + if (g_cmd_array) { + memcpy(&ctx->cmd, &g_cmd_array[qp->submitted_cmd_counter], sizeof(ctx->cmd)); + } else { + fuzz_fill_random_bytes((char *)&ctx->cmd, sizeof(ctx->cmd), &qp->random_seed); + + if (g_valid_ns_only) { + ctx->cmd.nsid = ns_entry->nsid; + } + } +} + +static int +submit_qp_cmds(struct nvme_fuzz_ns *ns, struct nvme_fuzz_qp *qp) +{ + struct nvme_fuzz_request *ctx; + int rc; + + if (qp->timed_out) { + return 0; + } + /* If we are reading from an array, we need to stop after the last one. */ + while ((qp->submitted_cmd_counter < g_cmd_array_size || g_cmd_array_size == 0) && + !TAILQ_EMPTY(&qp->free_ctx_objs)) { + ctx = TAILQ_FIRST(&qp->free_ctx_objs); + do { + prep_nvme_cmd(ns, qp, ctx); + } while (qp->is_admin && ctx->cmd.opc == SPDK_NVME_OPC_ASYNC_EVENT_REQUEST); + + TAILQ_REMOVE(&qp->free_ctx_objs, ctx, link); + TAILQ_INSERT_HEAD(&qp->outstanding_ctx_objs, ctx, link); + qp->num_cmds_outstanding++; + qp->submitted_cmd_counter++; + if (qp->is_admin) { + rc = spdk_nvme_ctrlr_cmd_admin_raw(ns->ctrlr, &ctx->cmd, NULL, 0, nvme_fuzz_cpl_cb, ctx); + } else { + rc = spdk_nvme_ctrlr_cmd_io_raw(ns->ctrlr, qp->qpair, &ctx->cmd, NULL, 0, nvme_fuzz_cpl_cb, ctx); + } + if (rc) { + return rc; + } + } + return 0; +} + +static void +submit_ns_cmds(struct nvme_fuzz_ns *ns_entry) +{ + int rc; + + if (!g_run) { + return; + } + + if (g_run_admin_commands) { + rc = submit_qp_cmds(ns_entry, &ns_entry->a_qp); + if (rc) { + goto err_exit; + } + } + + if (g_cmd_array == NULL || !g_run_admin_commands) { + rc = submit_qp_cmds(ns_entry, &ns_entry->io_qp); + } +err_exit: + if (rc) { + /* + * I see the prospect of having a broken qpair on one ns as interesting + * enough to recommend stopping the application. + */ + fprintf(stderr, "Unable to submit command with rc %d\n", rc); + g_run = false; + } +} + +static void +free_namespaces(void) +{ + struct nvme_fuzz_ns *ns, *tmp; + + TAILQ_FOREACH_SAFE(ns, &g_ns_list, tailq, tmp) { + printf("NS: %p I/O qp, Total commands completed: %lu, total successful commands: %lu, random_seed: %u\n", + ns->ns, + ns->io_qp.completed_cmd_counter, ns->io_qp.successful_completed_cmd_counter, ns->io_qp.random_seed); + printf("NS: %p admin qp, Total commands completed: %lu, total successful commands: %lu, random_seed: %u\n", + ns->ns, + ns->a_qp.completed_cmd_counter, ns->a_qp.successful_completed_cmd_counter, ns->a_qp.random_seed); + + TAILQ_REMOVE(&g_ns_list, ns, tailq); + if (ns->io_qp.qpair) { + spdk_nvme_ctrlr_free_io_qpair(ns->io_qp.qpair); + } + if (ns->io_qp.req_ctx) { + free(ns->io_qp.req_ctx); + } + if (ns->a_qp.req_ctx) { + free(ns->a_qp.req_ctx); + } + free(ns); + } +} + +static void +free_controllers(void) +{ + struct nvme_fuzz_ctrlr *ctrlr, *tmp; + + TAILQ_FOREACH_SAFE(ctrlr, &g_ctrlr_list, tailq, tmp) { + TAILQ_REMOVE(&g_ctrlr_list, ctrlr, tailq); + spdk_nvme_detach(ctrlr->ctrlr); + free(ctrlr); + } +} + +static void +free_trids(void) +{ + struct nvme_fuzz_trid *trid, *tmp; + + TAILQ_FOREACH_SAFE(trid, &g_trid_list, tailq, tmp) { + TAILQ_REMOVE(&g_trid_list, trid, tailq); + free(trid); + } +} + +static void +register_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns *ns, uint32_t nsid) +{ + struct nvme_fuzz_ns *ns_entry; + + ns_entry = calloc(1, sizeof(struct nvme_fuzz_ns)); + if (ns_entry == NULL) { + fprintf(stderr, "Unable to allocate an entry for a namespace\n"); + return; + } + + ns_entry->ns = ns; + ns_entry->ctrlr = ctrlr; + ns_entry->nsid = nsid; + + TAILQ_INIT(&ns_entry->io_qp.free_ctx_objs); + TAILQ_INIT(&ns_entry->io_qp.outstanding_ctx_objs); + if (g_run_admin_commands) { + ns_entry->a_qp.qpair = NULL; + TAILQ_INIT(&ns_entry->a_qp.free_ctx_objs); + TAILQ_INIT(&ns_entry->a_qp.outstanding_ctx_objs); + } + TAILQ_INSERT_TAIL(&g_ns_list, ns_entry, tailq); +} + +static void +register_ctrlr(struct spdk_nvme_ctrlr *ctrlr) +{ + struct nvme_fuzz_ctrlr *ctrlr_entry; + uint32_t nsid; + struct spdk_nvme_ns *ns; + + ctrlr_entry = calloc(1, sizeof(struct nvme_fuzz_ctrlr)); + if (ctrlr_entry == NULL) { + fprintf(stderr, "Unable to allocate an entry for a controller\n"); + return; + } + + ctrlr_entry->ctrlr = ctrlr; + TAILQ_INSERT_TAIL(&g_ctrlr_list, ctrlr_entry, tailq); + + for (nsid = spdk_nvme_ctrlr_get_first_active_ns(ctrlr); nsid != 0; + nsid = spdk_nvme_ctrlr_get_next_active_ns(ctrlr, nsid)) { + ns = spdk_nvme_ctrlr_get_ns(ctrlr, nsid); + if (ns == NULL) { + continue; + } + register_ns(ctrlr, ns, nsid); + } +} + +static void +attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ + register_ctrlr(ctrlr); +} + +static bool +probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, struct spdk_nvme_ctrlr_opts *opts) +{ + printf("Controller trtype %s\ttraddr %s\n", spdk_nvme_transport_id_trtype_str(trid->trtype), + trid->traddr); + + return true; +} + +static int +prep_qpair(struct nvme_fuzz_ns *ns, struct nvme_fuzz_qp *qp, uint32_t max_qdepth) +{ + uint32_t i; + + /* ensure that each qpair gets a unique random seed for maximum command dispersion. */ + + if (g_seed_value != 0) { + qp->random_seed = g_seed_value; + } else { + /* Take the low 32 bits of spdk_get_ticks. This should be more granular than time(). */ + qp->random_seed = spdk_get_ticks(); + } + + qp->timeout_tsc = fuzz_refresh_timeout(); + + qp->req_ctx = calloc(max_qdepth, sizeof(struct nvme_fuzz_request)); + if (qp->req_ctx == NULL) { + fprintf(stderr, "Unable to allocate I/O contexts for I/O qpair.\n"); + return -1; + } + + for (i = 0; i < max_qdepth; i++) { + qp->req_ctx[i].qp = qp; + TAILQ_INSERT_HEAD(&qp->free_ctx_objs, &qp->req_ctx[i], link); + } + + return 0; +} + +static int +prepare_qpairs(void) +{ + struct spdk_nvme_io_qpair_opts opts; + struct nvme_fuzz_ns *ns_entry; + + TAILQ_FOREACH(ns_entry, &g_ns_list, tailq) { + spdk_nvme_ctrlr_get_default_io_qpair_opts(ns_entry->ctrlr, &opts, sizeof(opts)); + ns_entry->io_qp.qpair = spdk_nvme_ctrlr_alloc_io_qpair(ns_entry->ctrlr, &opts, sizeof(opts)); + if (ns_entry->io_qp.qpair == NULL) { + fprintf(stderr, "Unable to create a qpair for a namespace\n"); + return -1; + } + + ns_entry->io_qp.is_admin = false; + if (prep_qpair(ns_entry, &ns_entry->io_qp, g_io_depth) != 0) { + fprintf(stderr, "Unable to allocate request contexts for I/O qpair.\n"); + return -1; + } + + if (g_run_admin_commands) { + ns_entry->a_qp.is_admin = true; + if (prep_qpair(ns_entry, &ns_entry->a_qp, g_admin_depth) != 0) { + fprintf(stderr, "Unable to allocate request contexts for admin qpair.\n"); + return -1; + } + } + } + return 0; +} + +static void +start_ns_poller(void *ctx) +{ + struct nvme_fuzz_ns *ns_entry = ctx; + + ns_entry->req_poller = SPDK_POLLER_REGISTER(poll_for_completions, ns_entry, 0); + submit_ns_cmds(ns_entry); +} + +static int +check_app_completion(void *ctx) +{ + + if (g_num_active_threads <= 0) { + spdk_poller_unregister(&g_app_completion_poller); + if (g_cmd_array) { + free(g_cmd_array); + } + printf("Fuzzing completed. Shutting down the fuzz application\n\n"); + printf("Dumping successful admin opcodes:\n"); + report_successful_opcodes(g_successful_admin_opcodes, UNIQUE_OPCODES); + printf("Dumping successful io opcodes:\n"); + report_successful_opcodes(g_successful_io_opcodes, UNIQUE_OPCODES); + free_namespaces(); + free_controllers(); + free_trids(); + spdk_app_stop(0); + } + return 0; +} + +static void +begin_fuzz(void *ctx) +{ + struct nvme_fuzz_ns *ns_entry; + struct nvme_fuzz_trid *trid; + int rc; + + if (!spdk_iommu_is_enabled()) { + /* Don't set rc to an error code here. We don't want to fail an automated test based on this. */ + fprintf(stderr, "The IOMMU must be enabled to run this program to avoid unsafe memory accesses.\n"); + rc = 0; + goto out; + } + + TAILQ_FOREACH(trid, &g_trid_list, tailq) { + if (spdk_nvme_probe(&trid->trid, trid, probe_cb, attach_cb, NULL) != 0) { + fprintf(stderr, "spdk_nvme_probe() failed for transport address '%s'\n", + trid->trid.traddr); + rc = -1; + goto out; + } + } + + if (TAILQ_EMPTY(&g_ns_list)) { + fprintf(stderr, "No valid NVMe Namespaces to fuzz\n"); + rc = -EINVAL; + goto out; + } + + rc = prepare_qpairs(); + + if (rc < 0) { + fprintf(stderr, "Unable to prepare the qpairs\n"); + goto out; + } + + g_runtime_ticks = spdk_get_ticks() + g_runtime * spdk_get_ticks_hz(); + + /* Assigning all of the threads and then starting them makes cleanup easier. */ + TAILQ_FOREACH(ns_entry, &g_ns_list, tailq) { + ns_entry->thread = spdk_thread_create(NULL, NULL); + if (ns_entry->thread == NULL) { + fprintf(stderr, "Failed to allocate thread for namespace.\n"); + goto out; + } + } + + TAILQ_FOREACH(ns_entry, &g_ns_list, tailq) { + spdk_thread_send_msg(ns_entry->thread, start_ns_poller, ns_entry); + __sync_add_and_fetch(&g_num_active_threads, 1); + } + + g_app_completion_poller = SPDK_POLLER_REGISTER(check_app_completion, NULL, 1000000); + return; +out: + printf("Shutting down the fuzz application\n"); + free_namespaces(); + free_controllers(); + free_trids(); + spdk_app_stop(rc); +} + +static int +parse_trids(void) +{ + struct spdk_conf *config = NULL; + struct spdk_conf_section *sp; + const char *trid_char; + struct nvme_fuzz_trid *current_trid; + int num_subsystems = 0; + int rc = 0; + + if (g_conf_file) { + config = spdk_conf_allocate(); + if (!config) { + fprintf(stderr, "Unable to allocate an spdk_conf object\n"); + return -1; + } + + rc = spdk_conf_read(config, g_conf_file); + if (rc) { + fprintf(stderr, "Unable to convert the conf file into a readable system\n"); + rc = -1; + goto exit; + } + + sp = spdk_conf_find_section(config, "Nvme"); + + if (sp == NULL) { + fprintf(stderr, "No Nvme configuration in conf file\n"); + goto exit; + } + + while ((trid_char = spdk_conf_section_get_nmval(sp, "TransportID", num_subsystems, 0)) != NULL) { + current_trid = malloc(sizeof(struct nvme_fuzz_trid)); + if (!current_trid) { + fprintf(stderr, "Unable to allocate memory for transport ID\n"); + rc = -1; + goto exit; + } + rc = spdk_nvme_transport_id_parse(¤t_trid->trid, trid_char); + + if (rc < 0) { + fprintf(stderr, "failed to parse transport ID: %s\n", trid_char); + free(current_trid); + rc = -1; + goto exit; + } + TAILQ_INSERT_TAIL(&g_trid_list, current_trid, tailq); + num_subsystems++; + } + } + +exit: + if (config != NULL) { + spdk_conf_free(config); + } + return rc; +} + +static void +nvme_fuzz_usage(void) +{ + fprintf(stderr, " -a Perform admin commands. if -j is specified, \ +only admin commands will run. Otherwise they will be run in tandem with I/O commands.\n"); + fprintf(stderr, " -C Path to a configuration file.\n"); + fprintf(stderr, + " -j Path to a json file containing named objects of type spdk_nvme_cmd. If this option is specified, -t will be ignored.\n"); + fprintf(stderr, " -N Target only valid namespace with commands. \ +This helps dig deeper into other errors besides invalid namespace.\n"); + fprintf(stderr, " -S Seed value for test.\n"); + fprintf(stderr, + " -t Time in seconds to run the fuzz test. Only valid if -j is not specified.\n"); + fprintf(stderr, " -V Enable logging of each submitted command.\n"); +} + +static int +nvme_fuzz_parse(int ch, char *arg) +{ + int64_t error_test; + + switch (ch) { + case 'a': + g_run_admin_commands = true; + break; + case 'C': + g_conf_file = optarg; + break; + case 'j': + g_json_file = optarg; + break; + case 'N': + g_valid_ns_only = true; + break; + case 'S': + error_test = spdk_strtol(arg, 10); + if (error_test < 0) { + fprintf(stderr, "Invalid value supplied for the random seed.\n"); + return -1; + } else { + g_seed_value = error_test; + } + break; + case 't': + g_runtime = spdk_strtol(optarg, 10); + if (g_runtime < 0 || g_runtime > MAX_RUNTIME_S) { + fprintf(stderr, "You must supply a positive runtime value less than 86401.\n"); + return -1; + } + break; + case 'V': + g_verbose_mode = true; + break; + case '?': + default: + return -EINVAL; + } + return 0; +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc; + + spdk_app_opts_init(&opts); + opts.name = "nvme_fuzz"; + + g_runtime = DEFAULT_RUNTIME; + g_run = true; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "aC:j:NS:t:V", NULL, nvme_fuzz_parse, + nvme_fuzz_usage) != SPDK_APP_PARSE_ARGS_SUCCESS)) { + return rc; + } + + if (g_conf_file) { + parse_trids(); + } + + if (g_json_file != NULL) { + g_cmd_array_size = fuzz_parse_args_into_array(g_json_file, (void **)&g_cmd_array, + sizeof(struct spdk_nvme_cmd), g_nvme_cmd_json_name, parse_nvme_cmd_obj); + if (g_cmd_array_size == 0) { + fprintf(stderr, "The provided json file did not contain any valid commands. Exiting."); + return -EINVAL; + } + } + + rc = spdk_app_start(&opts, begin_fuzz, NULL); + + return rc; +} diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/.gitignore b/src/spdk/test/app/fuzz/vhost_fuzz/.gitignore new file mode 100644 index 000000000..2df201f3f --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/.gitignore @@ -0,0 +1 @@ +vhost_fuzz diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/Makefile b/src/spdk/test/app/fuzz/vhost_fuzz/Makefile new file mode 100644 index 000000000..69b8d1866 --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/Makefile @@ -0,0 +1,42 @@ +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = vhost_fuzz + +CFLAGS += -I$(SPDK_ROOT_DIR)/test/app/fuzz/common + +C_SRCS := vhost_fuzz_rpc.c vhost_fuzz.c + +SPDK_LIB_LIST += event conf json jsonrpc rpc util log sock trace thread virtio +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/README.md b/src/spdk/test/app/fuzz/vhost_fuzz/README.md new file mode 100644 index 000000000..ab9656c5b --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/README.md @@ -0,0 +1,46 @@ +# Overview + +This application is intended to fuzz test the SPDK vhost target by supplying +malformed or invalid requests across a unix domain socket. This fuzzer +currently supports fuzzing both vhost block and vhost scsi devices. When +fuzzing a vhost scsi device, users can select whether to fuzz the scsi I/O +queue or the scsi admin queue. Please see the NVMe fuzzer readme for information +on how output is generated, debugging procedures, and the JSON format expected +when supplying preconstructed values to the fuzzer. + +# Request Types + +Like the NVMe fuzzer, there is an example json file showing the types of requests +that the application accepts. Since the vhost application accepts both vhost block +and vhost scsi commands, there are three distinct object types that can be passed in +to the application. + +1. vhost_blk_cmd +2. vhost_scsi_cmd +3. vhost_scsi_mgmt_cmd + +Each one of these objects contains distinct data types and they should not be used interchangeably. + +All three of the data types begin with three iovec structures describing the request, data, and response +memory locations. By default, these values are overwritten by the application even when supplied as part +of a json file. This is because the request and resp data pointers are intended to point to portions of +the data structure. + +If you want to override these iovec values using a json file, you can specify the -k option. +In most cases, this will just result in the application failing all I/O immediately since +the request will no longer point to a valid memory location. + +It is possible to supply all three types of requests in a single array to the application. They will be parsed and +submitted to the proper block devices. + +# RPC + +The vhost fuzzer differs from the NVMe fuzzer in that it expects devices to be configured via rpc. The fuzzer should +always be started with the --wait-for-rpc argument. Please see below for an example of starting the fuzzer. + +~~~ +./test/app/fuzz/vhost_fuzz/vhost_fuzz -t 30 --wait-for-rpc & +./scripts/rpc.py fuzz_vhost_create_dev -s ./Vhost.1 -b -V +./scripts/rpc.py fuzz_vhost_create_dev -s ./naa.VhostScsi0.1 -l -V +./scripts/rpc.py framework_start_init +~~~ diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/example.json b/src/spdk/test/app/fuzz/vhost_fuzz/example.json new file mode 100644 index 000000000..9157350f8 --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/example.json @@ -0,0 +1,95 @@ +{ + "vhost_scsi_mgmt_cmd": { + "req_iov": { + "iov_base": "20007960ff60", + "iov_len": 51 + }, + "data_iov": { + "iov_base": "2000794dbe00", + "iov_len": 1024 + }, + "resp_iov": { + "iov_base": "20007960ff98", + "iov_len": 108 + }, + "lun": "AQA5vBf3KyE=", + "tag": 6163879237324549222, + "task_attr": 247, + "prio": 242, + "crn": 169, + "cdb": "ErxZ/qpHBau8gPzjbpotpbTnOW/2g0ns2yRh4jhe5kc=" + }, + "vhost_scsi_mgmt_cmd": { + "req_iov": { + "iov_base": "20007960fe78", + "iov_len": 51 + }, + "data_iov": { + "iov_base": "2000794dbe00", + "iov_len": 1024 + }, + "resp_iov": { + "iov_base": "20007960feb0", + "iov_len": 108 + }, + "lun": "AQAwWRrhAoo=", + "tag": 10457151189012466200, + "task_attr": 97, + "prio": 158, + "crn": 41, + "cdb": "Ejjxdzl8KwRDhq+MPfY3J3niYfAHj+2irE8Q2vIfQIk=" + }, + "vhost_scsi_cmd": { + "req_iov": { + "iov_base": "20007960fe78", + "iov_len": 24 + }, + "data_iov": { + "iov_base": "20007960fe78", + "iov_len": 1024 + }, + "resp_iov": { + "iov_base": "20007960fe78", + "iov_len": 5 + }, + "type": 3, + "subtype": 872683406, + "lun": "LdaLkHOIQxI=", + "tag": 8452696012704506104 + }, + "vhost_scsi_cmd": { + "req_iov": { + "iov_base": "20007960fe78", + "iov_len": 24 + }, + "data_iov": { + "iov_base": "20007960fe78", + "iov_len": 1024 + }, + "resp_iov": { + "iov_base": "20007960fe78", + "iov_len": 5 + }, + "type": 3, + "subtype": 872683406, + "lun": "LdaLkHOIQxI=", + "tag": 8452696012704506104 + }, + "vhost_blk_cmd": { + "req_iov": { + "iov_base": "20007960fe78", + "iov_len": 24 + }, + "data_iov": { + "iov_base": "20007960fe78", + "iov_len": 1024 + }, + "resp_iov": { + "iov_base": "20007960fe78", + "iov_len": 5 + }, + "type": 2, + "ioprio": 4343, + "sector": 24323523 + } +} diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.c b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.c new file mode 100644 index 000000000..47dbfbc65 --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.c @@ -0,0 +1,1146 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. All rights reserved. + * Copyright (c) 2019 Mellanox Technologies LTD. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" +#include "spdk/conf.h" +#include "spdk/env.h" +#include "spdk/json.h" +#include "spdk/event.h" +#include "spdk/likely.h" +#include "spdk/util.h" +#include "spdk/string.h" +#include "spdk_internal/virtio.h" +#include "spdk_internal/vhost_user.h" + +#include "fuzz_common.h" +#include "vhost_fuzz.h" + +#include +#include + +/* Features desired/implemented by virtio blk. */ +#define VIRTIO_BLK_DEV_SUPPORTED_FEATURES \ + (1ULL << VIRTIO_BLK_F_BLK_SIZE | \ + 1ULL << VIRTIO_BLK_F_TOPOLOGY | \ + 1ULL << VIRTIO_BLK_F_MQ | \ + 1ULL << VIRTIO_BLK_F_RO | \ + 1ULL << VIRTIO_BLK_F_DISCARD | \ + 1ULL << VIRTIO_RING_F_EVENT_IDX | \ + 1ULL << VHOST_USER_F_PROTOCOL_FEATURES) + +/* Features desired/implemented by virtio scsi. */ +#define VIRTIO_SCSI_DEV_SUPPORTED_FEATURES \ + (1ULL << VIRTIO_SCSI_F_INOUT | \ + 1ULL << VIRTIO_SCSI_F_HOTPLUG | \ + 1ULL << VIRTIO_RING_F_EVENT_IDX | \ + 1ULL << VHOST_USER_F_PROTOCOL_FEATURES) + +#define VIRTIO_DEV_FIXED_QUEUES 2 +#define VIRTIO_SCSI_CONTROLQ 0 +#define VIRTIO_SCSI_EVENTQ 1 +#define VIRTIO_REQUESTQ 2 +#define FUZZ_MAX_QUEUES 3 + +#define FUZZ_QUEUE_DEPTH 128 + +#define BLK_IO_NAME "vhost_blk_cmd" +#define SCSI_IO_NAME "vhost_scsi_cmd" +#define SCSI_MGMT_NAME "vhost_scsi_mgmt_cmd" + +struct fuzz_vhost_iov_ctx { + struct iovec iov_req; + struct iovec iov_data; + struct iovec iov_resp; +}; + +struct fuzz_vhost_io_ctx { + struct fuzz_vhost_iov_ctx iovs; + union { + struct virtio_blk_outhdr blk_req; + struct virtio_scsi_cmd_req scsi_req; + struct virtio_scsi_ctrl_tmf_req scsi_tmf_req; + } req; + union { + uint8_t blk_resp; + struct virtio_scsi_cmd_resp scsi_resp; + union { + struct virtio_scsi_ctrl_tmf_resp scsi_tmf_resp; + struct virtio_scsi_ctrl_an_resp an_resp; + } scsi_tmf_resp; + } resp; + + TAILQ_ENTRY(fuzz_vhost_io_ctx) link; +}; + +struct fuzz_vhost_dev_ctx { + struct virtio_dev virtio_dev; + struct spdk_thread *thread; + struct spdk_poller *poller; + + struct fuzz_vhost_io_ctx *io_ctx_array; + TAILQ_HEAD(, fuzz_vhost_io_ctx) free_io_ctx; + TAILQ_HEAD(, fuzz_vhost_io_ctx) outstanding_io_ctx; + + unsigned int random_seed; + + uint64_t submitted_io; + uint64_t completed_io; + uint64_t successful_io; + uint64_t timeout_tsc; + + bool socket_is_blk; + bool test_scsi_tmf; + bool valid_lun; + bool use_bogus_buffer; + bool use_valid_buffer; + bool timed_out; + + TAILQ_ENTRY(fuzz_vhost_dev_ctx) link; +}; + +/* Global run state */ +uint64_t g_runtime_ticks; +int g_runtime; +int g_num_active_threads; +bool g_run = true; +bool g_verbose_mode = false; + +/* Global resources */ +TAILQ_HEAD(, fuzz_vhost_dev_ctx) g_dev_list = TAILQ_HEAD_INITIALIZER(g_dev_list); +struct spdk_poller *g_run_poller; +void *g_valid_buffer; +unsigned int g_random_seed; + + +/* Global parameters and resources for parsed commands */ +bool g_keep_iov_pointers = false; +char *g_json_file = NULL; +struct fuzz_vhost_io_ctx *g_blk_cmd_array = NULL; +struct fuzz_vhost_io_ctx *g_scsi_cmd_array = NULL; +struct fuzz_vhost_io_ctx *g_scsi_mgmt_cmd_array = NULL; + +size_t g_blk_cmd_array_size; +size_t g_scsi_cmd_array_size; +size_t g_scsi_mgmt_cmd_array_size; + +static void +cleanup(void) +{ + struct fuzz_vhost_dev_ctx *dev_ctx, *tmp; + printf("Fuzzing completed.\n"); + TAILQ_FOREACH_SAFE(dev_ctx, &g_dev_list, link, tmp) { + printf("device %p stats: Completed I/O: %lu, Successful I/O: %lu\n", dev_ctx, + dev_ctx->completed_io, dev_ctx->successful_io); + virtio_dev_release_queue(&dev_ctx->virtio_dev, VIRTIO_REQUESTQ); + if (!dev_ctx->socket_is_blk) { + virtio_dev_release_queue(&dev_ctx->virtio_dev, VIRTIO_SCSI_EVENTQ); + virtio_dev_release_queue(&dev_ctx->virtio_dev, VIRTIO_SCSI_CONTROLQ); + } + virtio_dev_stop(&dev_ctx->virtio_dev); + virtio_dev_destruct(&dev_ctx->virtio_dev); + if (dev_ctx->io_ctx_array) { + spdk_free(dev_ctx->io_ctx_array); + } + free(dev_ctx); + } + + spdk_free(g_valid_buffer); + + if (g_blk_cmd_array) { + free(g_blk_cmd_array); + } + if (g_scsi_cmd_array) { + free(g_scsi_cmd_array); + } + if (g_scsi_mgmt_cmd_array) { + free(g_scsi_mgmt_cmd_array); + } +} + +/* Get a memory address that is random and not located in our hugepage memory. */ +static void * +get_invalid_mem_address(uint64_t length) +{ + uint64_t chosen_address = 0x0; + + while (true) { + chosen_address = rand(); + chosen_address = (chosen_address << 32) | rand(); + if (spdk_vtophys((void *)chosen_address, &length) == SPDK_VTOPHYS_ERROR) { + return (void *)chosen_address; + } + } + return NULL; +} + +/* dev initialization code begin. */ +static int +virtio_dev_init(struct virtio_dev *vdev, const char *socket_path, uint64_t flags, + uint16_t max_queues) +{ + int rc; + + rc = virtio_user_dev_init(vdev, "dev_ctx", socket_path, 1024); + if (rc != 0) { + fprintf(stderr, "Failed to initialize virtual bdev\n"); + return rc; + } + + rc = virtio_dev_reset(vdev, flags); + if (rc != 0) { + return rc; + } + + rc = virtio_dev_start(vdev, max_queues, VIRTIO_DEV_FIXED_QUEUES); + if (rc != 0) { + return rc; + } + + rc = virtio_dev_acquire_queue(vdev, VIRTIO_REQUESTQ); + if (rc < 0) { + fprintf(stderr, "Couldn't get an unused queue for the io_channel.\n"); + virtio_dev_stop(vdev); + return rc; + } + return 0; +} + +static int +blk_dev_init(struct virtio_dev *vdev, const char *socket_path, uint16_t max_queues) +{ + uint16_t host_max_queues; + int rc; + + if (virtio_dev_has_feature(vdev, VIRTIO_BLK_F_MQ)) { + rc = virtio_dev_read_dev_config(vdev, offsetof(struct virtio_blk_config, num_queues), + &host_max_queues, sizeof(host_max_queues)); + if (rc) { + fprintf(stderr, "%s: config read failed: %s\n", vdev->name, spdk_strerror(-rc)); + return rc; + } + } else { + host_max_queues = 1; + } + + if (max_queues == 0) { + fprintf(stderr, "%s: requested 0 request queues (%"PRIu16" available).\n", + vdev->name, host_max_queues); + return -EINVAL; + } + + if (max_queues > host_max_queues) { + fprintf(stderr, "%s: requested %"PRIu16" request queues " + "but only %"PRIu16" available.\n", + vdev->name, max_queues, host_max_queues); + max_queues = host_max_queues; + } + + return virtio_dev_init(vdev, socket_path, VIRTIO_BLK_DEV_SUPPORTED_FEATURES, max_queues); +} + +static int +scsi_dev_init(struct virtio_dev *vdev, const char *socket_path, uint16_t max_queues) +{ + int rc; + + rc = virtio_dev_init(vdev, socket_path, VIRTIO_SCSI_DEV_SUPPORTED_FEATURES, max_queues); + if (rc != 0) { + return rc; + } + + rc = virtio_dev_acquire_queue(vdev, VIRTIO_SCSI_CONTROLQ); + if (rc != 0) { + SPDK_ERRLOG("Failed to acquire the controlq.\n"); + return rc; + } + + rc = virtio_dev_acquire_queue(vdev, VIRTIO_SCSI_EVENTQ); + if (rc != 0) { + SPDK_ERRLOG("Failed to acquire the eventq.\n"); + virtio_dev_release_queue(vdev, VIRTIO_SCSI_CONTROLQ); + return rc; + } + + return 0; +} + +int +fuzz_vhost_dev_init(const char *socket_path, bool is_blk_dev, bool use_bogus_buffer, + bool use_valid_buffer, bool valid_lun, bool test_scsi_tmf) +{ + struct fuzz_vhost_dev_ctx *dev_ctx; + int rc = 0, i; + + dev_ctx = calloc(1, sizeof(*dev_ctx)); + if (dev_ctx == NULL) { + return -ENOMEM; + } + + dev_ctx->socket_is_blk = is_blk_dev; + dev_ctx->use_bogus_buffer = use_bogus_buffer; + dev_ctx->use_valid_buffer = use_valid_buffer; + dev_ctx->valid_lun = valid_lun; + dev_ctx->test_scsi_tmf = test_scsi_tmf; + + TAILQ_INIT(&dev_ctx->free_io_ctx); + TAILQ_INIT(&dev_ctx->outstanding_io_ctx); + + assert(sizeof(*dev_ctx->io_ctx_array) <= UINT64_MAX / FUZZ_QUEUE_DEPTH); + dev_ctx->io_ctx_array = spdk_malloc(sizeof(*dev_ctx->io_ctx_array) * FUZZ_QUEUE_DEPTH, 0x0, NULL, + SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_SHARE); + if (dev_ctx->io_ctx_array == NULL) { + free(dev_ctx); + return -ENOMEM; + } + + for (i = 0; i < FUZZ_QUEUE_DEPTH; i++) { + TAILQ_INSERT_HEAD(&dev_ctx->free_io_ctx, &dev_ctx->io_ctx_array[i], link); + } + + dev_ctx->thread = spdk_thread_create(NULL, NULL); + if (dev_ctx->thread == NULL) { + fprintf(stderr, "Unable to allocate a thread for a fuzz device.\n"); + rc = -ENOMEM; + goto error_out; + } + + if (is_blk_dev) { + rc = blk_dev_init(&dev_ctx->virtio_dev, socket_path, FUZZ_MAX_QUEUES); + } else { + rc = scsi_dev_init(&dev_ctx->virtio_dev, socket_path, FUZZ_MAX_QUEUES); + } + + if (rc) { + fprintf(stderr, "Unable to prepare the device to perform I/O.\n"); + goto error_out; + } + + TAILQ_INSERT_TAIL(&g_dev_list, dev_ctx, link); + return 0; + +error_out: + spdk_free(dev_ctx->io_ctx_array); + free(dev_ctx); + return rc; +} +/* dev initialization code end */ + +/* data dumping functions begin */ +static int +dump_virtio_cmd(void *ctx, const void *data, size_t size) +{ + fprintf(stderr, "%s\n", (const char *)data); + return 0; +} + +static void +print_blk_io_data(struct spdk_json_write_ctx *w, struct fuzz_vhost_io_ctx *io_ctx) +{ + spdk_json_write_named_uint32(w, "type", io_ctx->req.blk_req.type); + spdk_json_write_named_uint32(w, "ioprio", io_ctx->req.blk_req.ioprio); + spdk_json_write_named_uint64(w, "sector", io_ctx->req.blk_req.sector); +} + +static void +print_scsi_tmf_io_data(struct spdk_json_write_ctx *w, struct fuzz_vhost_io_ctx *io_ctx) +{ + char *lun_data; + + lun_data = fuzz_get_value_base_64_buffer(io_ctx->req.scsi_tmf_req.lun, + sizeof(io_ctx->req.scsi_tmf_req.lun)); + + spdk_json_write_named_uint32(w, "type", io_ctx->req.scsi_tmf_req.type); + spdk_json_write_named_uint32(w, "subtype", io_ctx->req.scsi_tmf_req.subtype); + spdk_json_write_named_string(w, "lun", lun_data); + spdk_json_write_named_uint64(w, "tag", io_ctx->req.scsi_tmf_req.tag); + + free(lun_data); +} + +static void +print_scsi_io_data(struct spdk_json_write_ctx *w, struct fuzz_vhost_io_ctx *io_ctx) +{ + char *lun_data; + char *cdb_data; + + lun_data = fuzz_get_value_base_64_buffer(io_ctx->req.scsi_req.lun, + sizeof(io_ctx->req.scsi_req.lun)); + cdb_data = fuzz_get_value_base_64_buffer(io_ctx->req.scsi_req.cdb, + sizeof(io_ctx->req.scsi_req.cdb)); + + spdk_json_write_named_string(w, "lun", lun_data); + spdk_json_write_named_uint64(w, "tag", io_ctx->req.scsi_req.tag); + spdk_json_write_named_uint32(w, "task_attr", io_ctx->req.scsi_req.task_attr); + spdk_json_write_named_uint32(w, "prio", io_ctx->req.scsi_req.prio); + spdk_json_write_named_uint32(w, "crn", io_ctx->req.scsi_req.crn); + spdk_json_write_named_string(w, "cdb", cdb_data); + + free(lun_data); + free(cdb_data); +} + +static void +print_iov_obj(struct spdk_json_write_ctx *w, const char *iov_name, struct iovec *iov) +{ + /* "0x" + up to 16 digits + null terminator */ + char hex_addr[19]; + int rc; + + rc = snprintf(hex_addr, 19, "%lx", (uintptr_t)iov->iov_base); + + /* default to 0. */ + if (rc < 0 || rc >= 19) { + hex_addr[0] = '0'; + hex_addr[1] = '\0'; + } + + spdk_json_write_named_object_begin(w, iov_name); + spdk_json_write_named_string(w, "iov_base", hex_addr); + spdk_json_write_named_uint64(w, "iov_len", iov->iov_len); + spdk_json_write_object_end(w); +} + +static void +print_iovs(struct spdk_json_write_ctx *w, struct fuzz_vhost_io_ctx *io_ctx) +{ + print_iov_obj(w, "req_iov", &io_ctx->iovs.iov_req); + print_iov_obj(w, "data_iov", &io_ctx->iovs.iov_data); + print_iov_obj(w, "resp_iov", &io_ctx->iovs.iov_resp); +} + +static void +print_req_obj(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + + struct spdk_json_write_ctx *w; + + w = spdk_json_write_begin(dump_virtio_cmd, NULL, SPDK_JSON_WRITE_FLAG_FORMATTED); + + if (dev_ctx->socket_is_blk) { + spdk_json_write_named_object_begin(w, BLK_IO_NAME); + print_iovs(w, io_ctx); + print_blk_io_data(w, io_ctx); + } else if (dev_ctx->test_scsi_tmf) { + spdk_json_write_named_object_begin(w, SCSI_MGMT_NAME); + print_iovs(w, io_ctx); + print_scsi_tmf_io_data(w, io_ctx); + } else { + spdk_json_write_named_object_begin(w, SCSI_IO_NAME); + print_iovs(w, io_ctx); + print_scsi_io_data(w, io_ctx); + } + spdk_json_write_object_end(w); + spdk_json_write_end(w); +} + +static void +dump_outstanding_io(struct fuzz_vhost_dev_ctx *dev_ctx) +{ + struct fuzz_vhost_io_ctx *io_ctx, *tmp; + + TAILQ_FOREACH_SAFE(io_ctx, &dev_ctx->outstanding_io_ctx, link, tmp) { + print_req_obj(dev_ctx, io_ctx); + TAILQ_REMOVE(&dev_ctx->outstanding_io_ctx, io_ctx, link); + TAILQ_INSERT_TAIL(&dev_ctx->free_io_ctx, io_ctx, link); + } +} +/* data dumping functions end */ + +/* data parsing functions begin */ +static int +hex_value(uint8_t c) +{ +#define V(x, y) [x] = y + 1 + static const int8_t val[256] = { + V('0', 0), V('1', 1), V('2', 2), V('3', 3), V('4', 4), + V('5', 5), V('6', 6), V('7', 7), V('8', 8), V('9', 9), + V('A', 0xA), V('B', 0xB), V('C', 0xC), V('D', 0xD), V('E', 0xE), V('F', 0xF), + V('a', 0xA), V('b', 0xB), V('c', 0xC), V('d', 0xD), V('e', 0xE), V('f', 0xF), + }; +#undef V + + return val[c] - 1; +} + +static int +fuzz_json_decode_hex_uint64(const struct spdk_json_val *val, void *out) +{ + uint64_t *out_val = out; + size_t i; + char *val_pointer = val->start; + int current_val; + + if (val->len > 16) { + return -EINVAL; + } + + *out_val = 0; + for (i = 0; i < val->len; i++) { + *out_val = *out_val << 4; + current_val = hex_value(*val_pointer); + if (current_val < 0) { + return -EINVAL; + } + *out_val += current_val; + val_pointer++; + } + + return 0; +} + +static const struct spdk_json_object_decoder fuzz_vhost_iov_decoders[] = { + {"iov_base", offsetof(struct iovec, iov_base), fuzz_json_decode_hex_uint64}, + {"iov_len", offsetof(struct iovec, iov_len), spdk_json_decode_uint64}, +}; + +static size_t +parse_iov_struct(struct iovec *iovec, struct spdk_json_val *value) +{ + int rc; + + if (value->type != SPDK_JSON_VAL_OBJECT_BEGIN) { + return -1; + } + + rc = spdk_json_decode_object(value, + fuzz_vhost_iov_decoders, + SPDK_COUNTOF(fuzz_vhost_iov_decoders), + iovec); + if (rc) { + return -1; + } + + while (value->type != SPDK_JSON_VAL_OBJECT_END) { + value++; + rc++; + } + + /* The +1 instructs the calling function to skip over the OBJECT_END function. */ + rc += 1; + return rc; +} + +static bool +parse_vhost_blk_cmds(void *item, struct spdk_json_val *value, size_t num_values) +{ + struct fuzz_vhost_io_ctx *io_ctx = item; + struct spdk_json_val *prev_value; + int nested_object_size; + uint64_t tmp_val; + size_t i = 0; + + while (i < num_values) { + nested_object_size = 1; + if (value->type == SPDK_JSON_VAL_NAME) { + prev_value = value; + value++; + i++; + if (!strncmp(prev_value->start, "req_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_req, value); + } else if (!strncmp(prev_value->start, "data_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "resp_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "type", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT32_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.blk_req.type = tmp_val; + } + } else if (!strncmp(prev_value->start, "ioprio", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT32_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.blk_req.ioprio = tmp_val; + } + } else if (!strncmp(prev_value->start, "sector", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT64_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.blk_req.sector = tmp_val; + } + } + } + if (nested_object_size < 0) { + fprintf(stderr, "Invalid value supplied for io_ctx->%.*s: %.*s\n", prev_value->len, + (char *)prev_value->start, value->len, (char *)value->start); + return false; + } + value += nested_object_size; + i += nested_object_size; + } + return true; +} + +static bool +parse_vhost_scsi_cmds(void *item, struct spdk_json_val *value, size_t num_values) +{ + struct fuzz_vhost_io_ctx *io_ctx = item; + struct spdk_json_val *prev_value; + int nested_object_size; + uint64_t tmp_val; + size_t i = 0; + + while (i < num_values) { + nested_object_size = 1; + if (value->type == SPDK_JSON_VAL_NAME) { + prev_value = value; + value++; + i++; + if (!strncmp(prev_value->start, "req_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_req, value); + } else if (!strncmp(prev_value->start, "data_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "resp_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "lun", prev_value->len)) { + if (fuzz_get_base_64_buffer_value(&io_ctx->req.scsi_req.lun, + sizeof(io_ctx->req.scsi_req.lun), + (char *)value->start, + value->len)) { + nested_object_size = -1; + } + } else if (!strncmp(prev_value->start, "tag", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT64_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_req.tag = tmp_val; + } + } else if (!strncmp(prev_value->start, "task_attr", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT8_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_req.task_attr = tmp_val; + } + } else if (!strncmp(prev_value->start, "prio", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT8_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_req.prio = tmp_val; + } + } else if (!strncmp(prev_value->start, "crn", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT8_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_req.crn = tmp_val; + } + } else if (!strncmp(prev_value->start, "cdb", prev_value->len)) { + if (fuzz_get_base_64_buffer_value(&io_ctx->req.scsi_req.cdb, + sizeof(io_ctx->req.scsi_req.cdb), + (char *)value->start, + value->len)) { + nested_object_size = -1; + } + } + } + if (nested_object_size < 0) { + fprintf(stderr, "Invalid value supplied for io_ctx->%.*s: %.*s\n", prev_value->len, + (char *)prev_value->start, value->len, (char *)value->start); + return false; + } + value += nested_object_size; + i += nested_object_size; + } + return true; + +} + +static bool +parse_vhost_scsi_mgmt_cmds(void *item, struct spdk_json_val *value, size_t num_values) +{ + struct fuzz_vhost_io_ctx *io_ctx = item; + struct spdk_json_val *prev_value; + int nested_object_size; + uint64_t tmp_val; + size_t i = 0; + + while (i < num_values) { + nested_object_size = 1; + if (value->type == SPDK_JSON_VAL_NAME) { + prev_value = value; + value++; + i++; + if (!strncmp(prev_value->start, "req_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_req, value); + } else if (!strncmp(prev_value->start, "data_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "resp_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "type", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT32_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_tmf_req.type = tmp_val; + } + } else if (!strncmp(prev_value->start, "subtype", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT32_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_tmf_req.subtype = tmp_val; + } + } else if (!strncmp(prev_value->start, "lun", prev_value->len)) { + if (fuzz_get_base_64_buffer_value(&io_ctx->req.scsi_tmf_req.lun, + sizeof(io_ctx->req.scsi_tmf_req.lun), + (char *)value->start, + value->len)) { + nested_object_size = -1; + } + } else if (!strncmp(prev_value->start, "tag", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT64_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_tmf_req.tag = tmp_val; + } + } + } + if (nested_object_size < 0) { + fprintf(stderr, "Invalid value supplied for io_ctx->%.*s: %.*s\n", prev_value->len, + (char *)prev_value->start, value->len, (char *)value->start); + return false; + } + value += nested_object_size; + i += nested_object_size; + } + return true; +} +/* data parsing functions end */ + +/* build requests begin */ +static void +craft_io_from_array(struct fuzz_vhost_io_ctx *src_ctx, struct fuzz_vhost_io_ctx *dest_ctx) +{ + if (g_keep_iov_pointers) { + dest_ctx->iovs = src_ctx->iovs; + } + dest_ctx->req = src_ctx->req; +} + +static void +craft_virtio_scsi_req(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + io_ctx->iovs.iov_req.iov_len = sizeof(io_ctx->req.scsi_req); + io_ctx->iovs.iov_resp.iov_len = sizeof(io_ctx->resp.scsi_resp); + fuzz_fill_random_bytes((char *)&io_ctx->req.scsi_req, sizeof(io_ctx->req.scsi_req), + &dev_ctx->random_seed); + /* TODO: set up the logic to find all luns on the target. Right now we are just assuming the first is OK. */ + if (dev_ctx->valid_lun) { + io_ctx->req.scsi_req.lun[0] = 1; + io_ctx->req.scsi_req.lun[1] = 0; + } +} + +static void +craft_virtio_scsi_tmf_req(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + io_ctx->iovs.iov_req.iov_len = sizeof(io_ctx->req.scsi_tmf_req); + io_ctx->iovs.iov_resp.iov_len = sizeof(io_ctx->resp.scsi_tmf_resp); + fuzz_fill_random_bytes((char *)&io_ctx->req.scsi_tmf_req, sizeof(io_ctx->req.scsi_tmf_req), + &dev_ctx->random_seed); + /* TODO: set up the logic to find all luns on the target. Right now we are just assuming the first is OK. */ + if (dev_ctx->valid_lun) { + io_ctx->req.scsi_tmf_req.lun[0] = 1; + io_ctx->req.scsi_tmf_req.lun[1] = 0; + } + + /* Valid controlqueue commands have to be of type 0, 1, or 2. Any others just return immediately from the target. */ + /* Try to only test the opcodes that will exercise extra paths in the target side. But allow for at least one invalid value. */ + io_ctx->req.scsi_tmf_req.type = rand() % 4; +} + +static void +craft_virtio_blk_req(struct fuzz_vhost_io_ctx *io_ctx) +{ + io_ctx->iovs.iov_req.iov_len = sizeof(io_ctx->req.blk_req); + io_ctx->iovs.iov_resp.iov_len = sizeof(io_ctx->resp.blk_resp); + io_ctx->req.blk_req.type = rand(); + io_ctx->req.blk_req.sector = rand(); +} + +static void +craft_virtio_req_rsp_pair(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + struct fuzz_vhost_iov_ctx *iovs = &io_ctx->iovs; + + /* + * Always set these buffer values up front. + * If the user wants to override this with the json values, + * they can specify -k when starting the app. */ + iovs->iov_req.iov_base = &io_ctx->req; + if (dev_ctx->use_bogus_buffer) { + iovs->iov_data.iov_len = rand(); + iovs->iov_data.iov_base = get_invalid_mem_address(iovs->iov_data.iov_len); + } else if (dev_ctx->use_valid_buffer) { + iovs->iov_data.iov_len = 1024; + iovs->iov_data.iov_base = g_valid_buffer; + } + iovs->iov_resp.iov_base = &io_ctx->resp; + + if (dev_ctx->socket_is_blk && g_blk_cmd_array) { + craft_io_from_array(&g_blk_cmd_array[dev_ctx->submitted_io], io_ctx); + return; + } else if (dev_ctx->test_scsi_tmf && g_scsi_mgmt_cmd_array) { + craft_io_from_array(&g_scsi_mgmt_cmd_array[dev_ctx->submitted_io], io_ctx); + return; + } else if (g_scsi_cmd_array) { + craft_io_from_array(&g_scsi_cmd_array[dev_ctx->submitted_io], io_ctx); + return; + } + + if (dev_ctx->socket_is_blk) { + craft_virtio_blk_req(io_ctx); + } else if (dev_ctx->test_scsi_tmf) { + craft_virtio_scsi_tmf_req(dev_ctx, io_ctx); + } else { + craft_virtio_scsi_req(dev_ctx, io_ctx); + } +} +/* build requests end */ + +/* submit requests begin */ +static uint64_t +get_max_num_io(struct fuzz_vhost_dev_ctx *dev_ctx) +{ + if (dev_ctx->socket_is_blk) { + return g_blk_cmd_array_size; + } else if (dev_ctx->test_scsi_tmf) { + return g_scsi_mgmt_cmd_array_size; + } else { + return g_scsi_cmd_array_size; + } +} + +static int +submit_virtio_req_rsp_pair(struct fuzz_vhost_dev_ctx *dev_ctx, struct virtqueue *vq, + struct fuzz_vhost_io_ctx *io_ctx) +{ + struct fuzz_vhost_iov_ctx *iovs = &io_ctx->iovs; + int num_iovs = 2, rc; + + num_iovs += dev_ctx->use_bogus_buffer || dev_ctx->use_valid_buffer ? 1 : 0; + + rc = virtqueue_req_start(vq, io_ctx, num_iovs); + if (rc) { + return rc; + } + virtqueue_req_add_iovs(vq, &iovs->iov_req, 1, SPDK_VIRTIO_DESC_RO); + /* blk and scsi requests favor different orders for the iov objects. */ + if (dev_ctx->socket_is_blk) { + if (dev_ctx->use_bogus_buffer || dev_ctx->use_valid_buffer) { + virtqueue_req_add_iovs(vq, &iovs->iov_data, 1, SPDK_VIRTIO_DESC_WR); + } + virtqueue_req_add_iovs(vq, &iovs->iov_resp, 1, SPDK_VIRTIO_DESC_WR); + } else { + virtqueue_req_add_iovs(vq, &iovs->iov_resp, 1, SPDK_VIRTIO_DESC_WR); + if (dev_ctx->use_bogus_buffer || dev_ctx->use_valid_buffer) { + virtqueue_req_add_iovs(vq, &iovs->iov_data, 1, SPDK_VIRTIO_DESC_WR); + } + } + virtqueue_req_flush(vq); + return 0; +} + +static void +dev_submit_requests(struct fuzz_vhost_dev_ctx *dev_ctx, struct virtqueue *vq, + uint64_t max_io_to_submit) +{ + struct fuzz_vhost_io_ctx *io_ctx; + int rc; + + while (!TAILQ_EMPTY(&dev_ctx->free_io_ctx) && dev_ctx->submitted_io < max_io_to_submit) { + io_ctx = TAILQ_FIRST(&dev_ctx->free_io_ctx); + craft_virtio_req_rsp_pair(dev_ctx, io_ctx); + rc = submit_virtio_req_rsp_pair(dev_ctx, vq, io_ctx); + if (rc == 0) { + TAILQ_REMOVE(&dev_ctx->free_io_ctx, io_ctx, link); + TAILQ_INSERT_TAIL(&dev_ctx->outstanding_io_ctx, io_ctx, link); + dev_ctx->submitted_io++; + } else if (rc == -ENOMEM) { + /* There are just not enough available buffers right now. try later. */ + return; + } else if (rc == -EINVAL) { + /* The virtqueue must be broken. We know we can fit at least three descriptors */ + fprintf(stderr, "One of the virtqueues for dev %p is broken. stopping all devices.\n", dev_ctx); + g_run = 0; + } + } +} +/* submit requests end */ + +/* complete requests begin */ +static void +check_successful_op(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + bool is_successful = false; + + if (dev_ctx->socket_is_blk) { + if (io_ctx->resp.blk_resp == 0) { + is_successful = true; + } + } else if (dev_ctx->test_scsi_tmf) { + if (io_ctx->resp.scsi_tmf_resp.scsi_tmf_resp.response == 0 && + io_ctx->resp.scsi_tmf_resp.an_resp.response == 0) { + is_successful = true; + } + } else { + if (io_ctx->resp.scsi_resp.status == 0) { + is_successful = true; + } + } + + if (is_successful) { + fprintf(stderr, "An I/O completed without an error status. This could be worth looking into.\n"); + fprintf(stderr, + "There is also a good chance that the target just failed before setting a status.\n"); + dev_ctx->successful_io++; + print_req_obj(dev_ctx, io_ctx); + } else if (g_verbose_mode) { + fprintf(stderr, "The following I/O failed as expected.\n"); + print_req_obj(dev_ctx, io_ctx); + } +} + +static void +complete_io(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + TAILQ_REMOVE(&dev_ctx->outstanding_io_ctx, io_ctx, link); + TAILQ_INSERT_HEAD(&dev_ctx->free_io_ctx, io_ctx, link); + check_successful_op(dev_ctx, io_ctx); + dev_ctx->completed_io++; + dev_ctx->timeout_tsc = fuzz_refresh_timeout(); +} + +static int +poll_dev(void *ctx) +{ + struct fuzz_vhost_dev_ctx *dev_ctx = ctx; + struct virtqueue *vq; + struct fuzz_vhost_io_ctx *io_ctx[FUZZ_QUEUE_DEPTH]; + int num_active_threads; + uint64_t max_io_to_complete = UINT64_MAX; + uint64_t current_ticks; + uint32_t len[FUZZ_QUEUE_DEPTH]; + uint16_t num_cpl, i; + + if (g_json_file) { + max_io_to_complete = get_max_num_io(dev_ctx); + } + + if (!dev_ctx->socket_is_blk && dev_ctx->test_scsi_tmf) { + vq = dev_ctx->virtio_dev.vqs[VIRTIO_SCSI_CONTROLQ]; + } else { + vq = dev_ctx->virtio_dev.vqs[VIRTIO_REQUESTQ]; + } + + num_cpl = virtio_recv_pkts(vq, (void **)io_ctx, len, FUZZ_QUEUE_DEPTH); + + for (i = 0; i < num_cpl; i++) { + complete_io(dev_ctx, io_ctx[i]); + } + + current_ticks = spdk_get_ticks(); + + if (current_ticks > dev_ctx->timeout_tsc) { + dev_ctx->timed_out = true; + g_run = false; + fprintf(stderr, "The VQ on device %p timed out. Dumping contents now.\n", dev_ctx); + dump_outstanding_io(dev_ctx); + } + + if (current_ticks > g_runtime_ticks) { + g_run = 0; + } + + if (!g_run || dev_ctx->completed_io >= max_io_to_complete) { + if (TAILQ_EMPTY(&dev_ctx->outstanding_io_ctx)) { + spdk_poller_unregister(&dev_ctx->poller); + num_active_threads = __sync_sub_and_fetch(&g_num_active_threads, 1); + if (num_active_threads == 0) { + g_run = 0; + } + spdk_thread_exit(dev_ctx->thread); + } + return 0; + } + + dev_submit_requests(dev_ctx, vq, max_io_to_complete); + return 0; +} +/* complete requests end */ + +static void +start_io(void *ctx) +{ + struct fuzz_vhost_dev_ctx *dev_ctx = ctx; + + if (g_random_seed) { + dev_ctx->random_seed = g_random_seed; + } else { + dev_ctx->random_seed = spdk_get_ticks(); + } + + dev_ctx->timeout_tsc = fuzz_refresh_timeout(); + + dev_ctx->poller = SPDK_POLLER_REGISTER(poll_dev, dev_ctx, 0); + if (dev_ctx->poller == NULL) { + return; + } + +} + +static int +end_fuzz(void *ctx) +{ + if (!g_run && !g_num_active_threads) { + spdk_poller_unregister(&g_run_poller); + cleanup(); + spdk_app_stop(0); + } + return 0; +} + +static void +begin_fuzz(void *ctx) +{ + struct fuzz_vhost_dev_ctx *dev_ctx; + + g_runtime_ticks = spdk_get_ticks() + spdk_get_ticks_hz() * g_runtime; + + g_valid_buffer = spdk_malloc(0x1000, 0x200, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_SHARE); + if (g_valid_buffer == NULL) { + fprintf(stderr, "Failed to allocate a valid buffer for I/O\n"); + goto out; + } + + g_run_poller = SPDK_POLLER_REGISTER(end_fuzz, NULL, 0); + if (g_run_poller == NULL) { + fprintf(stderr, "Failed to register a poller for test completion checking.\n"); + } + + TAILQ_FOREACH(dev_ctx, &g_dev_list, link) { + assert(dev_ctx->thread != NULL); + spdk_thread_send_msg(dev_ctx->thread, start_io, dev_ctx); + __sync_add_and_fetch(&g_num_active_threads, 1); + } + + return; +out: + cleanup(); + spdk_app_stop(0); +} + +static void +fuzz_vhost_usage(void) +{ + fprintf(stderr, " -j Path to a json file containing named objects.\n"); + fprintf(stderr, + " -k Keep the iov pointer addresses from the json file. only valid with -j.\n"); + fprintf(stderr, " -S Seed value for test.\n"); + fprintf(stderr, " -t Time in seconds to run the fuzz test.\n"); + fprintf(stderr, " -V Enable logging of each submitted command.\n"); +} + +static int +fuzz_vhost_parse(int ch, char *arg) +{ + int64_t error_test; + + switch (ch) { + case 'j': + g_json_file = optarg; + break; + case 'k': + g_keep_iov_pointers = true; + break; + case 'S': + error_test = spdk_strtol(arg, 10); + if (error_test < 0) { + fprintf(stderr, "Invalid value supplied for the random seed.\n"); + return -1; + } else { + g_random_seed = spdk_strtol(arg, 10); + } + break; + case 't': + g_runtime = spdk_strtol(arg, 10); + if (g_runtime < 0 || g_runtime > MAX_RUNTIME_S) { + fprintf(stderr, "You must supply a positive runtime value less than 86401.\n"); + return -1; + } + break; + case 'V': + g_verbose_mode = true; + break; + case '?': + default: + return -EINVAL; + } + return 0; +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc; + + spdk_app_opts_init(&opts); + opts.name = "vhost_fuzz"; + g_runtime = DEFAULT_RUNTIME; + + rc = spdk_app_parse_args(argc, argv, &opts, "j:kS:t:V", NULL, fuzz_vhost_parse, fuzz_vhost_usage); + if (rc != SPDK_APP_PARSE_ARGS_SUCCESS) { + fprintf(stderr, "Unable to parse the application arguments.\n"); + return -1; + } + + if (g_json_file != NULL) { + g_blk_cmd_array_size = fuzz_parse_args_into_array(g_json_file, + (void **)&g_blk_cmd_array, + sizeof(struct fuzz_vhost_io_ctx), + BLK_IO_NAME, parse_vhost_blk_cmds); + g_scsi_cmd_array_size = fuzz_parse_args_into_array(g_json_file, + (void **)&g_scsi_cmd_array, + sizeof(struct fuzz_vhost_io_ctx), + SCSI_IO_NAME, parse_vhost_scsi_cmds); + g_scsi_mgmt_cmd_array_size = fuzz_parse_args_into_array(g_json_file, + (void **)&g_scsi_mgmt_cmd_array, + sizeof(struct fuzz_vhost_io_ctx), + SCSI_IO_NAME, parse_vhost_scsi_mgmt_cmds); + if (g_blk_cmd_array_size == 0 && g_scsi_cmd_array_size == 0 && g_scsi_mgmt_cmd_array_size == 0) { + fprintf(stderr, "The provided json file did not contain any valid commands. Exiting.\n"); + return -EINVAL; + } + } + + spdk_app_start(&opts, begin_fuzz, NULL); +} diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.h b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.h new file mode 100644 index 000000000..df71a846d --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.h @@ -0,0 +1,41 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef VHOST_FUZZ_H +#define VHOST_FUZZ_H + +int fuzz_vhost_dev_init(const char *socket_path, bool is_blk_dev, bool use_bogus_buffer, + bool use_valid_buffer, bool valid_lun, bool test_scsi_tmf); + +#endif diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz_rpc.c b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz_rpc.c new file mode 100644 index 000000000..b60e3f097 --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz_rpc.c @@ -0,0 +1,108 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. All rights reserved. + * Copyright (c) 2018 Mellanox Technologies LTD. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" +#include "spdk/rpc.h" +#include "spdk/util.h" + +#include "vhost_fuzz.h" + +struct rpc_fuzz_vhost_dev_create { + char *socket; + bool is_blk; + bool use_bogus_buffer; + bool use_valid_buffer; + bool valid_lun; + bool test_scsi_tmf; +}; + +static const struct spdk_json_object_decoder rpc_fuzz_vhost_dev_create_decoders[] = { + {"socket", offsetof(struct rpc_fuzz_vhost_dev_create, socket), spdk_json_decode_string}, + {"is_blk", offsetof(struct rpc_fuzz_vhost_dev_create, is_blk), spdk_json_decode_bool, true}, + {"use_bogus_buffer", offsetof(struct rpc_fuzz_vhost_dev_create, use_bogus_buffer), spdk_json_decode_bool, true}, + {"use_valid_buffer", offsetof(struct rpc_fuzz_vhost_dev_create, use_valid_buffer), spdk_json_decode_bool, true}, + {"valid_lun", offsetof(struct rpc_fuzz_vhost_dev_create, valid_lun), spdk_json_decode_bool, true}, + {"test_scsi_tmf", offsetof(struct rpc_fuzz_vhost_dev_create, test_scsi_tmf), spdk_json_decode_bool, true}, +}; + +static void +spdk_rpc_fuzz_vhost_create_dev(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct spdk_json_write_ctx *w; + struct rpc_fuzz_vhost_dev_create req = {0}; + int rc; + + if (spdk_json_decode_object(params, rpc_fuzz_vhost_dev_create_decoders, + SPDK_COUNTOF(rpc_fuzz_vhost_dev_create_decoders), &req)) { + fprintf(stderr, "Unable to parse the request.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Unable to parse the object parameters.\n"); + return; + } + + if (strlen(req.socket) > PATH_MAX) { + fprintf(stderr, "Socket address is too long.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Unable to parse the object parameters.\n"); + free(req.socket); + return; + } + + rc = fuzz_vhost_dev_init(req.socket, req.is_blk, req.use_bogus_buffer, req.use_valid_buffer, + req.valid_lun, req.test_scsi_tmf); + + if (rc != 0) { + if (rc == -ENOMEM) { + fprintf(stderr, "No valid memory for device initialization.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "No memory returned from host.\n"); + } else if (rc == -EINVAL) { + fprintf(stderr, "Invalid device parameters provided.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Parameters provided were invalid.\n"); + } else { + fprintf(stderr, "unknown error from the guest.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "Unexpected error code.\n"); + } + } else { + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + } + + free(req.socket); + return; +} +SPDK_RPC_REGISTER("fuzz_vhost_create_dev", spdk_rpc_fuzz_vhost_create_dev, SPDK_RPC_STARTUP); diff --git a/src/spdk/test/app/histogram_perf/.gitignore b/src/spdk/test/app/histogram_perf/.gitignore new file mode 100644 index 000000000..c77b05312 --- /dev/null +++ b/src/spdk/test/app/histogram_perf/.gitignore @@ -0,0 +1 @@ +histogram_perf diff --git a/src/spdk/test/app/histogram_perf/Makefile b/src/spdk/test/app/histogram_perf/Makefile new file mode 100644 index 000000000..b60c704a8 --- /dev/null +++ b/src/spdk/test/app/histogram_perf/Makefile @@ -0,0 +1,43 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = histogram_perf + +C_SRCS = histogram_perf.c + +SPDK_LIB_LIST = thread util log + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/app/histogram_perf/histogram_perf.c b/src/spdk/test/app/histogram_perf/histogram_perf.c new file mode 100644 index 000000000..5d9de5274 --- /dev/null +++ b/src/spdk/test/app/histogram_perf/histogram_perf.c @@ -0,0 +1,102 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk/env.h" +#include "spdk/util.h" +#include "spdk/histogram_data.h" + +/* + * This applications is a simple test app used to test the performance of + * tallying datapoints with struct spdk_histogram_data. It can be used + * to measure the effect of changes to the spdk_histogram_data implementation. + * + * There are no command line parameters currently - it just tallies + * datapoints for 10 seconds in a default-sized histogram structure and + * then prints out the number of tallies performed. + */ + +static void +usage(const char *prog) +{ + printf("usage: %s\n", prog); + printf("Options:\n"); +} + +int +main(int argc, char **argv) +{ + struct spdk_histogram_data *h; + struct spdk_env_opts opts; + uint64_t tsc[128], t, end_tsc, count; + uint32_t i; + int ch; + int rc = 0; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage(argv[0]); + return 1; + } + } + + spdk_env_opts_init(&opts); + if (spdk_env_init(&opts)) { + printf("Err: Unable to initialize SPDK env\n"); + return 1; + } + + for (i = 0; i < SPDK_COUNTOF(tsc); i++) { + tsc[i] = spdk_get_ticks(); + } + + end_tsc = spdk_get_ticks() + (10 * spdk_get_ticks_hz()); + count = 0; + h = spdk_histogram_data_alloc(); + + while (true) { + t = spdk_get_ticks(); + spdk_histogram_data_tally(h, t - tsc[count % 128]); + count++; + if (t > end_tsc) { + break; + } + } + + printf("count = %ju\n", count); + spdk_histogram_data_free(h); + + return rc; +} diff --git a/src/spdk/test/app/jsoncat/.gitignore b/src/spdk/test/app/jsoncat/.gitignore new file mode 100644 index 000000000..3e6db4f0e --- /dev/null +++ b/src/spdk/test/app/jsoncat/.gitignore @@ -0,0 +1 @@ +jsoncat diff --git a/src/spdk/test/app/jsoncat/Makefile b/src/spdk/test/app/jsoncat/Makefile new file mode 100644 index 000000000..2635e501b --- /dev/null +++ b/src/spdk/test/app/jsoncat/Makefile @@ -0,0 +1,43 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = jsoncat + +C_SRCS = jsoncat.c + +SPDK_LIB_LIST = json thread util log + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/app/jsoncat/jsoncat.c b/src/spdk/test/app/jsoncat/jsoncat.c new file mode 100644 index 000000000..e932b54bd --- /dev/null +++ b/src/spdk/test/app/jsoncat/jsoncat.c @@ -0,0 +1,192 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* Simple JSON "cat" utility */ + +#include "spdk/stdinc.h" + +#include "spdk/json.h" +#include "spdk/file.h" + +static void +usage(const char *prog) +{ + printf("usage: %s [-c] [-f] file.json\n", prog); + printf("Options:\n"); + printf("-c\tallow comments in input (non-standard)\n"); + printf("-f\tformatted output (default: compact output)\n"); +} + +static void +print_json_error(FILE *pf, int rc, const char *filename) +{ + switch (rc) { + case SPDK_JSON_PARSE_INVALID: + fprintf(pf, "%s: invalid JSON\n", filename); + break; + case SPDK_JSON_PARSE_INCOMPLETE: + fprintf(pf, "%s: incomplete JSON\n", filename); + break; + case SPDK_JSON_PARSE_MAX_DEPTH_EXCEEDED: + fprintf(pf, "%s: maximum nesting depth exceeded\n", filename); + break; + default: + fprintf(pf, "%s: unknown JSON parse error\n", filename); + break; + } +} + +static int +json_write_cb(void *cb_ctx, const void *data, size_t size) +{ + FILE *f = cb_ctx; + size_t rc; + + rc = fwrite(data, 1, size, f); + return rc == size ? 0 : -1; +} + +static int +process_file(const char *filename, FILE *f, uint32_t parse_flags, uint32_t write_flags) +{ + size_t size; + void *buf, *end; + ssize_t rc; + struct spdk_json_val *values; + size_t num_values; + struct spdk_json_write_ctx *w; + + buf = spdk_posix_file_load(f, &size); + if (buf == NULL) { + fprintf(stderr, "%s: file read error\n", filename); + return 1; + } + + rc = spdk_json_parse(buf, size, NULL, 0, NULL, parse_flags); + if (rc <= 0) { + print_json_error(stderr, rc, filename); + free(buf); + return 1; + } + + num_values = (size_t)rc; + values = calloc(num_values, sizeof(*values)); + if (values == NULL) { + perror("values calloc"); + free(buf); + return 1; + } + + rc = spdk_json_parse(buf, size, values, num_values, &end, + parse_flags | SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE); + if (rc <= 0) { + print_json_error(stderr, rc, filename); + free(values); + free(buf); + return 1; + } + + w = spdk_json_write_begin(json_write_cb, stdout, write_flags); + if (w == NULL) { + fprintf(stderr, "json_write_begin failed\n"); + free(values); + free(buf); + return 1; + } + + spdk_json_write_val(w, values); + spdk_json_write_end(w); + printf("\n"); + + if (end != buf + size) { + fprintf(stderr, "%s: garbage at end of file\n", filename); + free(values); + free(buf); + return 1; + } + + free(values); + free(buf); + return 0; +} + +int +main(int argc, char **argv) +{ + FILE *f; + int ch; + int rc; + uint32_t parse_flags = 0, write_flags = 0; + const char *filename; + + while ((ch = getopt(argc, argv, "cf")) != -1) { + switch (ch) { + case 'c': + parse_flags |= SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS; + break; + case 'f': + write_flags |= SPDK_JSON_WRITE_FLAG_FORMATTED; + break; + default: + usage(argv[0]); + return 1; + } + } + + if (optind == argc) { + filename = "-"; + } else if (optind == argc - 1) { + filename = argv[optind]; + } else { + usage(argv[0]); + return 1; + } + + if (strcmp(filename, "-") == 0) { + f = stdin; + } else { + f = fopen(filename, "r"); + if (f == NULL) { + perror("fopen"); + return 1; + } + } + + rc = process_file(filename, f, parse_flags, write_flags); + + if (f != stdin) { + fclose(f); + } + + return rc; +} diff --git a/src/spdk/test/app/match/match b/src/spdk/test/app/match/match new file mode 100755 index 000000000..63fee5203 --- /dev/null +++ b/src/spdk/test/app/match/match @@ -0,0 +1,332 @@ +#!/usr/bin/env perl +# +# Copyright 2014-2017, Intel Corporation +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# +# match -- compare an output file with expected results +# +# usage: match [-adoqv] [match-file]... +# +# this script compares the output from a test run, stored in a file, with +# the expected output. comparison is done line-by-line until either all +# lines compare correctly (exit code 0) or a miscompare is found (exit +# code nonzero). +# +# expected output is stored in a ".match" file, which contains a copy of +# the expected output with embedded tokens for things that should not be +# exact matches. the supported tokens are: +# +# $(N) an integer (i.e. one or more decimal digits) +# $(NC) one or more decimal digits with comma separators +# $(FP) a floating point number +# $(S) ascii string +# $(X) hex number +# $(XX) hex number prefixed with 0x +# $(W) whitespace +# $(nW) non-whitespace +# $(*) any string +# $(DD) output of a "dd" run +# $(OPT) line is optional (may be missing, matched if found) +# $(OPX) ends a contiguous list of $(OPT)...$(OPX) lines, at least +# one of which must match +# +# Additionally, if any "X.ignore" file exists, strings or phrases found per +# line in the file will be ignored if found as a substring in the +# corresponding output file (making it easy to skip entire output lines). +# +# arguments are: +# +# -a find all files of the form "X.match" in the current +# directory and match them again the corresponding file "X". +# +# -o custom output filename - only one match file can be given +# +# -d debug -- show lots of debug output +# +# -q don't print log files on mismatch +# +# -v verbose -- show every line as it is being matched +# + +use strict; +use Getopt::Std; +use Encode; +use v5.16; + +select STDERR; +binmode(STDOUT, ":utf8"); +binmode(STDERR, ":utf8"); + +my $Me = $0; +$Me =~ s,.*/,,; + +our ($opt_a, $opt_d, $opt_q, $opt_v, $opt_o); + +$SIG{HUP} = $SIG{INT} = $SIG{TERM} = $SIG{__DIE__} = sub { + die @_ if $^S; + my $errstr = shift; + die "FAIL: $Me: $errstr"; +}; + +sub usage { + my $msg = shift; + + warn "$Me: $msg\n" if $msg; + warn "Usage: $Me [-adqv] [match-file]...\n"; + warn " or: $Me [-dqv] -o output-file match-file...\n"; + exit 1; +} + +getopts('adoqv') or usage; + +my %match2file; + +if ($opt_a) { + usage("-a and filename arguments are mutually exclusive") + if $#ARGV != -1; + opendir(DIR, '.') or die "opendir: .: $!\n"; + my @matchfiles = grep { /(.*)\.match$/ && -f $1 } readdir(DIR); + closedir(DIR); + die "no files found to process\n" unless @matchfiles; + foreach my $mfile (@matchfiles) { + die "$mfile: $!\n" unless open(F, $mfile); + close(F); + my $ofile = $mfile; + $ofile =~ s/\.match$//; + die "$mfile found but cannot open $ofile: $!\n" + unless open(F, $ofile); + close(F); + $match2file{$mfile} = $ofile; + } +} elsif ($opt_o) { + usage("-o argument requires two paths") if $#ARGV != 1; + + $match2file{$ARGV[1]} = $ARGV[0]; +} else { + usage("no match-file arguments found") if $#ARGV == -1; + + # to improve the failure case, check all filename args exist and + # are provided in pairs now, before going through and processing them + foreach my $mfile (@ARGV) { + my $ofile = $mfile; + usage("$mfile: not a .match file") unless + $ofile =~ s/\.match$//; + usage("$mfile: $!") unless open(F, $mfile); + close(F); + usage("$ofile: $!") unless open(F, $ofile); + close(F); + $match2file{$mfile} = $ofile; + } +} + +my $mfile; +my $ofile; +my $ifile; +print "Files to be processed:\n" if $opt_v; +foreach $mfile (sort keys %match2file) { + $ofile = $match2file{$mfile}; + $ifile = $ofile . ".ignore"; + $ifile = undef unless (-f $ifile); + if ($opt_v) { + print " match-file \"$mfile\" output-file \"$ofile\""; + if ($ifile) { + print " ignore-file $ifile\n"; + } else { + print "\n"; + } + } + match($mfile, $ofile, $ifile); +} + +exit 0; + +# +# strip_it - user can optionally ignore lines from files that contain +# any number of substrings listed in a file called "X.ignore" where X +# is the name of the output file. +# +sub strip_it { + my ($ifile, $file, $input) = @_; + # if there is no ignore file just return unaltered input + return $input unless $ifile; + my @lines_in = split /^/, $input; + my $output; + my $line_in; + my @i_file = split /^/, snarf($ifile); + my $i_line; + my $ignore_it = 0; + + foreach $line_in (@lines_in) { + my @i_lines = @i_file; + foreach $i_line (@i_lines) { + # Check if both ignore and input lines are new lines + if ($i_line eq "\n" && $line_in eq "\n") { + $ignore_it = 1; + last; + } + # Find the ignore string in input line + chomp($i_line); + if (index($line_in, $i_line) != -1 && length($i_line) != 0) { + $ignore_it = 1; + last; + } + } + if ($ignore_it == 0) { + $output .= $line_in; + } elsif($opt_v) { + print "Ignoring (from $file): $line_in"; + } + $ignore_it = 0; + } + return $output; +} + +# +# match -- process a match-file, output-file pair +# +sub match { + my ($mfile, $ofile, $ifile) = @_; + my $pat; + my $output = snarf($ofile); + $output = strip_it($ifile, $ofile, $output); + my $all_lines = $output; + my $line_pat = 0; + my $line_out = 0; + my $opt = 0; + my $opx = 0; + my $opt_found = 0; + my $fstr = snarf($mfile); + $fstr = strip_it($ifile, $mfile, $fstr); + for (split /^/, $fstr) { + $pat = $_; + $line_pat++; + $line_out++; + s/([*+?|{}.\\^\$\[()])/\\$1/g; + s/\\\$\\\(FP\\\)/[-+]?\\d*\\.?\\d+([eE][-+]?\\d+)?/g; + s/\\\$\\\(N\\\)/[-+]?\\d+/g; + s/\\\$\\\(NC\\\)/[-+]?\\d+(,[0-9]+)*/g; + s/\\\$\\\(\\\*\\\)/\\p{Print}*/g; + s/\\\$\\\(S\\\)/\\P{IsC}+/g; + s/\\\$\\\(X\\\)/\\p{XPosixXDigit}+/g; + s/\\\$\\\(XX\\\)/0x\\p{XPosixXDigit}+/g; + s/\\\$\\\(W\\\)/\\p{Blank}*/g; + s/\\\$\\\(nW\\\)/\\p{Graph}*/g; + s/\\\$\\\(DD\\\)/\\d+\\+\\d+ records in\n\\d+\\+\\d+ records out\n\\d+ bytes \\\(\\d+ .B\\\) copied, [.0-9e-]+[^,]*, [.0-9]+ .B.s/g; + if (s/\\\$\\\(OPT\\\)//) { + $opt = 1; + } elsif (s/\\\$\\\(OPX\\\)//) { + $opx = 1; + } else { + $opt_found = 0; + } + + if ($opt_v) { + my @lines = split /\n/, $output; + my $line; + if (@lines) { + $line = $lines[0]; + } else { + $line = "[EOF]"; + } + + printf("%s:%-3d %s%s:%-3d %s\n", $mfile, $line_pat, $pat, $ofile, $line_out, $line); + } + + print " => /$_/\n" if $opt_d; + print " [$output]\n" if $opt_d; + unless ($output =~ s/^$_//) { + if ($opt || ($opx && $opt_found)) { + printf("%s:%-3d [skipping optional line]\n", $ofile, $line_out) if $opt_v; + $line_out--; + $opt = 0; + } else { + if (!$opt_v) { + if ($opt_q) { + print "[MATCHING FAILED]\n"; + } else { + print "[MATCHING FAILED, COMPLETE FILE ($ofile) BELOW]\n$all_lines\n[EOF]\n"; + } + $opt_v = 1; + match($mfile, $ofile); + } + + die "$mfile:$line_pat did not match pattern\n"; + } + } elsif ($opt) { + $opt_found = 1; + } + $opx = 0; + } + + if ($output ne '') { + if (!$opt_v) { + if ($opt_q) { + print "[MATCHING FAILED]\n"; + } else { + print "[MATCHING FAILED, COMPLETE FILE ($ofile) BELOW]\n$all_lines\n[EOF]\n"; + } + } + + # make it a little more print-friendly... + $output =~ s/\n/\\n/g; + die "line $line_pat: unexpected output: \"$output\"\n"; + } +} + + +# +# snarf -- slurp an entire file into memory +# +sub snarf { + my ($file) = @_; + my $fh; + open($fh, '<', $file) or die "$file $!\n"; + + local $/; + $_ = <$fh>; + close $fh; + + # check known encodings or die + my $decoded; + my @encodings = ("UTF-8", "UTF-16", "UTF-16LE", "UTF-16BE"); + + foreach my $enc (@encodings) { + eval { $decoded = decode( $enc, $_, Encode::FB_CROAK ) }; + + if (!$@) { + $decoded =~ s/\R/\n/g; + return $decoded; + } + } + + die "$Me: ERROR: Unknown file encoding"; +} diff --git a/src/spdk/test/app/stub/.gitignore b/src/spdk/test/app/stub/.gitignore new file mode 100644 index 000000000..39802f642 --- /dev/null +++ b/src/spdk/test/app/stub/.gitignore @@ -0,0 +1 @@ +stub diff --git a/src/spdk/test/app/stub/Makefile b/src/spdk/test/app/stub/Makefile new file mode 100644 index 000000000..79ca8a912 --- /dev/null +++ b/src/spdk/test/app/stub/Makefile @@ -0,0 +1,49 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = stub + +C_SRCS := stub.c + +SPDK_LIB_LIST = $(SOCK_MODULES_LIST) +SPDK_LIB_LIST += event conf nvme log trace rpc jsonrpc json thread util sock notify + +ifeq ($(CONFIG_RDMA),y) +SPDK_LIB_LIST += rdma +endif + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/app/stub/stub.c b/src/spdk/test/app/stub/stub.c new file mode 100644 index 000000000..83d9f706f --- /dev/null +++ b/src/spdk/test/app/stub/stub.c @@ -0,0 +1,203 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk/event.h" +#include "spdk/nvme.h" +#include "spdk/string.h" +#include "spdk/thread.h" + +static char g_path[256]; +static struct spdk_poller *g_poller; + +struct ctrlr_entry { + struct spdk_nvme_ctrlr *ctrlr; + struct ctrlr_entry *next; +}; + +static struct ctrlr_entry *g_controllers = NULL; + +static void +cleanup(void) +{ + struct ctrlr_entry *ctrlr_entry = g_controllers; + + while (ctrlr_entry) { + struct ctrlr_entry *next = ctrlr_entry->next; + + spdk_nvme_detach(ctrlr_entry->ctrlr); + free(ctrlr_entry); + ctrlr_entry = next; + } +} + +static void +usage(char *executable_name) +{ + printf("%s [options]\n", executable_name); + printf("options:\n"); + printf(" -i shared memory ID [required]\n"); + printf(" -m mask core mask for DPDK\n"); + printf(" -n channel number of memory channels used for DPDK\n"); + printf(" -p core master (primary) core for DPDK\n"); + printf(" -s size memory size in MB for DPDK\n"); + printf(" -H show this usage\n"); +} + +static bool +probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr_opts *opts) +{ + /* + * Set the io_queue_size to UINT16_MAX to initialize + * the controller with the possible largest queue size. + */ + opts->io_queue_size = UINT16_MAX; + return true; +} + +static void +attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ + struct ctrlr_entry *entry; + + entry = malloc(sizeof(struct ctrlr_entry)); + if (entry == NULL) { + fprintf(stderr, "Malloc error\n"); + exit(1); + } + + entry->ctrlr = ctrlr; + entry->next = g_controllers; + g_controllers = entry; +} + +static int +stub_sleep(void *arg) +{ + usleep(1000 * 1000); + return 0; +} + +static void +stub_start(void *arg1) +{ + int shm_id = (intptr_t)arg1; + + spdk_unaffinitize_thread(); + + if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) { + fprintf(stderr, "spdk_nvme_probe() failed\n"); + exit(1); + } + + snprintf(g_path, sizeof(g_path), "/var/run/spdk_stub%d", shm_id); + if (mknod(g_path, S_IFREG, 0) != 0) { + fprintf(stderr, "could not create sentinel file %s\n", g_path); + exit(1); + } + + g_poller = SPDK_POLLER_REGISTER(stub_sleep, NULL, 0); +} + +static void +stub_shutdown(void) +{ + spdk_poller_unregister(&g_poller); + unlink(g_path); + spdk_app_stop(0); +} + +int +main(int argc, char **argv) +{ + int ch; + struct spdk_app_opts opts = {}; + long int val; + + /* default value in opts structure */ + spdk_app_opts_init(&opts); + + opts.name = "stub"; + opts.rpc_addr = NULL; + + while ((ch = getopt(argc, argv, "i:m:n:p:s:H")) != -1) { + if (ch == 'm') { + opts.reactor_mask = optarg; + } else if (ch == '?') { + usage(argv[0]); + exit(1); + } else { + val = spdk_strtol(optarg, 10); + if (val < 0) { + fprintf(stderr, "Converting a string to integer failed\n"); + exit(1); + } + switch (ch) { + case 'i': + opts.shm_id = val; + break; + case 'n': + opts.mem_channel = val; + break; + case 'p': + opts.master_core = val; + break; + case 's': + opts.mem_size = val; + break; + case 'H': + default: + usage(argv[0]); + exit(EXIT_SUCCESS); + } + } + } + + if (opts.shm_id < 0) { + fprintf(stderr, "%s: -i shared memory ID must be specified\n", argv[0]); + usage(argv[0]); + exit(1); + } + + opts.shutdown_cb = stub_shutdown; + + ch = spdk_app_start(&opts, stub_start, (void *)(intptr_t)opts.shm_id); + + cleanup(); + spdk_app_fini(); + + return ch; +} diff --git a/src/spdk/test/bdev/Makefile b/src/spdk/test/bdev/Makefile new file mode 100644 index 000000000..cb15bd49a --- /dev/null +++ b/src/spdk/test/bdev/Makefile @@ -0,0 +1,44 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = bdevio bdevperf + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/bdev/bdev_raid.sh b/src/spdk/test/bdev/bdev_raid.sh new file mode 100755 index 000000000..c85d33f6e --- /dev/null +++ b/src/spdk/test/bdev/bdev_raid.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../..) +rpc_server=/var/tmp/spdk-raid.sock +rpc_py="$rootdir/scripts/rpc.py -s $rpc_server" +tmp_file=$SPDK_TEST_STORAGE/raidrandtest + +source $rootdir/test/common/autotest_common.sh +source $testdir/nbd_common.sh + +function raid_unmap_data_verify() { + if hash blkdiscard; then + local nbd=$1 + local rpc_server=$2 + local blksize + blksize=$(lsblk -o LOG-SEC $nbd | grep -v LOG-SEC | cut -d ' ' -f 5) + local rw_blk_num=4096 + local rw_len=$((blksize * rw_blk_num)) + local unmap_blk_offs=(0 1028 321) + local unmap_blk_nums=(128 2035 456) + local unmap_off + local unmap_len + + # data write + dd if=/dev/urandom of=$tmp_file bs=$blksize count=$rw_blk_num + dd if=$tmp_file of=$nbd bs=$blksize count=$rw_blk_num oflag=direct + blockdev --flushbufs $nbd + + # confirm random data is written correctly in raid0 device + cmp -b -n $rw_len $tmp_file $nbd + + for ((i = 0; i < ${#unmap_blk_offs[@]}; i++)); do + unmap_off=$((blksize * ${unmap_blk_offs[$i]})) + unmap_len=$((blksize * ${unmap_blk_nums[$i]})) + + # data unmap on tmp_file + dd if=/dev/zero of=$tmp_file bs=$blksize seek=${unmap_blk_offs[$i]} count=${unmap_blk_nums[$i]} conv=notrunc + + # data unmap on raid bdev + blkdiscard -o $unmap_off -l $unmap_len $nbd + blockdev --flushbufs $nbd + + # data verify after unmap + cmp -b -n $rw_len $tmp_file $nbd + done + fi + + return 0 +} + +function on_error_exit() { + if [ -n "$raid_pid" ]; then + killprocess $raid_pid + fi + + rm -f $tmp_file + print_backtrace + exit 1 +} + +function configure_raid_bdev() { + rm -rf $testdir/rpcs.txt + + cat <<- EOL >> $testdir/rpcs.txt + bdev_malloc_create 32 512 -b Base_1 + bdev_malloc_create 32 512 -b Base_2 + bdev_raid_create -z 64 -r 0 -b "Base_1 Base_2" -n raid0 + EOL + $rpc_py < $testdir/rpcs.txt + + rm -rf $testdir/rpcs.txt +} + +function raid_function_test() { + if [ $(uname -s) = Linux ] && modprobe -n nbd; then + local nbd=/dev/nbd0 + local raid_bdev + + modprobe nbd + $rootdir/test/app/bdev_svc/bdev_svc -r $rpc_server -i 0 -L bdev_raid & + raid_pid=$! + echo "Process raid pid: $raid_pid" + waitforlisten $raid_pid $rpc_server + + configure_raid_bdev + raid_bdev=$($rpc_py bdev_raid_get_bdevs online | cut -d ' ' -f 1) + if [ $raid_bdev = "" ]; then + echo "No raid0 device in SPDK app" + return 1 + fi + + nbd_start_disks $rpc_server $raid_bdev $nbd + count=$(nbd_get_count $rpc_server) + if [ $count -ne 1 ]; then + return 1 + fi + + raid_unmap_data_verify $nbd $rpc_server + + nbd_stop_disks $rpc_server $nbd + count=$(nbd_get_count $rpc_server) + if [ $count -ne 0 ]; then + return 1 + fi + + killprocess $raid_pid + else + echo "skipping bdev raid tests." + fi + + return 0 +} + +trap 'on_error_exit;' ERR + +raid_function_test + +rm -f $tmp_file diff --git a/src/spdk/test/bdev/bdevio/.gitignore b/src/spdk/test/bdev/bdevio/.gitignore new file mode 100644 index 000000000..1bb55429d --- /dev/null +++ b/src/spdk/test/bdev/bdevio/.gitignore @@ -0,0 +1 @@ +bdevio diff --git a/src/spdk/test/bdev/bdevio/Makefile b/src/spdk/test/bdev/bdevio/Makefile new file mode 100644 index 000000000..83aca58ca --- /dev/null +++ b/src/spdk/test/bdev/bdevio/Makefile @@ -0,0 +1,48 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = bdevio + +C_SRCS := bdevio.c + +SPDK_LIB_LIST = $(ALL_MODULES_LIST) +SPDK_LIB_LIST += $(EVENT_BDEV_SUBSYSTEM) +SPDK_LIB_LIST += app_rpc bdev bdev_rpc accel event trace log conf thread util rpc jsonrpc json sock notify + +LIBS += -lcunit + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/bdev/bdevio/bdevio.c b/src/spdk/test/bdev/bdevio/bdevio.c new file mode 100644 index 000000000..54d1712e3 --- /dev/null +++ b/src/spdk/test/bdev/bdevio/bdevio.c @@ -0,0 +1,1433 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk/bdev.h" +#include "spdk/accel_engine.h" +#include "spdk/env.h" +#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" + +#define BUFFER_IOVS 1024 +#define BUFFER_SIZE 260 * 1024 +#define BDEV_TASK_ARRAY_SIZE 2048 + +pthread_mutex_t g_test_mutex; +pthread_cond_t g_test_cond; + +static struct spdk_thread *g_thread_init; +static struct spdk_thread *g_thread_ut; +static struct spdk_thread *g_thread_io; +static bool g_wait_for_tests = false; +static int g_num_failures = 0; + +struct io_target { + struct spdk_bdev *bdev; + struct spdk_bdev_desc *bdev_desc; + struct spdk_io_channel *ch; + struct io_target *next; +}; + +struct bdevio_request { + char *buf; + char *fused_buf; + int data_len; + uint64_t offset; + struct iovec iov[BUFFER_IOVS]; + int iovcnt; + struct iovec fused_iov[BUFFER_IOVS]; + int fused_iovcnt; + struct io_target *target; +}; + +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_msg_fn fn, void *arg) +{ + pthread_mutex_lock(&g_test_mutex); + spdk_thread_send_msg(g_thread_io, fn, arg); + pthread_cond_wait(&g_test_cond, &g_test_mutex); + pthread_mutex_unlock(&g_test_mutex); +} + +static void +wake_ut_thread(void) +{ + pthread_mutex_lock(&g_test_mutex); + pthread_cond_signal(&g_test_cond); + pthread_mutex_unlock(&g_test_mutex); +} + +static void +__get_io_channel(void *arg) +{ + struct io_target *target = arg; + + target->ch = spdk_bdev_get_io_channel(target->bdev_desc); + assert(target->ch); + wake_ut_thread(); +} + +static int +bdevio_construct_target(struct spdk_bdev *bdev) +{ + struct io_target *target; + int rc; + uint64_t num_blocks = spdk_bdev_get_num_blocks(bdev); + uint32_t block_size = spdk_bdev_get_block_size(bdev); + + target = malloc(sizeof(struct io_target)); + if (target == NULL) { + return -ENOMEM; + } + + rc = spdk_bdev_open(bdev, true, NULL, NULL, &target->bdev_desc); + if (rc != 0) { + free(target); + SPDK_ERRLOG("Could not open leaf bdev %s, error=%d\n", spdk_bdev_get_name(bdev), rc); + return rc; + } + + printf(" %s: %" PRIu64 " blocks of %" PRIu32 " bytes (%" PRIu64 " MiB)\n", + spdk_bdev_get_name(bdev), + num_blocks, block_size, + (num_blocks * block_size + 1024 * 1024 - 1) / (1024 * 1024)); + + target->bdev = bdev; + target->next = g_io_targets; + execute_spdk_function(__get_io_channel, target); + g_io_targets = target; + + return 0; +} + +static int +bdevio_construct_targets(void) +{ + struct spdk_bdev *bdev; + int rc; + + printf("I/O targets:\n"); + + bdev = spdk_bdev_first_leaf(); + while (bdev != NULL) { + rc = bdevio_construct_target(bdev); + if (rc < 0) { + SPDK_ERRLOG("Could not construct bdev %s, error=%d\n", spdk_bdev_get_name(bdev), rc); + return rc; + } + bdev = spdk_bdev_next_leaf(bdev); + } + + if (g_io_targets == NULL) { + SPDK_ERRLOG("No bdevs to perform tests on\n"); + return -1; + } + + return 0; +} + +static void +__put_io_channel(void *arg) +{ + struct io_target *target = arg; + + spdk_put_io_channel(target->ch); + wake_ut_thread(); +} + +static void +bdevio_cleanup_targets(void) +{ + struct io_target *target; + + target = g_io_targets; + while (target != NULL) { + execute_spdk_function(__put_io_channel, target); + spdk_bdev_close(target->bdev_desc); + g_io_targets = target->next; + free(target); + target = g_io_targets; + } +} + +static bool g_completion_success; + +static void +initialize_buffer(char **buf, int pattern, int size) +{ + *buf = spdk_zmalloc(size, 0x1000, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); + memset(*buf, pattern, size); +} + +static void +quick_test_complete(struct spdk_bdev_io *bdev_io, bool success, void *arg) +{ + g_completion_success = success; + spdk_bdev_free_io(bdev_io); + wake_ut_thread(); +} + +static void +__blockdev_write(void *arg) +{ + struct bdevio_request *req = arg; + struct io_target *target = req->target; + int rc; + + if (req->iovcnt) { + rc = spdk_bdev_writev(target->bdev_desc, target->ch, req->iov, req->iovcnt, req->offset, + req->data_len, quick_test_complete, NULL); + } else { + rc = spdk_bdev_write(target->bdev_desc, target->ch, req->buf, req->offset, + req->data_len, quick_test_complete, NULL); + } + + if (rc) { + g_completion_success = false; + wake_ut_thread(); + } +} + +static void +__blockdev_write_zeroes(void *arg) +{ + struct bdevio_request *req = arg; + struct io_target *target = req->target; + int rc; + + rc = spdk_bdev_write_zeroes(target->bdev_desc, target->ch, req->offset, + req->data_len, quick_test_complete, NULL); + if (rc) { + g_completion_success = false; + wake_ut_thread(); + } +} + +static void +__blockdev_compare_and_write(void *arg) +{ + struct bdevio_request *req = arg; + struct io_target *target = req->target; + int rc; + + rc = spdk_bdev_comparev_and_writev_blocks(target->bdev_desc, target->ch, req->iov, req->iovcnt, + req->fused_iov, req->fused_iovcnt, req->offset, req->data_len, quick_test_complete, NULL); + + if (rc) { + g_completion_success = false; + wake_ut_thread(); + } +} + +static void +sgl_chop_buffer(struct bdevio_request *req, int iov_len) +{ + int data_len = req->data_len; + char *buf = req->buf; + + req->iovcnt = 0; + if (!iov_len) { + return; + } + + for (; data_len > 0 && req->iovcnt < BUFFER_IOVS; req->iovcnt++) { + if (data_len < iov_len) { + iov_len = data_len; + } + + req->iov[req->iovcnt].iov_base = buf; + req->iov[req->iovcnt].iov_len = iov_len; + + buf += iov_len; + data_len -= iov_len; + } + + CU_ASSERT_EQUAL_FATAL(data_len, 0); +} + +static void +sgl_chop_fused_buffer(struct bdevio_request *req, int iov_len) +{ + int data_len = req->data_len; + char *buf = req->fused_buf; + + req->fused_iovcnt = 0; + if (!iov_len) { + return; + } + + for (; data_len > 0 && req->fused_iovcnt < BUFFER_IOVS; req->fused_iovcnt++) { + if (data_len < iov_len) { + iov_len = data_len; + } + + req->fused_iov[req->fused_iovcnt].iov_base = buf; + req->fused_iov[req->fused_iovcnt].iov_len = iov_len; + + buf += iov_len; + data_len -= iov_len; + } + + CU_ASSERT_EQUAL_FATAL(data_len, 0); +} + +static void +blockdev_write(struct io_target *target, char *tx_buf, + uint64_t offset, int data_len, int iov_len) +{ + struct bdevio_request req; + + req.target = target; + req.buf = tx_buf; + req.data_len = data_len; + req.offset = offset; + sgl_chop_buffer(&req, iov_len); + + g_completion_success = false; + + execute_spdk_function(__blockdev_write, &req); +} + +static void +_blockdev_compare_and_write(struct io_target *target, char *cmp_buf, char *write_buf, + uint64_t offset, int data_len, int iov_len) +{ + struct bdevio_request req; + + req.target = target; + req.buf = cmp_buf; + req.fused_buf = write_buf; + req.data_len = data_len; + req.offset = offset; + sgl_chop_buffer(&req, iov_len); + sgl_chop_fused_buffer(&req, iov_len); + + g_completion_success = false; + + execute_spdk_function(__blockdev_compare_and_write, &req); +} + +static void +blockdev_write_zeroes(struct io_target *target, char *tx_buf, + uint64_t offset, int data_len) +{ + struct bdevio_request req; + + req.target = target; + req.buf = tx_buf; + req.data_len = data_len; + req.offset = offset; + + g_completion_success = false; + + execute_spdk_function(__blockdev_write_zeroes, &req); +} + +static void +__blockdev_read(void *arg) +{ + struct bdevio_request *req = arg; + struct io_target *target = req->target; + int rc; + + if (req->iovcnt) { + rc = spdk_bdev_readv(target->bdev_desc, target->ch, req->iov, req->iovcnt, req->offset, + req->data_len, quick_test_complete, NULL); + } else { + rc = spdk_bdev_read(target->bdev_desc, target->ch, req->buf, req->offset, + req->data_len, quick_test_complete, NULL); + } + + if (rc) { + g_completion_success = false; + wake_ut_thread(); + } +} + +static void +blockdev_read(struct io_target *target, char *rx_buf, + uint64_t offset, int data_len, int iov_len) +{ + struct bdevio_request req; + + req.target = target; + req.buf = rx_buf; + req.data_len = data_len; + req.offset = offset; + req.iovcnt = 0; + sgl_chop_buffer(&req, iov_len); + + g_completion_success = false; + + execute_spdk_function(__blockdev_read, &req); +} + +static int +blockdev_write_read_data_match(char *rx_buf, char *tx_buf, int data_length) +{ + int rc; + rc = memcmp(rx_buf, tx_buf, data_length); + + spdk_free(rx_buf); + spdk_free(tx_buf); + + return rc; +} + +static bool +blockdev_io_valid_blocks(struct spdk_bdev *bdev, uint64_t data_length) +{ + if (data_length < spdk_bdev_get_block_size(bdev) || + data_length % spdk_bdev_get_block_size(bdev) || + data_length / spdk_bdev_get_block_size(bdev) > spdk_bdev_get_num_blocks(bdev)) { + return false; + } + + return true; +} + +static void +blockdev_write_read(uint32_t data_length, uint32_t iov_len, int pattern, uint64_t offset, + int expected_rc, bool write_zeroes) +{ + struct io_target *target; + char *tx_buf = NULL; + char *rx_buf = NULL; + int rc; + + target = g_current_io_target; + + if (!blockdev_io_valid_blocks(target->bdev, data_length)) { + return; + } + + if (!write_zeroes) { + initialize_buffer(&tx_buf, pattern, data_length); + initialize_buffer(&rx_buf, 0, data_length); + + blockdev_write(target, tx_buf, offset, data_length, iov_len); + } else { + initialize_buffer(&tx_buf, 0, data_length); + initialize_buffer(&rx_buf, pattern, data_length); + + blockdev_write_zeroes(target, tx_buf, offset, data_length); + } + + + if (expected_rc == 0) { + CU_ASSERT_EQUAL(g_completion_success, true); + } else { + CU_ASSERT_EQUAL(g_completion_success, false); + } + blockdev_read(target, rx_buf, offset, data_length, iov_len); + + if (expected_rc == 0) { + CU_ASSERT_EQUAL(g_completion_success, true); + } else { + CU_ASSERT_EQUAL(g_completion_success, false); + } + + if (g_completion_success) { + rc = blockdev_write_read_data_match(rx_buf, tx_buf, data_length); + /* Assert the write by comparing it with values read + * from each blockdev */ + CU_ASSERT_EQUAL(rc, 0); + } +} + +static void +blockdev_compare_and_write(uint32_t data_length, uint32_t iov_len, uint64_t offset) +{ + struct io_target *target; + char *tx_buf = NULL; + char *write_buf = NULL; + char *rx_buf = NULL; + int rc; + + target = g_current_io_target; + + if (!blockdev_io_valid_blocks(target->bdev, data_length)) { + return; + } + + initialize_buffer(&tx_buf, 0xAA, data_length); + initialize_buffer(&rx_buf, 0, data_length); + initialize_buffer(&write_buf, 0xBB, data_length); + + blockdev_write(target, tx_buf, offset, data_length, iov_len); + CU_ASSERT_EQUAL(g_completion_success, true); + + _blockdev_compare_and_write(target, tx_buf, write_buf, offset, data_length, iov_len); + CU_ASSERT_EQUAL(g_completion_success, true); + + _blockdev_compare_and_write(target, tx_buf, write_buf, offset, data_length, iov_len); + CU_ASSERT_EQUAL(g_completion_success, false); + + blockdev_read(target, rx_buf, offset, data_length, iov_len); + CU_ASSERT_EQUAL(g_completion_success, true); + rc = blockdev_write_read_data_match(rx_buf, write_buf, data_length); + /* Assert the write by comparing it with values read + * from each blockdev */ + CU_ASSERT_EQUAL(rc, 0); +} + +static void +blockdev_write_read_4k(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 4K */ + data_length = 4096; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +blockdev_write_zeroes_read_4k(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 4K */ + data_length = 4096; + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write_zeroes and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 1); +} + +/* + * This i/o will not have to split at the bdev layer. + */ +static void +blockdev_write_zeroes_read_1m(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 1M */ + data_length = 1048576; + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write_zeroes and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 1); +} + +/* + * This i/o will have to split at the bdev layer if + * write-zeroes is not supported by the bdev. + */ +static void +blockdev_write_zeroes_read_3m(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 3M */ + data_length = 3145728; + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write_zeroes and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 1); +} + +/* + * This i/o will have to split at the bdev layer if + * write-zeroes is not supported by the bdev. It also + * tests a write size that is not an even multiple of + * the bdev layer zero buffer size. + */ +static void +blockdev_write_zeroes_read_3m_500k(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 3.5M */ + data_length = 3670016; + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write_zeroes and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 1); +} + +static void +blockdev_writev_readv_4k(void) +{ + uint32_t data_length, iov_len; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 4K */ + data_length = 4096; + iov_len = 4096; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, iov_len, pattern, offset, expected_rc, 0); +} + +static void +blockdev_comparev_and_writev(void) +{ + uint32_t data_length, iov_len; + uint64_t offset; + + data_length = 1; + iov_len = 1; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 0; + + blockdev_compare_and_write(data_length, iov_len, offset); +} + +static void +blockdev_writev_readv_30x4k(void) +{ + uint32_t data_length, iov_len; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 4K */ + data_length = 4096 * 30; + iov_len = 4096; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, iov_len, pattern, offset, expected_rc, 0); +} + +static void +blockdev_write_read_512Bytes(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 512 */ + data_length = 512; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +blockdev_writev_readv_512Bytes(void) +{ + uint32_t data_length, iov_len; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 512 */ + data_length = 512; + iov_len = 512; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, iov_len, pattern, offset, expected_rc, 0); +} + +static void +blockdev_write_read_size_gt_128k(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 132K */ + data_length = 135168; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +blockdev_writev_readv_size_gt_128k(void) +{ + uint32_t data_length, iov_len; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 132K */ + data_length = 135168; + iov_len = 135168; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, iov_len, pattern, offset, expected_rc, 0); +} + +static void +blockdev_writev_readv_size_gt_128k_two_iov(void) +{ + uint32_t data_length, iov_len; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 132K */ + data_length = 135168; + iov_len = 128 * 1024; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + + blockdev_write_read(data_length, iov_len, pattern, offset, expected_rc, 0); +} + +static void +blockdev_write_read_invalid_size(void) +{ + uint32_t data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size is not a multiple of the block size */ + data_length = 0x1015; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 8192; + pattern = 0xA3; + /* Params are invalid, hence the expected return value + * of write and read for all blockdevs is < 0 */ + expected_rc = -1; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +blockdev_write_read_offset_plus_nbytes_equals_bdev_size(void) +{ + struct io_target *target; + struct spdk_bdev *bdev; + char *tx_buf = NULL; + char *rx_buf = NULL; + uint64_t offset; + uint32_t block_size; + int rc; + + target = g_current_io_target; + bdev = target->bdev; + + block_size = spdk_bdev_get_block_size(bdev); + + /* The start offset has been set to a marginal value + * such that offset + nbytes == Total size of + * blockdev. */ + offset = ((spdk_bdev_get_num_blocks(bdev) - 1) * block_size); + + initialize_buffer(&tx_buf, 0xA3, block_size); + initialize_buffer(&rx_buf, 0, block_size); + + blockdev_write(target, tx_buf, offset, block_size, 0); + CU_ASSERT_EQUAL(g_completion_success, true); + + blockdev_read(target, rx_buf, offset, block_size, 0); + CU_ASSERT_EQUAL(g_completion_success, true); + + rc = blockdev_write_read_data_match(rx_buf, tx_buf, block_size); + /* Assert the write by comparing it with values read + * from each blockdev */ + CU_ASSERT_EQUAL(rc, 0); +} + +static void +blockdev_write_read_offset_plus_nbytes_gt_bdev_size(void) +{ + struct io_target *target; + struct spdk_bdev *bdev; + char *tx_buf = NULL; + char *rx_buf = NULL; + int data_length; + uint64_t offset; + int pattern; + + /* Tests the overflow condition of the blockdevs. */ + data_length = 4096; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + pattern = 0xA3; + + target = g_current_io_target; + bdev = target->bdev; + + /* The start offset has been set to a valid value + * but offset + nbytes is greater than the Total size + * of the blockdev. The test should fail. */ + offset = ((spdk_bdev_get_num_blocks(bdev) * spdk_bdev_get_block_size(bdev)) - 1024); + + initialize_buffer(&tx_buf, pattern, data_length); + initialize_buffer(&rx_buf, 0, data_length); + + blockdev_write(target, tx_buf, offset, data_length, 0); + CU_ASSERT_EQUAL(g_completion_success, false); + + blockdev_read(target, rx_buf, offset, data_length, 0); + CU_ASSERT_EQUAL(g_completion_success, false); +} + +static void +blockdev_write_read_max_offset(void) +{ + int data_length; + uint64_t offset; + int pattern; + int expected_rc; + + data_length = 4096; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + /* The start offset has been set to UINT64_MAX such that + * adding nbytes wraps around and points to an invalid address. */ + offset = UINT64_MAX; + pattern = 0xA3; + /* Params are invalid, hence the expected return value + * of write and read for all blockdevs is < 0 */ + expected_rc = -1; + + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +blockdev_overlapped_write_read_8k(void) +{ + int data_length; + uint64_t offset; + int pattern; + int expected_rc; + + /* Data size = 8K */ + data_length = 8192; + CU_ASSERT_TRUE(data_length < BUFFER_SIZE); + offset = 0; + pattern = 0xA3; + /* Params are valid, hence the expected return value + * of write and read for all blockdevs is 0. */ + expected_rc = 0; + /* Assert the write by comparing it with values read + * from the same offset for each blockdev */ + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); + + /* Overwrite the pattern 0xbb of size 8K on an address offset overlapping + * with the address written above and assert the new value in + * the overlapped address range */ + /* Populate 8k with value 0xBB */ + pattern = 0xBB; + /* Offset = 6144; Overlap offset addresses and write value 0xbb */ + offset = 4096; + /* Assert the write by comparing it with values read + * from the overlapped offset for each blockdev */ + blockdev_write_read(data_length, 0, pattern, offset, expected_rc, 0); +} + +static void +__blockdev_reset(void *arg) +{ + struct bdevio_request *req = arg; + struct io_target *target = req->target; + int rc; + + rc = spdk_bdev_reset(target->bdev_desc, target->ch, quick_test_complete, NULL); + if (rc < 0) { + g_completion_success = false; + wake_ut_thread(); + } +} + +static void +blockdev_test_reset(void) +{ + struct bdevio_request req; + struct io_target *target; + + target = g_current_io_target; + req.target = target; + + g_completion_success = false; + + execute_spdk_function(__blockdev_reset, &req); + + /* Workaround: NVMe-oF target doesn't support reset yet - so for now + * don't fail the test if it's an NVMe bdev. + */ + if (!spdk_bdev_io_type_supported(target->bdev, SPDK_BDEV_IO_TYPE_NVME_IO)) { + CU_ASSERT_EQUAL(g_completion_success, true); + } +} + +struct bdevio_passthrough_request { + struct spdk_nvme_cmd cmd; + void *buf; + uint32_t len; + struct io_target *target; + int sct; + int sc; + uint32_t cdw0; +}; + +static void +nvme_pt_test_complete(struct spdk_bdev_io *bdev_io, bool success, void *arg) +{ + struct bdevio_passthrough_request *pt_req = arg; + + spdk_bdev_io_get_nvme_status(bdev_io, &pt_req->cdw0, &pt_req->sct, &pt_req->sc); + spdk_bdev_free_io(bdev_io); + wake_ut_thread(); +} + +static void +__blockdev_nvme_passthru(void *arg) +{ + struct bdevio_passthrough_request *pt_req = arg; + struct io_target *target = pt_req->target; + int rc; + + rc = spdk_bdev_nvme_io_passthru(target->bdev_desc, target->ch, + &pt_req->cmd, pt_req->buf, pt_req->len, + nvme_pt_test_complete, pt_req); + if (rc) { + wake_ut_thread(); + } +} + +static void +blockdev_test_nvme_passthru_rw(void) +{ + struct bdevio_passthrough_request pt_req; + void *write_buf, *read_buf; + struct io_target *target; + + target = g_current_io_target; + + if (!spdk_bdev_io_type_supported(target->bdev, SPDK_BDEV_IO_TYPE_NVME_IO)) { + return; + } + + memset(&pt_req, 0, sizeof(pt_req)); + pt_req.target = target; + pt_req.cmd.opc = SPDK_NVME_OPC_WRITE; + pt_req.cmd.nsid = 1; + *(uint64_t *)&pt_req.cmd.cdw10 = 4; + pt_req.cmd.cdw12 = 0; + + pt_req.len = spdk_bdev_get_block_size(target->bdev); + write_buf = spdk_malloc(pt_req.len, 0, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); + memset(write_buf, 0xA5, pt_req.len); + pt_req.buf = write_buf; + + pt_req.sct = SPDK_NVME_SCT_VENDOR_SPECIFIC; + pt_req.sc = SPDK_NVME_SC_INVALID_FIELD; + execute_spdk_function(__blockdev_nvme_passthru, &pt_req); + CU_ASSERT(pt_req.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(pt_req.sc == SPDK_NVME_SC_SUCCESS); + + pt_req.cmd.opc = SPDK_NVME_OPC_READ; + read_buf = spdk_zmalloc(pt_req.len, 0, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); + pt_req.buf = read_buf; + + pt_req.sct = SPDK_NVME_SCT_VENDOR_SPECIFIC; + pt_req.sc = SPDK_NVME_SC_INVALID_FIELD; + execute_spdk_function(__blockdev_nvme_passthru, &pt_req); + CU_ASSERT(pt_req.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(pt_req.sc == SPDK_NVME_SC_SUCCESS); + + CU_ASSERT(!memcmp(read_buf, write_buf, pt_req.len)); + spdk_free(read_buf); + spdk_free(write_buf); +} + +static void +blockdev_test_nvme_passthru_vendor_specific(void) +{ + struct bdevio_passthrough_request pt_req; + struct io_target *target; + + target = g_current_io_target; + + if (!spdk_bdev_io_type_supported(target->bdev, SPDK_BDEV_IO_TYPE_NVME_IO)) { + return; + } + + memset(&pt_req, 0, sizeof(pt_req)); + pt_req.target = target; + pt_req.cmd.opc = 0x7F; /* choose known invalid opcode */ + pt_req.cmd.nsid = 1; + + pt_req.sct = SPDK_NVME_SCT_VENDOR_SPECIFIC; + pt_req.sc = SPDK_NVME_SC_SUCCESS; + pt_req.cdw0 = 0xbeef; + execute_spdk_function(__blockdev_nvme_passthru, &pt_req); + CU_ASSERT(pt_req.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(pt_req.sc == SPDK_NVME_SC_INVALID_OPCODE); + CU_ASSERT(pt_req.cdw0 == 0x0); +} + +static void +__blockdev_nvme_admin_passthru(void *arg) +{ + struct bdevio_passthrough_request *pt_req = arg; + struct io_target *target = pt_req->target; + int rc; + + rc = spdk_bdev_nvme_admin_passthru(target->bdev_desc, target->ch, + &pt_req->cmd, pt_req->buf, pt_req->len, + nvme_pt_test_complete, pt_req); + if (rc) { + wake_ut_thread(); + } +} + +static void +blockdev_test_nvme_admin_passthru(void) +{ + struct io_target *target; + struct bdevio_passthrough_request pt_req; + + target = g_current_io_target; + + if (!spdk_bdev_io_type_supported(target->bdev, SPDK_BDEV_IO_TYPE_NVME_ADMIN)) { + return; + } + + memset(&pt_req, 0, sizeof(pt_req)); + pt_req.target = target; + pt_req.cmd.opc = SPDK_NVME_OPC_IDENTIFY; + pt_req.cmd.nsid = 0; + *(uint64_t *)&pt_req.cmd.cdw10 = SPDK_NVME_IDENTIFY_CTRLR; + + pt_req.len = sizeof(struct spdk_nvme_ctrlr_data); + pt_req.buf = spdk_malloc(pt_req.len, 0, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); + + pt_req.sct = SPDK_NVME_SCT_GENERIC; + pt_req.sc = SPDK_NVME_SC_SUCCESS; + execute_spdk_function(__blockdev_nvme_admin_passthru, &pt_req); + CU_ASSERT(pt_req.sct == SPDK_NVME_SCT_GENERIC); + CU_ASSERT(pt_req.sc == SPDK_NVME_SC_SUCCESS); +} + +static void +__stop_init_thread(void *arg) +{ + unsigned num_failures = g_num_failures; + struct spdk_jsonrpc_request *request = arg; + + g_num_failures = 0; + + 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, struct spdk_jsonrpc_request *request) +{ + g_num_failures = num_failures; + + spdk_thread_send_msg(g_thread_init, __stop_init_thread, request); +} + +static int +suite_init(void) +{ + if (g_current_io_target == NULL) { + g_current_io_target = g_io_targets; + } + return 0; +} + +static int +suite_fini(void) +{ + g_current_io_target = g_current_io_target->next; + return 0; +} + +#define SUITE_NAME_MAX 64 + +static int +__setup_ut_on_single_target(struct io_target *target) +{ + unsigned rc = 0; + CU_pSuite suite = NULL; + char name[SUITE_NAME_MAX]; + + snprintf(name, sizeof(name), "bdevio tests on: %s", spdk_bdev_get_name(target->bdev)); + suite = CU_add_suite(name, suite_init, suite_fini); + if (suite == NULL) { + CU_cleanup_registry(); + rc = CU_get_error(); + return -rc; + } + + if ( + CU_add_test(suite, "blockdev write read 4k", blockdev_write_read_4k) == NULL + || CU_add_test(suite, "blockdev write zeroes read 4k", blockdev_write_zeroes_read_4k) == NULL + || CU_add_test(suite, "blockdev write zeroes read 1m", blockdev_write_zeroes_read_1m) == NULL + || CU_add_test(suite, "blockdev write zeroes read 3m", blockdev_write_zeroes_read_3m) == NULL + || CU_add_test(suite, "blockdev write zeroes read 3.5m", blockdev_write_zeroes_read_3m_500k) == NULL + || CU_add_test(suite, "blockdev reset", + blockdev_test_reset) == NULL + || CU_add_test(suite, "blockdev write read 512 bytes", + blockdev_write_read_512Bytes) == NULL + || CU_add_test(suite, "blockdev write read size > 128k", + blockdev_write_read_size_gt_128k) == NULL + || CU_add_test(suite, "blockdev write read invalid size", + blockdev_write_read_invalid_size) == NULL + || CU_add_test(suite, "blockdev write read offset + nbytes == size of blockdev", + blockdev_write_read_offset_plus_nbytes_equals_bdev_size) == NULL + || CU_add_test(suite, "blockdev write read offset + nbytes > size of blockdev", + blockdev_write_read_offset_plus_nbytes_gt_bdev_size) == NULL + || CU_add_test(suite, "blockdev write read max offset", + blockdev_write_read_max_offset) == NULL + || CU_add_test(suite, "blockdev write read 8k on overlapped address offset", + blockdev_overlapped_write_read_8k) == NULL + || CU_add_test(suite, "blockdev writev readv 4k", blockdev_writev_readv_4k) == NULL + || CU_add_test(suite, "blockdev writev readv 30 x 4k", + blockdev_writev_readv_30x4k) == NULL + || CU_add_test(suite, "blockdev writev readv 512 bytes", + blockdev_writev_readv_512Bytes) == NULL + || CU_add_test(suite, "blockdev writev readv size > 128k", + blockdev_writev_readv_size_gt_128k) == NULL + || CU_add_test(suite, "blockdev writev readv size > 128k in two iovs", + blockdev_writev_readv_size_gt_128k_two_iov) == NULL + || CU_add_test(suite, "blockdev comparev and writev", blockdev_comparev_and_writev) == NULL + || CU_add_test(suite, "blockdev nvme passthru rw", + blockdev_test_nvme_passthru_rw) == NULL + || CU_add_test(suite, "blockdev nvme passthru vendor specific", + blockdev_test_nvme_passthru_vendor_specific) == NULL + || CU_add_test(suite, "blockdev nvme admin passthru", + blockdev_test_nvme_admin_passthru) == NULL + ) { + CU_cleanup_registry(); + rc = CU_get_error(); + return -rc; + } + return 0; +} + +static void +__run_ut_thread(void *arg) +{ + struct spdk_jsonrpc_request *request = arg; + int rc = 0; + struct io_target *target; + unsigned num_failures; + + if (CU_initialize_registry() != CUE_SUCCESS) { + /* CUnit error, probably won't recover */ + rc = CU_get_error(); + stop_init_thread(-rc, request); + } + + target = g_io_targets; + while (target != NULL) { + rc = __setup_ut_on_single_target(target); + if (rc < 0) { + /* CUnit error, probably won't recover */ + stop_init_thread(-rc, request); + } + target = target->next; + } + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + stop_init_thread(num_failures, request); +} + +static void +__construct_targets(void *arg) +{ + if (bdevio_construct_targets() < 0) { + spdk_app_stop(-1); + return; + } + + spdk_thread_send_msg(g_thread_ut, __run_ut_thread, NULL); +} + +static void +test_main(void *arg1) +{ + struct spdk_cpuset tmpmask = {}, *appmask; + uint32_t cpu, init_cpu; + + pthread_mutex_init(&g_test_mutex, NULL); + pthread_cond_init(&g_test_cond, NULL); + + appmask = spdk_app_get_core_mask(); + + if (spdk_cpuset_count(appmask) < 3) { + spdk_app_stop(-1); + return; + } + + init_cpu = spdk_env_get_current_core(); + g_thread_init = spdk_get_thread(); + + for (cpu = 0; cpu < SPDK_ENV_LCORE_ID_ANY; cpu++) { + if (cpu != init_cpu && spdk_cpuset_get_cpu(appmask, cpu)) { + spdk_cpuset_zero(&tmpmask); + spdk_cpuset_set_cpu(&tmpmask, cpu, true); + g_thread_ut = spdk_thread_create("ut_thread", &tmpmask); + break; + } + } + + if (cpu == SPDK_ENV_LCORE_ID_ANY) { + spdk_app_stop(-1); + return; + } + + for (cpu++; cpu < SPDK_ENV_LCORE_ID_ANY; cpu++) { + if (cpu != init_cpu && spdk_cpuset_get_cpu(appmask, cpu)) { + spdk_cpuset_zero(&tmpmask); + spdk_cpuset_set_cpu(&tmpmask, cpu, true); + g_thread_io = spdk_thread_create("io_thread", &tmpmask); + break; + } + } + + if (cpu == SPDK_ENV_LCORE_ID_ANY) { + spdk_app_stop(-1); + return; + } + + if (g_wait_for_tests) { + /* Do not perform any tests until RPC is received */ + return; + } + + spdk_thread_send_msg(g_thread_init, __construct_targets, NULL); +} + +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; + + if (num_failures == 0) { + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_uint32(w, num_failures); + spdk_jsonrpc_end_result(request, w); + } else { + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "%d test cases failed", num_failures); + } +} + +static void +rpc_perform_tests(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params) +{ + struct rpc_perform_tests req = {NULL}; + 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); + + spdk_thread_send_msg(g_thread_ut, __run_ut_thread, request); + + return; + +invalid: + free_rpc_perform_tests(&req); +} +SPDK_RPC_REGISTER("perform_tests", rpc_perform_tests, SPDK_RPC_RUNTIME) + +int +main(int argc, char **argv) +{ + int rc; + struct spdk_app_opts opts = {}; + + spdk_app_opts_init(&opts); + opts.name = "bdevio"; + opts.reactor_mask = "0x7"; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "w", NULL, + bdevio_parse_arg, bdevio_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + return rc; + } + + rc = spdk_app_start(&opts, test_main, NULL); + spdk_app_fini(); + + return rc; +} diff --git a/src/spdk/test/bdev/bdevio/tests.py b/src/spdk/test/bdev/bdevio/tests.py new file mode 100755 index 000000000..8b46061d0 --- /dev/null +++ b/src/spdk/test/bdev/bdevio/tests.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +import logging +import argparse +import sys +import shlex + +try: + from rpc.client import print_dict, JSONRPCException + import rpc +except ImportError: + print("SPDK RPC library missing. Please add spdk/scripts/ directory to PYTHONPATH:") + print("'export PYTHONPATH=$PYTHONPATH:./spdk/scripts/'") + exit(1) + +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) diff --git a/src/spdk/test/bdev/bdevperf/.gitignore b/src/spdk/test/bdev/bdevperf/.gitignore new file mode 100644 index 000000000..e14ddd841 --- /dev/null +++ b/src/spdk/test/bdev/bdevperf/.gitignore @@ -0,0 +1 @@ +bdevperf diff --git a/src/spdk/test/bdev/bdevperf/Makefile b/src/spdk/test/bdev/bdevperf/Makefile new file mode 100644 index 000000000..689d7fe10 --- /dev/null +++ b/src/spdk/test/bdev/bdevperf/Makefile @@ -0,0 +1,55 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = bdevperf + +C_SRCS := bdevperf.c + +SPDK_LIB_LIST = $(ALL_MODULES_LIST) +SPDK_LIB_LIST += $(EVENT_BDEV_SUBSYSTEM) +SPDK_LIB_LIST += bdev accel event trace log conf thread util sock notify +SPDK_LIB_LIST += rpc jsonrpc json app_rpc log_rpc bdev_rpc + +ifeq ($(OS),Linux) +SPDK_LIB_LIST += event_nbd nbd +endif + +ifeq ($(SPDK_ROOT_DIR)/lib/env_dpdk,$(CONFIG_ENV)) +SPDK_LIB_LIST += env_dpdk_rpc +endif + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/bdev/bdevperf/bdevperf.c b/src/spdk/test/bdev/bdevperf/bdevperf.c new file mode 100644 index 000000000..adcdf31cb --- /dev/null +++ b/src/spdk/test/bdev/bdevperf/bdevperf.c @@ -0,0 +1,2137 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk/bdev.h" +#include "spdk/accel_engine.h" +#include "spdk/endian.h" +#include "spdk/env.h" +#include "spdk/event.h" +#include "spdk/log.h" +#include "spdk/util.h" +#include "spdk/thread.h" +#include "spdk/string.h" +#include "spdk/rpc.h" +#include "spdk/bit_array.h" +#include "spdk/conf.h" + +#define BDEVPERF_CONFIG_MAX_FILENAME 1024 +#define BDEVPERF_CONFIG_UNDEFINED -1 +#define BDEVPERF_CONFIG_ERROR -2 + +struct bdevperf_task { + struct iovec iov; + struct bdevperf_job *job; + struct spdk_bdev_io *bdev_io; + void *buf; + void *md_buf; + uint64_t offset_blocks; + struct bdevperf_task *task_to_abort; + enum spdk_bdev_io_type io_type; + TAILQ_ENTRY(bdevperf_task) link; + struct spdk_bdev_io_wait_entry bdev_io_wait; +}; + +static const char *g_workload_type = NULL; +static int g_io_size = 0; +/* initialize to invalid value so we can detect if user overrides it. */ +static int g_rw_percentage = -1; +static bool g_verify = false; +static bool g_reset = false; +static bool g_continue_on_failure = false; +static bool g_abort = false; +static int g_queue_depth = 0; +static uint64_t g_time_in_usec; +static int g_show_performance_real_time = 0; +static uint64_t g_show_performance_period_in_usec = 1000000; +static uint64_t g_show_performance_period_num = 0; +static uint64_t g_show_performance_ema_period = 0; +static int g_run_rc = 0; +static bool g_shutdown = false; +static uint64_t g_shutdown_tsc; +static bool g_zcopy = true; +static struct spdk_thread *g_master_thread; +static int g_time_in_sec = 0; +static bool g_mix_specified = false; +static const char *g_job_bdev_name; +static bool g_wait_for_tests = false; +static struct spdk_jsonrpc_request *g_request = NULL; +static bool g_multithread_mode = false; +static int g_timeout_in_sec; +static struct spdk_conf *g_bdevperf_conf = NULL; +static const char *g_bdevperf_conf_file = NULL; + +static struct spdk_cpuset g_all_cpuset; +static struct spdk_poller *g_perf_timer = NULL; + +static void bdevperf_submit_single(struct bdevperf_job *job, struct bdevperf_task *task); +static void rpc_perform_tests_cb(void); + +struct bdevperf_job { + char *name; + struct spdk_bdev *bdev; + struct spdk_bdev_desc *bdev_desc; + struct spdk_io_channel *ch; + TAILQ_ENTRY(bdevperf_job) link; + struct spdk_thread *thread; + + const char *workload_type; + int io_size; + int rw_percentage; + bool is_random; + bool verify; + bool reset; + bool continue_on_failure; + bool unmap; + bool write_zeroes; + bool flush; + bool abort; + int queue_depth; + + uint64_t io_completed; + uint64_t io_failed; + uint64_t io_timeout; + uint64_t prev_io_completed; + double ema_io_per_second; + int current_queue_depth; + uint64_t size_in_ios; + uint64_t ios_base; + uint64_t offset_in_ios; + uint64_t io_size_blocks; + uint64_t buf_size; + uint32_t dif_check_flags; + bool is_draining; + struct spdk_poller *run_timer; + struct spdk_poller *reset_timer; + struct spdk_bit_array *outstanding; + TAILQ_HEAD(, bdevperf_task) task_list; +}; + +struct spdk_bdevperf { + TAILQ_HEAD(, bdevperf_job) jobs; + uint32_t running_jobs; +}; + +static struct spdk_bdevperf g_bdevperf = { + .jobs = TAILQ_HEAD_INITIALIZER(g_bdevperf.jobs), + .running_jobs = 0, +}; + +enum job_config_rw { + JOB_CONFIG_RW_READ = 0, + JOB_CONFIG_RW_WRITE, + JOB_CONFIG_RW_RANDREAD, + JOB_CONFIG_RW_RANDWRITE, + JOB_CONFIG_RW_RW, + JOB_CONFIG_RW_RANDRW, + JOB_CONFIG_RW_VERIFY, + JOB_CONFIG_RW_RESET, + JOB_CONFIG_RW_UNMAP, + JOB_CONFIG_RW_FLUSH, + JOB_CONFIG_RW_WRITE_ZEROES, +}; + +/* Storing values from a section of job config file */ +struct job_config { + const char *name; + const char *filename; + struct spdk_cpuset cpumask; + int bs; + int iodepth; + int rwmixread; + int offset; + int length; + enum job_config_rw rw; + TAILQ_ENTRY(job_config) link; +}; + +TAILQ_HEAD(, job_config) job_config_list + = TAILQ_HEAD_INITIALIZER(job_config_list); + +static bool g_performance_dump_active = false; + +struct bdevperf_aggregate_stats { + struct bdevperf_job *current_job; + uint64_t io_time_in_usec; + uint64_t ema_period; + double total_io_per_second; + double total_mb_per_second; + double total_failed_per_second; + double total_timeout_per_second; +}; + +static struct bdevperf_aggregate_stats g_stats = {}; + +/* + * Cumulative Moving Average (CMA): average of all data up to current + * Exponential Moving Average (EMA): weighted mean of the previous n data and more weight is given to recent + * Simple Moving Average (SMA): unweighted mean of the previous n data + * + * Bdevperf supports CMA and EMA. + */ +static double +get_cma_io_per_second(struct bdevperf_job *job, uint64_t io_time_in_usec) +{ + return (double)job->io_completed * 1000000 / io_time_in_usec; +} + +static double +get_ema_io_per_second(struct bdevperf_job *job, uint64_t ema_period) +{ + double io_completed, io_per_second; + + io_completed = job->io_completed; + io_per_second = (double)(io_completed - job->prev_io_completed) * 1000000 + / g_show_performance_period_in_usec; + job->prev_io_completed = io_completed; + + job->ema_io_per_second += (io_per_second - job->ema_io_per_second) * 2 + / (ema_period + 1); + return job->ema_io_per_second; +} + +static void +performance_dump_job(struct bdevperf_aggregate_stats *stats, struct bdevperf_job *job) +{ + double io_per_second, mb_per_second, failed_per_second, timeout_per_second; + + printf("\r Thread name: %s\n", spdk_thread_get_name(job->thread)); + printf("\r Core Mask: 0x%s\n", spdk_cpuset_fmt(spdk_thread_get_cpumask(job->thread))); + + if (stats->ema_period == 0) { + io_per_second = get_cma_io_per_second(job, stats->io_time_in_usec); + } else { + io_per_second = get_ema_io_per_second(job, stats->ema_period); + } + mb_per_second = io_per_second * job->io_size / (1024 * 1024); + failed_per_second = (double)job->io_failed * 1000000 / stats->io_time_in_usec; + timeout_per_second = (double)job->io_timeout * 1000000 / stats->io_time_in_usec; + + printf("\r %-20s: %10.2f IOPS %10.2f MiB/s\n", + job->name, io_per_second, mb_per_second); + if (failed_per_second != 0) { + printf("\r %-20s: %10.2f Fail/s %8.2f TO/s\n", + "", failed_per_second, timeout_per_second); + } + stats->total_io_per_second += io_per_second; + stats->total_mb_per_second += mb_per_second; + stats->total_failed_per_second += failed_per_second; + stats->total_timeout_per_second += timeout_per_second; +} + +static void +generate_data(void *buf, int buf_len, int block_size, void *md_buf, int md_size, + int num_blocks, int seed) +{ + int offset_blocks = 0, md_offset, data_block_size; + + if (buf_len < num_blocks * block_size) { + return; + } + + if (md_buf == NULL) { + data_block_size = block_size - md_size; + md_buf = (char *)buf + data_block_size; + md_offset = block_size; + } else { + data_block_size = block_size; + md_offset = md_size; + } + + while (offset_blocks < num_blocks) { + memset(buf, seed, data_block_size); + memset(md_buf, seed, md_size); + buf += block_size; + md_buf += md_offset; + offset_blocks++; + } +} + +static bool +copy_data(void *wr_buf, int wr_buf_len, void *rd_buf, int rd_buf_len, int block_size, + void *wr_md_buf, void *rd_md_buf, int md_size, int num_blocks) +{ + if (wr_buf_len < num_blocks * block_size || rd_buf_len < num_blocks * block_size) { + return false; + } + + assert((wr_md_buf != NULL) == (rd_md_buf != NULL)); + + memcpy(wr_buf, rd_buf, block_size * num_blocks); + + if (wr_md_buf != NULL) { + memcpy(wr_md_buf, rd_md_buf, md_size * num_blocks); + } + + return true; +} + +static bool +verify_data(void *wr_buf, int wr_buf_len, void *rd_buf, int rd_buf_len, int block_size, + void *wr_md_buf, void *rd_md_buf, int md_size, int num_blocks, bool md_check) +{ + int offset_blocks = 0, md_offset, data_block_size; + + if (wr_buf_len < num_blocks * block_size || rd_buf_len < num_blocks * block_size) { + return false; + } + + assert((wr_md_buf != NULL) == (rd_md_buf != NULL)); + + if (wr_md_buf == NULL) { + data_block_size = block_size - md_size; + wr_md_buf = (char *)wr_buf + data_block_size; + rd_md_buf = (char *)rd_buf + data_block_size; + md_offset = block_size; + } else { + data_block_size = block_size; + md_offset = md_size; + } + + while (offset_blocks < num_blocks) { + if (memcmp(wr_buf, rd_buf, data_block_size) != 0) { + return false; + } + + wr_buf += block_size; + rd_buf += block_size; + + if (md_check) { + if (memcmp(wr_md_buf, rd_md_buf, md_size) != 0) { + return false; + } + + wr_md_buf += md_offset; + rd_md_buf += md_offset; + } + + offset_blocks++; + } + + return true; +} + +static void +free_job_config(void) +{ + struct job_config *config, *tmp; + + spdk_conf_free(g_bdevperf_conf); + g_bdevperf_conf = NULL; + + TAILQ_FOREACH_SAFE(config, &job_config_list, link, tmp) { + TAILQ_REMOVE(&job_config_list, config, link); + free(config); + } +} + +static void +bdevperf_test_done(void *ctx) +{ + struct bdevperf_job *job, *jtmp; + struct bdevperf_task *task, *ttmp; + + if (g_time_in_usec && !g_run_rc) { + g_stats.io_time_in_usec = g_time_in_usec; + + if (g_performance_dump_active) { + spdk_thread_send_msg(spdk_get_thread(), bdevperf_test_done, NULL); + return; + } + } else { + printf("Job run time less than one microsecond, no performance data will be shown\n"); + } + + if (g_show_performance_real_time) { + spdk_poller_unregister(&g_perf_timer); + } + + if (g_shutdown) { + g_time_in_usec = g_shutdown_tsc * 1000000 / spdk_get_ticks_hz(); + printf("Received shutdown signal, test time was about %.6f seconds\n", + (double)g_time_in_usec / 1000000); + } + + TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, jtmp) { + TAILQ_REMOVE(&g_bdevperf.jobs, job, link); + + performance_dump_job(&g_stats, job); + + TAILQ_FOREACH_SAFE(task, &job->task_list, link, ttmp) { + TAILQ_REMOVE(&job->task_list, task, link); + spdk_free(task->buf); + spdk_free(task->md_buf); + free(task); + } + + if (job->verify) { + spdk_bit_array_free(&job->outstanding); + } + + free(job->name); + free(job); + } + + printf("\r =====================================================\n"); + printf("\r %-20s: %10.2f IOPS %10.2f MiB/s\n", + "Total", g_stats.total_io_per_second, g_stats.total_mb_per_second); + if (g_stats.total_failed_per_second != 0 || g_stats.total_timeout_per_second != 0) { + printf("\r %-20s: %10.2f Fail/s %8.2f TO/s\n", + "", g_stats.total_failed_per_second, g_stats.total_timeout_per_second); + } + fflush(stdout); + + if (g_request && !g_shutdown) { + rpc_perform_tests_cb(); + } else { + spdk_app_stop(g_run_rc); + } +} + +static void +bdevperf_job_end(void *ctx) +{ + assert(g_master_thread == spdk_get_thread()); + + if (--g_bdevperf.running_jobs == 0) { + bdevperf_test_done(NULL); + } +} + +static void +bdevperf_queue_io_wait_with_cb(struct bdevperf_task *task, spdk_bdev_io_wait_cb cb_fn) +{ + struct bdevperf_job *job = task->job; + + task->bdev_io_wait.bdev = job->bdev; + task->bdev_io_wait.cb_fn = cb_fn; + task->bdev_io_wait.cb_arg = task; + spdk_bdev_queue_io_wait(job->bdev, job->ch, &task->bdev_io_wait); +} + +static int +bdevperf_job_drain(void *ctx) +{ + struct bdevperf_job *job = ctx; + + spdk_poller_unregister(&job->run_timer); + if (job->reset) { + spdk_poller_unregister(&job->reset_timer); + } + + job->is_draining = true; + + return -1; +} + +static void +bdevperf_abort_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + struct bdevperf_task *task = cb_arg; + struct bdevperf_job *job = task->job; + + job->current_queue_depth--; + + if (success) { + job->io_completed++; + } else { + job->io_failed++; + if (!job->continue_on_failure) { + bdevperf_job_drain(job); + g_run_rc = -1; + } + } + + spdk_bdev_free_io(bdev_io); + + /* Return task to free list because abort is submitted on demand. */ + TAILQ_INSERT_TAIL(&job->task_list, task, link); + + if (job->is_draining) { + if (job->current_queue_depth == 0) { + spdk_put_io_channel(job->ch); + spdk_bdev_close(job->bdev_desc); + spdk_thread_send_msg(g_master_thread, bdevperf_job_end, NULL); + } + } +} + +static void +bdevperf_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + struct bdevperf_job *job; + struct bdevperf_task *task = cb_arg; + struct iovec *iovs; + int iovcnt; + bool md_check; + uint64_t offset_in_ios; + + job = task->job; + md_check = spdk_bdev_get_dif_type(job->bdev) == SPDK_DIF_DISABLE; + + if (!success) { + if (!job->reset && !job->continue_on_failure) { + bdevperf_job_drain(job); + g_run_rc = -1; + printf("task offset: %lu on job bdev=%s fails\n", + task->offset_blocks, job->name); + } + } else if (job->verify || job->reset) { + spdk_bdev_io_get_iovec(bdev_io, &iovs, &iovcnt); + assert(iovcnt == 1); + assert(iovs != NULL); + if (!verify_data(task->buf, job->buf_size, iovs[0].iov_base, iovs[0].iov_len, + spdk_bdev_get_block_size(job->bdev), + task->md_buf, spdk_bdev_io_get_md_buf(bdev_io), + spdk_bdev_get_md_size(job->bdev), + job->io_size_blocks, md_check)) { + printf("Buffer mismatch! Target: %s Disk Offset: %lu\n", job->name, task->offset_blocks); + printf(" First dword expected 0x%x got 0x%x\n", *(int *)task->buf, *(int *)iovs[0].iov_base); + bdevperf_job_drain(job); + g_run_rc = -1; + } + } + + job->current_queue_depth--; + + if (success) { + job->io_completed++; + } else { + job->io_failed++; + } + + if (job->verify) { + assert(task->offset_blocks / job->io_size_blocks >= job->ios_base); + offset_in_ios = task->offset_blocks / job->io_size_blocks - job->ios_base; + + assert(spdk_bit_array_get(job->outstanding, offset_in_ios) == true); + spdk_bit_array_clear(job->outstanding, offset_in_ios); + } + + spdk_bdev_free_io(bdev_io); + + /* + * is_draining indicates when time has expired for the test run + * and we are just waiting for the previously submitted I/O + * to complete. In this case, do not submit a new I/O to replace + * the one just completed. + */ + if (!job->is_draining) { + bdevperf_submit_single(job, task); + } else { + TAILQ_INSERT_TAIL(&job->task_list, task, link); + if (job->current_queue_depth == 0) { + spdk_put_io_channel(job->ch); + spdk_bdev_close(job->bdev_desc); + spdk_thread_send_msg(g_master_thread, bdevperf_job_end, NULL); + } + } +} + +static void +bdevperf_verify_submit_read(void *cb_arg) +{ + struct bdevperf_job *job; + struct bdevperf_task *task = cb_arg; + int rc; + + job = task->job; + + /* Read the data back in */ + if (spdk_bdev_is_md_separate(job->bdev)) { + rc = spdk_bdev_read_blocks_with_md(job->bdev_desc, job->ch, NULL, NULL, + task->offset_blocks, job->io_size_blocks, + bdevperf_complete, task); + } else { + rc = spdk_bdev_read_blocks(job->bdev_desc, job->ch, NULL, + task->offset_blocks, job->io_size_blocks, + bdevperf_complete, task); + } + + if (rc == -ENOMEM) { + bdevperf_queue_io_wait_with_cb(task, bdevperf_verify_submit_read); + } else if (rc != 0) { + printf("Failed to submit read: %d\n", rc); + bdevperf_job_drain(job); + g_run_rc = rc; + } +} + +static void +bdevperf_verify_write_complete(struct spdk_bdev_io *bdev_io, bool success, + void *cb_arg) +{ + if (success) { + spdk_bdev_free_io(bdev_io); + bdevperf_verify_submit_read(cb_arg); + } else { + bdevperf_complete(bdev_io, success, cb_arg); + } +} + +static void +bdevperf_zcopy_populate_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + if (!success) { + bdevperf_complete(bdev_io, success, cb_arg); + return; + } + + spdk_bdev_zcopy_end(bdev_io, false, bdevperf_complete, cb_arg); +} + +static int +bdevperf_generate_dif(struct bdevperf_task *task) +{ + struct bdevperf_job *job = task->job; + struct spdk_bdev *bdev = job->bdev; + struct spdk_dif_ctx dif_ctx; + int rc; + + rc = spdk_dif_ctx_init(&dif_ctx, + spdk_bdev_get_block_size(bdev), + spdk_bdev_get_md_size(bdev), + spdk_bdev_is_md_interleaved(bdev), + spdk_bdev_is_dif_head_of_md(bdev), + spdk_bdev_get_dif_type(bdev), + job->dif_check_flags, + task->offset_blocks, 0, 0, 0, 0); + if (rc != 0) { + fprintf(stderr, "Initialization of DIF context failed\n"); + return rc; + } + + if (spdk_bdev_is_md_interleaved(bdev)) { + rc = spdk_dif_generate(&task->iov, 1, job->io_size_blocks, &dif_ctx); + } else { + struct iovec md_iov = { + .iov_base = task->md_buf, + .iov_len = spdk_bdev_get_md_size(bdev) * job->io_size_blocks, + }; + + rc = spdk_dix_generate(&task->iov, 1, &md_iov, job->io_size_blocks, &dif_ctx); + } + + if (rc != 0) { + fprintf(stderr, "Generation of DIF/DIX failed\n"); + } + + return rc; +} + +static void +bdevperf_submit_task(void *arg) +{ + struct bdevperf_task *task = arg; + struct bdevperf_job *job = task->job; + struct spdk_bdev_desc *desc; + struct spdk_io_channel *ch; + spdk_bdev_io_completion_cb cb_fn; + uint64_t offset_in_ios; + int rc = 0; + + desc = job->bdev_desc; + ch = job->ch; + + switch (task->io_type) { + case SPDK_BDEV_IO_TYPE_WRITE: + if (spdk_bdev_get_md_size(job->bdev) != 0 && job->dif_check_flags != 0) { + rc = bdevperf_generate_dif(task); + } + if (rc == 0) { + cb_fn = (job->verify || job->reset) ? bdevperf_verify_write_complete : bdevperf_complete; + + if (g_zcopy) { + spdk_bdev_zcopy_end(task->bdev_io, true, cb_fn, task); + return; + } else { + if (spdk_bdev_is_md_separate(job->bdev)) { + rc = spdk_bdev_writev_blocks_with_md(desc, ch, &task->iov, 1, + task->md_buf, + task->offset_blocks, + job->io_size_blocks, + cb_fn, task); + } else { + rc = spdk_bdev_writev_blocks(desc, ch, &task->iov, 1, + task->offset_blocks, + job->io_size_blocks, + cb_fn, task); + } + } + } + break; + case SPDK_BDEV_IO_TYPE_FLUSH: + rc = spdk_bdev_flush_blocks(desc, ch, task->offset_blocks, + job->io_size_blocks, bdevperf_complete, task); + break; + case SPDK_BDEV_IO_TYPE_UNMAP: + rc = spdk_bdev_unmap_blocks(desc, ch, task->offset_blocks, + job->io_size_blocks, bdevperf_complete, task); + break; + case SPDK_BDEV_IO_TYPE_WRITE_ZEROES: + rc = spdk_bdev_write_zeroes_blocks(desc, ch, task->offset_blocks, + job->io_size_blocks, bdevperf_complete, task); + break; + case SPDK_BDEV_IO_TYPE_READ: + if (g_zcopy) { + rc = spdk_bdev_zcopy_start(desc, ch, task->offset_blocks, job->io_size_blocks, + true, bdevperf_zcopy_populate_complete, task); + } else { + if (spdk_bdev_is_md_separate(job->bdev)) { + rc = spdk_bdev_read_blocks_with_md(desc, ch, task->buf, task->md_buf, + task->offset_blocks, + job->io_size_blocks, + bdevperf_complete, task); + } else { + rc = spdk_bdev_read_blocks(desc, ch, task->buf, task->offset_blocks, + job->io_size_blocks, bdevperf_complete, task); + } + } + break; + case SPDK_BDEV_IO_TYPE_ABORT: + rc = spdk_bdev_abort(desc, ch, task->task_to_abort, bdevperf_abort_complete, task); + break; + default: + assert(false); + rc = -EINVAL; + break; + } + + if (rc == -ENOMEM) { + bdevperf_queue_io_wait_with_cb(task, bdevperf_submit_task); + return; + } else if (rc != 0) { + printf("Failed to submit bdev_io: %d\n", rc); + if (job->verify) { + assert(task->offset_blocks / job->io_size_blocks >= job->ios_base); + offset_in_ios = task->offset_blocks / job->io_size_blocks - job->ios_base; + + assert(spdk_bit_array_get(job->outstanding, offset_in_ios) == true); + spdk_bit_array_clear(job->outstanding, offset_in_ios); + } + bdevperf_job_drain(job); + g_run_rc = rc; + return; + } + + job->current_queue_depth++; +} + +static void +bdevperf_zcopy_get_buf_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + struct bdevperf_task *task = cb_arg; + struct bdevperf_job *job = task->job; + struct iovec *iovs; + int iovcnt; + + if (!success) { + bdevperf_job_drain(job); + g_run_rc = -1; + return; + } + + task->bdev_io = bdev_io; + task->io_type = SPDK_BDEV_IO_TYPE_WRITE; + + if (job->verify || job->reset) { + /* When job->verify or job->reset is enabled, task->buf is used for + * verification of read after write. For write I/O, when zcopy APIs + * are used, task->buf cannot be used, and data must be written to + * the data buffer allocated underneath bdev layer instead. + * Hence we copy task->buf to the allocated data buffer here. + */ + spdk_bdev_io_get_iovec(bdev_io, &iovs, &iovcnt); + assert(iovcnt == 1); + assert(iovs != NULL); + + copy_data(iovs[0].iov_base, iovs[0].iov_len, task->buf, job->buf_size, + spdk_bdev_get_block_size(job->bdev), + spdk_bdev_io_get_md_buf(bdev_io), task->md_buf, + spdk_bdev_get_md_size(job->bdev), job->io_size_blocks); + } + + bdevperf_submit_task(task); +} + +static void +bdevperf_prep_zcopy_write_task(void *arg) +{ + struct bdevperf_task *task = arg; + struct bdevperf_job *job = task->job; + int rc; + + rc = spdk_bdev_zcopy_start(job->bdev_desc, job->ch, + task->offset_blocks, job->io_size_blocks, + false, bdevperf_zcopy_get_buf_complete, task); + if (rc != 0) { + assert(rc == -ENOMEM); + bdevperf_queue_io_wait_with_cb(task, bdevperf_prep_zcopy_write_task); + return; + } + + job->current_queue_depth++; +} + +static struct bdevperf_task * +bdevperf_job_get_task(struct bdevperf_job *job) +{ + struct bdevperf_task *task; + + task = TAILQ_FIRST(&job->task_list); + if (!task) { + printf("Task allocation failed\n"); + abort(); + } + + TAILQ_REMOVE(&job->task_list, task, link); + return task; +} + +static __thread unsigned int seed = 0; + +static void +bdevperf_submit_single(struct bdevperf_job *job, struct bdevperf_task *task) +{ + uint64_t offset_in_ios; + + if (job->is_random) { + offset_in_ios = rand_r(&seed) % job->size_in_ios; + } else { + offset_in_ios = job->offset_in_ios++; + if (job->offset_in_ios == job->size_in_ios) { + job->offset_in_ios = 0; + } + + /* Increment of offset_in_ios if there's already an outstanding IO + * to that location. We only need this with job->verify as random + * offsets are not supported with job->verify at this time. + */ + if (job->verify) { + assert(spdk_bit_array_find_first_clear(job->outstanding, 0) != UINT32_MAX); + + while (spdk_bit_array_get(job->outstanding, offset_in_ios)) { + offset_in_ios = job->offset_in_ios++; + if (job->offset_in_ios == job->size_in_ios) { + job->offset_in_ios = 0; + } + } + spdk_bit_array_set(job->outstanding, offset_in_ios); + } + } + + /* For multi-thread to same job, offset_in_ios is relative + * to the LBA range assigned for that job. job->offset_blocks + * is absolute (entire bdev LBA range). + */ + task->offset_blocks = (offset_in_ios + job->ios_base) * job->io_size_blocks; + + if (job->verify || job->reset) { + generate_data(task->buf, job->buf_size, + spdk_bdev_get_block_size(job->bdev), + task->md_buf, spdk_bdev_get_md_size(job->bdev), + job->io_size_blocks, rand_r(&seed) % 256); + if (g_zcopy) { + bdevperf_prep_zcopy_write_task(task); + return; + } else { + task->iov.iov_base = task->buf; + task->iov.iov_len = job->buf_size; + task->io_type = SPDK_BDEV_IO_TYPE_WRITE; + } + } else if (job->flush) { + task->io_type = SPDK_BDEV_IO_TYPE_FLUSH; + } else if (job->unmap) { + task->io_type = SPDK_BDEV_IO_TYPE_UNMAP; + } else if (job->write_zeroes) { + task->io_type = SPDK_BDEV_IO_TYPE_WRITE_ZEROES; + } else if ((job->rw_percentage == 100) || + (job->rw_percentage != 0 && ((rand_r(&seed) % 100) < job->rw_percentage))) { + task->io_type = SPDK_BDEV_IO_TYPE_READ; + } else { + if (g_zcopy) { + bdevperf_prep_zcopy_write_task(task); + return; + } else { + task->iov.iov_base = task->buf; + task->iov.iov_len = job->buf_size; + task->io_type = SPDK_BDEV_IO_TYPE_WRITE; + } + } + + bdevperf_submit_task(task); +} + +static int reset_job(void *arg); + +static void +reset_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + struct bdevperf_task *task = cb_arg; + struct bdevperf_job *job = task->job; + + if (!success) { + printf("Reset blockdev=%s failed\n", spdk_bdev_get_name(job->bdev)); + bdevperf_job_drain(job); + g_run_rc = -1; + } + + TAILQ_INSERT_TAIL(&job->task_list, task, link); + spdk_bdev_free_io(bdev_io); + + job->reset_timer = SPDK_POLLER_REGISTER(reset_job, job, + 10 * 1000000); +} + +static int +reset_job(void *arg) +{ + struct bdevperf_job *job = arg; + struct bdevperf_task *task; + int rc; + + spdk_poller_unregister(&job->reset_timer); + + /* Do reset. */ + task = bdevperf_job_get_task(job); + rc = spdk_bdev_reset(job->bdev_desc, job->ch, + reset_cb, task); + if (rc) { + printf("Reset failed: %d\n", rc); + bdevperf_job_drain(job); + g_run_rc = -1; + } + + return -1; +} + +static void +bdevperf_timeout_cb(void *cb_arg, struct spdk_bdev_io *bdev_io) +{ + struct bdevperf_job *job = cb_arg; + struct bdevperf_task *task; + + job->io_timeout++; + + if (job->is_draining || !job->abort || + !spdk_bdev_io_type_supported(job->bdev, SPDK_BDEV_IO_TYPE_ABORT)) { + return; + } + + task = bdevperf_job_get_task(job); + if (task == NULL) { + return; + } + + task->task_to_abort = spdk_bdev_io_get_cb_arg(bdev_io); + task->io_type = SPDK_BDEV_IO_TYPE_ABORT; + + bdevperf_submit_task(task); +} + +static void +bdevperf_job_run(void *ctx) +{ + struct bdevperf_job *job = ctx; + struct bdevperf_task *task; + int i; + + /* Submit initial I/O for this job. Each time one + * completes, another will be submitted. */ + + /* Start a timer to stop this I/O chain when the run is over */ + job->run_timer = SPDK_POLLER_REGISTER(bdevperf_job_drain, job, g_time_in_usec); + if (job->reset) { + job->reset_timer = SPDK_POLLER_REGISTER(reset_job, job, + 10 * 1000000); + } + + spdk_bdev_set_timeout(job->bdev_desc, g_timeout_in_sec, bdevperf_timeout_cb, job); + + for (i = 0; i < job->queue_depth; i++) { + task = bdevperf_job_get_task(job); + bdevperf_submit_single(job, task); + } +} + +static void +_performance_dump_done(void *ctx) +{ + struct bdevperf_aggregate_stats *stats = ctx; + + printf("\r =====================================================\n"); + printf("\r %-20s: %10.2f IOPS %10.2f MiB/s\n", + "Total", stats->total_io_per_second, stats->total_mb_per_second); + if (stats->total_failed_per_second != 0 || stats->total_timeout_per_second != 0) { + printf("\r %-20s: %10.2f Fail/s %8.2f TO/s\n", + "", stats->total_failed_per_second, stats->total_timeout_per_second); + } + fflush(stdout); + + g_performance_dump_active = false; + + free(stats); +} + +static void +_performance_dump(void *ctx) +{ + struct bdevperf_aggregate_stats *stats = ctx; + + performance_dump_job(stats, stats->current_job); + + /* This assumes the jobs list is static after start up time. + * That's true right now, but if that ever changed this would need a lock. */ + stats->current_job = TAILQ_NEXT(stats->current_job, link); + if (stats->current_job == NULL) { + spdk_thread_send_msg(g_master_thread, _performance_dump_done, stats); + } else { + spdk_thread_send_msg(stats->current_job->thread, _performance_dump, stats); + } +} + +static int +performance_statistics_thread(void *arg) +{ + struct bdevperf_aggregate_stats *stats; + + if (g_performance_dump_active) { + return -1; + } + + g_performance_dump_active = true; + + stats = calloc(1, sizeof(*stats)); + if (stats == NULL) { + return -1; + } + + g_show_performance_period_num++; + + stats->io_time_in_usec = g_show_performance_period_num * g_show_performance_period_in_usec; + stats->ema_period = g_show_performance_ema_period; + + /* Iterate all of the jobs to gather stats + * These jobs will not get removed here until a final performance dump is run, + * so this should be safe without locking. + */ + stats->current_job = TAILQ_FIRST(&g_bdevperf.jobs); + if (stats->current_job == NULL) { + spdk_thread_send_msg(g_master_thread, _performance_dump_done, stats); + } else { + spdk_thread_send_msg(stats->current_job->thread, _performance_dump, stats); + } + + return -1; +} + +static void +bdevperf_test(void) +{ + struct bdevperf_job *job; + + printf("Running I/O for %" PRIu64 " seconds...\n", g_time_in_usec / 1000000); + fflush(stdout); + + /* Start a timer to dump performance numbers */ + g_shutdown_tsc = spdk_get_ticks(); + if (g_show_performance_real_time) { + g_perf_timer = SPDK_POLLER_REGISTER(performance_statistics_thread, NULL, + g_show_performance_period_in_usec); + } + + /* Iterate jobs to start all I/O */ + TAILQ_FOREACH(job, &g_bdevperf.jobs, link) { + g_bdevperf.running_jobs++; + spdk_thread_send_msg(job->thread, bdevperf_job_run, job); + } +} + +static void +bdevperf_bdev_removed(void *arg) +{ + struct bdevperf_job *job = arg; + + bdevperf_job_drain(job); +} + +static uint32_t g_construct_job_count = 0; + +static void +_bdevperf_construct_job_done(void *ctx) +{ + if (--g_construct_job_count == 0) { + + if (g_run_rc != 0) { + /* Something failed. */ + bdevperf_test_done(NULL); + return; + } + + /* Ready to run the test */ + bdevperf_test(); + } +} + +/* Checkformat will not allow to use inlined type, + this is a workaround */ +typedef struct spdk_thread *spdk_thread_t; + +static spdk_thread_t +construct_job_thread(struct spdk_cpuset *cpumask, const char *tag) +{ + char thread_name[32]; + struct spdk_cpuset tmp; + + /* This function runs on the master thread. */ + assert(g_master_thread == spdk_get_thread()); + + /* Handle default mask */ + if (spdk_cpuset_count(cpumask) == 0) { + cpumask = &g_all_cpuset; + } + + /* Warn user that mask might need to be changed */ + spdk_cpuset_copy(&tmp, cpumask); + spdk_cpuset_or(&tmp, &g_all_cpuset); + if (!spdk_cpuset_equal(&tmp, &g_all_cpuset)) { + fprintf(stderr, "cpumask for '%s' is too big\n", tag); + } + + snprintf(thread_name, sizeof(thread_name), "%s_%s", + tag, + spdk_cpuset_fmt(cpumask)); + + return spdk_thread_create(thread_name, cpumask); +} + +static uint32_t +_get_next_core(void) +{ + static uint32_t current_core = SPDK_ENV_LCORE_ID_ANY; + + if (current_core == SPDK_ENV_LCORE_ID_ANY) { + current_core = spdk_env_get_first_core(); + return current_core; + } + + current_core = spdk_env_get_next_core(current_core); + if (current_core == SPDK_ENV_LCORE_ID_ANY) { + current_core = spdk_env_get_first_core(); + } + + return current_core; +} + +static void +_bdevperf_construct_job(void *ctx) +{ + struct bdevperf_job *job = ctx; + int rc; + + rc = spdk_bdev_open(job->bdev, true, bdevperf_bdev_removed, job, &job->bdev_desc); + if (rc != 0) { + SPDK_ERRLOG("Could not open leaf bdev %s, error=%d\n", spdk_bdev_get_name(job->bdev), rc); + g_run_rc = -EINVAL; + goto end; + } + + job->ch = spdk_bdev_get_io_channel(job->bdev_desc); + if (!job->ch) { + SPDK_ERRLOG("Could not get io_channel for device %s, error=%d\n", spdk_bdev_get_name(job->bdev), + rc); + g_run_rc = -ENOMEM; + goto end; + } + +end: + spdk_thread_send_msg(g_master_thread, _bdevperf_construct_job_done, NULL); +} + +static void +job_init_rw(struct bdevperf_job *job, enum job_config_rw rw) +{ + switch (rw) { + case JOB_CONFIG_RW_READ: + job->rw_percentage = 100; + break; + case JOB_CONFIG_RW_WRITE: + job->rw_percentage = 0; + break; + case JOB_CONFIG_RW_RANDREAD: + job->is_random = true; + job->rw_percentage = 100; + break; + case JOB_CONFIG_RW_RANDWRITE: + job->is_random = true; + job->rw_percentage = 0; + break; + case JOB_CONFIG_RW_RW: + job->is_random = false; + break; + case JOB_CONFIG_RW_RANDRW: + job->is_random = true; + break; + case JOB_CONFIG_RW_VERIFY: + job->verify = true; + job->rw_percentage = 50; + break; + case JOB_CONFIG_RW_RESET: + job->reset = true; + job->verify = true; + job->rw_percentage = 50; + break; + case JOB_CONFIG_RW_UNMAP: + job->unmap = true; + break; + case JOB_CONFIG_RW_FLUSH: + job->flush = true; + break; + case JOB_CONFIG_RW_WRITE_ZEROES: + job->write_zeroes = true; + break; + } +} + +static int +bdevperf_construct_job(struct spdk_bdev *bdev, struct job_config *config, + struct spdk_thread *thread) +{ + struct bdevperf_job *job; + struct bdevperf_task *task; + int block_size, data_block_size; + int rc; + int task_num, n; + + block_size = spdk_bdev_get_block_size(bdev); + data_block_size = spdk_bdev_get_data_block_size(bdev); + + job = calloc(1, sizeof(struct bdevperf_job)); + if (!job) { + fprintf(stderr, "Unable to allocate memory for new job.\n"); + return -ENOMEM; + } + + job->name = strdup(spdk_bdev_get_name(bdev)); + if (!job->name) { + fprintf(stderr, "Unable to allocate memory for job name.\n"); + free(job); + return -ENOMEM; + } + + job->workload_type = g_workload_type; + job->io_size = config->bs; + job->rw_percentage = config->rwmixread; + job->continue_on_failure = g_continue_on_failure; + job->queue_depth = config->iodepth; + job->bdev = bdev; + job->io_size_blocks = job->io_size / data_block_size; + job->buf_size = job->io_size_blocks * block_size; + job_init_rw(job, config->rw); + + if ((job->io_size % data_block_size) != 0) { + SPDK_ERRLOG("IO size (%d) is not multiples of data block size of bdev %s (%"PRIu32")\n", + job->io_size, spdk_bdev_get_name(bdev), data_block_size); + free(job->name); + free(job); + return -ENOTSUP; + } + + if (job->unmap && !spdk_bdev_io_type_supported(bdev, SPDK_BDEV_IO_TYPE_UNMAP)) { + printf("Skipping %s because it does not support unmap\n", spdk_bdev_get_name(bdev)); + free(job->name); + free(job); + return -ENOTSUP; + } + + if (spdk_bdev_is_dif_check_enabled(bdev, SPDK_DIF_CHECK_TYPE_REFTAG)) { + job->dif_check_flags |= SPDK_DIF_FLAGS_REFTAG_CHECK; + } + if (spdk_bdev_is_dif_check_enabled(bdev, SPDK_DIF_CHECK_TYPE_GUARD)) { + job->dif_check_flags |= SPDK_DIF_FLAGS_GUARD_CHECK; + } + + job->offset_in_ios = 0; + + if (config->length != 0) { + /* Use subset of disk */ + job->size_in_ios = config->length / job->io_size_blocks; + job->ios_base = config->offset / job->io_size_blocks; + } else { + /* Use whole disk */ + job->size_in_ios = spdk_bdev_get_num_blocks(bdev) / job->io_size_blocks; + job->ios_base = 0; + } + + if (job->verify) { + job->outstanding = spdk_bit_array_create(job->size_in_ios); + if (job->outstanding == NULL) { + SPDK_ERRLOG("Could not create outstanding array bitmap for bdev %s\n", + spdk_bdev_get_name(bdev)); + free(job->name); + free(job); + return -ENOMEM; + } + } + + TAILQ_INIT(&job->task_list); + + task_num = job->queue_depth; + if (job->reset) { + task_num += 1; + } + if (job->abort) { + task_num += job->queue_depth; + } + + TAILQ_INSERT_TAIL(&g_bdevperf.jobs, job, link); + + for (n = 0; n < task_num; n++) { + task = calloc(1, sizeof(struct bdevperf_task)); + if (!task) { + fprintf(stderr, "Failed to allocate task from memory\n"); + return -ENOMEM; + } + + task->buf = spdk_zmalloc(job->buf_size, spdk_bdev_get_buf_align(job->bdev), NULL, + SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); + if (!task->buf) { + fprintf(stderr, "Cannot allocate buf for task=%p\n", task); + free(task); + return -ENOMEM; + } + + if (spdk_bdev_is_md_separate(job->bdev)) { + task->md_buf = spdk_zmalloc(job->io_size_blocks * + spdk_bdev_get_md_size(job->bdev), 0, NULL, + SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); + if (!task->md_buf) { + fprintf(stderr, "Cannot allocate md buf for task=%p\n", task); + spdk_free(task->buf); + free(task); + return -ENOMEM; + } + } + + task->job = job; + TAILQ_INSERT_TAIL(&job->task_list, task, link); + } + + job->thread = thread; + + g_construct_job_count++; + + rc = spdk_thread_send_msg(thread, _bdevperf_construct_job, job); + assert(rc == 0); + + return rc; +} + +static int +parse_rw(const char *str, enum job_config_rw ret) +{ + if (str == NULL) { + return ret; + } + + if (!strcmp(str, "read")) { + ret = JOB_CONFIG_RW_READ; + } else if (!strcmp(str, "randread")) { + ret = JOB_CONFIG_RW_RANDREAD; + } else if (!strcmp(str, "write")) { + ret = JOB_CONFIG_RW_WRITE; + } else if (!strcmp(str, "randwrite")) { + ret = JOB_CONFIG_RW_RANDWRITE; + } else if (!strcmp(str, "verify")) { + ret = JOB_CONFIG_RW_VERIFY; + } else if (!strcmp(str, "reset")) { + ret = JOB_CONFIG_RW_RESET; + } else if (!strcmp(str, "unmap")) { + ret = JOB_CONFIG_RW_UNMAP; + } else if (!strcmp(str, "write_zeroes")) { + ret = JOB_CONFIG_RW_WRITE_ZEROES; + } else if (!strcmp(str, "flush")) { + ret = JOB_CONFIG_RW_FLUSH; + } else if (!strcmp(str, "rw")) { + ret = JOB_CONFIG_RW_RW; + } else if (!strcmp(str, "randrw")) { + ret = JOB_CONFIG_RW_RANDRW; + } else { + fprintf(stderr, "rw must be one of\n" + "(read, write, randread, randwrite, rw, randrw, verify, reset, unmap, flush)\n"); + ret = BDEVPERF_CONFIG_ERROR; + } + + return ret; +} + +static const char * +config_filename_next(const char *filename, char *out) +{ + int i, k; + + if (filename == NULL) { + out[0] = '\0'; + return NULL; + } + + if (filename[0] == ':') { + filename++; + } + + for (i = 0, k = 0; + filename[i] != '\0' && + filename[i] != ':' && + i < BDEVPERF_CONFIG_MAX_FILENAME; + i++) { + if (filename[i] == ' ' || filename[i] == '\t') { + continue; + } + + out[k++] = filename[i]; + } + out[k] = 0; + + return filename + i; +} + +static void +bdevperf_construct_config_jobs(void) +{ + char filename[BDEVPERF_CONFIG_MAX_FILENAME]; + struct spdk_thread *thread; + struct job_config *config; + struct spdk_bdev *bdev; + const char *filenames; + int rc; + + TAILQ_FOREACH(config, &job_config_list, link) { + filenames = config->filename; + + thread = construct_job_thread(&config->cpumask, config->name); + assert(thread); + + while (filenames) { + filenames = config_filename_next(filenames, filename); + if (strlen(filename) == 0) { + break; + } + + bdev = spdk_bdev_get_by_name(filename); + if (!bdev) { + fprintf(stderr, "Unable to find bdev '%s'\n", filename); + g_run_rc = -EINVAL; + return; + } + + rc = bdevperf_construct_job(bdev, config, thread); + if (rc < 0) { + g_run_rc = rc; + return; + } + } + } +} + +static int +make_cli_job_config(const char *filename, int offset, int range) +{ + struct job_config *config = calloc(1, sizeof(*config)); + + if (config == NULL) { + fprintf(stderr, "Unable to allocate memory for job config\n"); + return -ENOMEM; + } + + config->name = filename; + config->filename = filename; + spdk_cpuset_zero(&config->cpumask); + spdk_cpuset_set_cpu(&config->cpumask, _get_next_core(), true); + config->bs = g_io_size; + config->iodepth = g_queue_depth; + config->rwmixread = g_rw_percentage; + config->offset = offset; + config->length = range; + config->rw = parse_rw(g_workload_type, BDEVPERF_CONFIG_ERROR); + if ((int)config->rw == BDEVPERF_CONFIG_ERROR) { + return -EINVAL; + } + + TAILQ_INSERT_TAIL(&job_config_list, config, link); + return 0; +} + +static void +bdevperf_construct_multithread_jobs(void) +{ + struct spdk_bdev *bdev; + uint32_t i; + uint32_t num_cores; + uint32_t blocks_per_job; + uint32_t offset; + + num_cores = 0; + SPDK_ENV_FOREACH_CORE(i) { + num_cores++; + } + + if (num_cores == 0) { + g_run_rc = -EINVAL; + return; + } + + if (g_job_bdev_name != NULL) { + bdev = spdk_bdev_get_by_name(g_job_bdev_name); + if (!bdev) { + fprintf(stderr, "Unable to find bdev '%s'\n", g_job_bdev_name); + return; + } + + blocks_per_job = spdk_bdev_get_num_blocks(bdev) / num_cores; + offset = 0; + + SPDK_ENV_FOREACH_CORE(i) { + g_run_rc = make_cli_job_config(g_job_bdev_name, offset, blocks_per_job); + if (g_run_rc) { + return; + } + + offset += blocks_per_job; + } + } else { + bdev = spdk_bdev_first_leaf(); + while (bdev != NULL) { + blocks_per_job = spdk_bdev_get_num_blocks(bdev) / num_cores; + offset = 0; + + SPDK_ENV_FOREACH_CORE(i) { + g_run_rc = make_cli_job_config(spdk_bdev_get_name(bdev), + offset, blocks_per_job); + if (g_run_rc) { + return; + } + + offset += blocks_per_job; + } + + bdev = spdk_bdev_next_leaf(bdev); + } + } +} + +static void +bdevperf_construct_jobs(void) +{ + struct spdk_bdev *bdev; + + /* There are three different modes for allocating jobs. Standard mode + * (the default) creates one spdk_thread per bdev and runs the I/O job there. + * + * The -C flag places bdevperf into "multithread" mode, meaning it creates + * one spdk_thread per bdev PER CORE, and runs a copy of the job on each. + * This runs multiple threads per bdev, effectively. + * + * The -j flag implies "FIO" mode which tries to mimic semantic of FIO jobs. + * In "FIO" mode, threads are spawned per-job instead of per-bdev. + * Each FIO job can be individually parameterized by filename, cpu mask, etc, + * which is different from other modes in that they only support global options. + */ + + /* Increment initial construct_jobs count so that it will never reach 0 in the middle + * of iteration. + */ + g_construct_job_count = 1; + + if (g_bdevperf_conf) { + goto end; + } else if (g_multithread_mode) { + bdevperf_construct_multithread_jobs(); + goto end; + } + + if (g_job_bdev_name != NULL) { + bdev = spdk_bdev_get_by_name(g_job_bdev_name); + if (bdev) { + /* Construct the job */ + g_run_rc = make_cli_job_config(g_job_bdev_name, 0, 0); + } else { + fprintf(stderr, "Unable to find bdev '%s'\n", g_job_bdev_name); + } + } else { + bdev = spdk_bdev_first_leaf(); + + while (bdev != NULL) { + /* Construct the job */ + g_run_rc = make_cli_job_config(spdk_bdev_get_name(bdev), 0, 0); + if (g_run_rc) { + break; + } + + bdev = spdk_bdev_next_leaf(bdev); + } + } + +end: + if (g_run_rc == 0) { + bdevperf_construct_config_jobs(); + } + + if (--g_construct_job_count == 0) { + if (g_run_rc != 0) { + /* Something failed. */ + bdevperf_test_done(NULL); + return; + } + + bdevperf_test(); + } +} + +static int +parse_uint_option(struct spdk_conf_section *s, const char *name, int def) +{ + const char *job_name; + int tmp; + + tmp = spdk_conf_section_get_intval(s, name); + if (tmp == -1) { + /* Field was not found. Check default value + * In [global] section it is ok to have undefined values + * but for other sections it is not ok */ + if (def == BDEVPERF_CONFIG_UNDEFINED) { + job_name = spdk_conf_section_get_name(s); + if (strcmp(job_name, "global") == 0) { + return def; + } + + fprintf(stderr, + "Job '%s' has no '%s' assigned\n", + job_name, name); + return BDEVPERF_CONFIG_ERROR; + } + return def; + } + + /* NOTE: get_intval returns nonnegative on success */ + if (tmp < 0) { + fprintf(stderr, "Job '%s' has bad '%s' value.\n", + spdk_conf_section_get_name(s), name); + return BDEVPERF_CONFIG_ERROR; + } + + return tmp; +} + +/* CLI arguments override parameters for global sections */ +static void +config_set_cli_args(struct job_config *config) +{ + if (g_job_bdev_name) { + config->filename = g_job_bdev_name; + } + if (g_io_size > 0) { + config->bs = g_io_size; + } + if (g_queue_depth > 0) { + config->iodepth = g_queue_depth; + } + if (g_rw_percentage > 0) { + config->rwmixread = g_rw_percentage; + } + if (g_workload_type) { + config->rw = parse_rw(g_workload_type, config->rw); + } +} + +static int +read_job_config(void) +{ + struct job_config global_default_config; + struct job_config global_config; + struct spdk_conf_section *s; + struct job_config *config; + const char *cpumask; + const char *rw; + bool is_global; + int n = 0; + + if (g_bdevperf_conf_file == NULL) { + return 0; + } + + g_bdevperf_conf = spdk_conf_allocate(); + if (g_bdevperf_conf == NULL) { + fprintf(stderr, "Could not allocate job config structure\n"); + return 1; + } + + spdk_conf_disable_sections_merge(g_bdevperf_conf); + if (spdk_conf_read(g_bdevperf_conf, g_bdevperf_conf_file)) { + fprintf(stderr, "Invalid job config"); + return 1; + } + + /* Initialize global defaults */ + global_default_config.filename = NULL; + /* Zero mask is the same as g_all_cpuset + * The g_all_cpuset is not initialized yet, + * so use zero mask as the default instead */ + spdk_cpuset_zero(&global_default_config.cpumask); + global_default_config.bs = BDEVPERF_CONFIG_UNDEFINED; + global_default_config.iodepth = BDEVPERF_CONFIG_UNDEFINED; + /* bdevperf has no default for -M option but in FIO the default is 50 */ + global_default_config.rwmixread = 50; + global_default_config.offset = 0; + /* length 0 means 100% */ + global_default_config.length = 0; + global_default_config.rw = BDEVPERF_CONFIG_UNDEFINED; + config_set_cli_args(&global_default_config); + + if ((int)global_default_config.rw == BDEVPERF_CONFIG_ERROR) { + return 1; + } + + /* There is only a single instance of global job_config + * We just reset its value when we encounter new [global] section */ + global_config = global_default_config; + + for (s = spdk_conf_first_section(g_bdevperf_conf); + s != NULL; + s = spdk_conf_next_section(s)) { + config = calloc(1, sizeof(*config)); + if (config == NULL) { + fprintf(stderr, "Unable to allocate memory for job config\n"); + return 1; + } + + config->name = spdk_conf_section_get_name(s); + is_global = strcmp(config->name, "global") == 0; + + if (is_global) { + global_config = global_default_config; + } + + config->filename = spdk_conf_section_get_val(s, "filename"); + if (config->filename == NULL) { + config->filename = global_config.filename; + } + if (!is_global) { + if (config->filename == NULL) { + fprintf(stderr, "Job '%s' expects 'filename' parameter\n", config->name); + goto error; + } else if (strnlen(config->filename, BDEVPERF_CONFIG_MAX_FILENAME) + >= BDEVPERF_CONFIG_MAX_FILENAME) { + fprintf(stderr, + "filename for '%s' job is too long. Max length is %d\n", + config->name, BDEVPERF_CONFIG_MAX_FILENAME); + goto error; + } + } + + cpumask = spdk_conf_section_get_val(s, "cpumask"); + if (cpumask == NULL) { + config->cpumask = global_config.cpumask; + } else if (spdk_cpuset_parse(&config->cpumask, cpumask)) { + fprintf(stderr, "Job '%s' has bad 'cpumask' value\n", config->name); + goto error; + } + + config->bs = parse_uint_option(s, "bs", global_config.bs); + if (config->bs == BDEVPERF_CONFIG_ERROR) { + goto error; + } else if (config->bs == 0) { + fprintf(stderr, "'bs' of job '%s' must be greater than 0\n", config->name); + goto error; + } + + config->iodepth = parse_uint_option(s, "iodepth", global_config.iodepth); + if (config->iodepth == BDEVPERF_CONFIG_ERROR) { + goto error; + } else if (config->iodepth == 0) { + fprintf(stderr, + "'iodepth' of job '%s' must be greater than 0\n", + config->name); + goto error; + } + + config->rwmixread = parse_uint_option(s, "rwmixread", global_config.rwmixread); + if (config->rwmixread == BDEVPERF_CONFIG_ERROR) { + goto error; + } else if (config->rwmixread > 100) { + fprintf(stderr, + "'rwmixread' value of '%s' job is not in 0-100 range\n", + config->name); + goto error; + } + + config->offset = parse_uint_option(s, "offset", global_config.offset); + if (config->offset == BDEVPERF_CONFIG_ERROR) { + goto error; + } + + config->length = parse_uint_option(s, "length", global_config.length); + if (config->length == BDEVPERF_CONFIG_ERROR) { + goto error; + } + + rw = spdk_conf_section_get_val(s, "rw"); + config->rw = parse_rw(rw, global_config.rw); + if ((int)config->rw == BDEVPERF_CONFIG_ERROR) { + fprintf(stderr, "Job '%s' has bad 'rw' value\n", config->name); + goto error; + } else if (!is_global && (int)config->rw == BDEVPERF_CONFIG_UNDEFINED) { + fprintf(stderr, "Job '%s' has no 'rw' assigned\n", config->name); + goto error; + } + + if (is_global) { + config_set_cli_args(config); + global_config = *config; + free(config); + } else { + TAILQ_INSERT_TAIL(&job_config_list, config, link); + n++; + } + } + + printf("Using job config with %d jobs\n", n); + return 0; +error: + free(config); + return 1; +} + +static void +bdevperf_run(void *arg1) +{ + uint32_t i; + + g_master_thread = spdk_get_thread(); + + spdk_cpuset_zero(&g_all_cpuset); + SPDK_ENV_FOREACH_CORE(i) { + spdk_cpuset_set_cpu(&g_all_cpuset, i, true); + } + + if (g_wait_for_tests) { + /* Do not perform any tests until RPC is received */ + return; + } + + bdevperf_construct_jobs(); +} + +static void +rpc_perform_tests_cb(void) +{ + struct spdk_json_write_ctx *w; + struct spdk_jsonrpc_request *request = g_request; + + g_request = NULL; + + if (g_run_rc == 0) { + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_uint32(w, g_run_rc); + spdk_jsonrpc_end_result(request, w); + } else { + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "bdevperf failed with error %s", spdk_strerror(-g_run_rc)); + } + + /* Reset g_run_rc to 0 for the next test run. */ + g_run_rc = 0; +} + +static void +rpc_perform_tests(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params) +{ + if (params != NULL) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "perform_tests method requires no parameters"); + return; + } + if (g_request != NULL) { + fprintf(stderr, "Another test is already in progress.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + spdk_strerror(-EINPROGRESS)); + return; + } + g_request = request; + + bdevperf_construct_jobs(); +} +SPDK_RPC_REGISTER("perform_tests", rpc_perform_tests, SPDK_RPC_RUNTIME) + +static void +_bdevperf_job_drain(void *ctx) +{ + bdevperf_job_drain(ctx); +} + +static void +spdk_bdevperf_shutdown_cb(void) +{ + g_shutdown = true; + struct bdevperf_job *job, *tmp; + + if (g_bdevperf.running_jobs == 0) { + bdevperf_test_done(NULL); + return; + } + + g_shutdown_tsc = spdk_get_ticks() - g_shutdown_tsc; + + /* Iterate jobs to stop all I/O */ + TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, tmp) { + spdk_thread_send_msg(job->thread, _bdevperf_job_drain, job); + } +} + +static int +bdevperf_parse_arg(int ch, char *arg) +{ + long long tmp; + + if (ch == 'w') { + g_workload_type = optarg; + } else if (ch == 'T') { + g_job_bdev_name = optarg; + } else if (ch == 'z') { + g_wait_for_tests = true; + } else if (ch == 'x') { + g_zcopy = false; + } else if (ch == 'A') { + g_abort = true; + } else if (ch == 'C') { + g_multithread_mode = true; + } else if (ch == 'f') { + g_continue_on_failure = true; + } else if (ch == 'j') { + g_bdevperf_conf_file = optarg; + } else { + tmp = spdk_strtoll(optarg, 10); + if (tmp < 0) { + fprintf(stderr, "Parse failed for the option %c.\n", ch); + return tmp; + } else if (tmp >= INT_MAX) { + fprintf(stderr, "Parsed option was too large %c.\n", ch); + return -ERANGE; + } + + switch (ch) { + case 'q': + g_queue_depth = tmp; + break; + case 'o': + g_io_size = tmp; + break; + case 't': + g_time_in_sec = tmp; + break; + case 'k': + g_timeout_in_sec = tmp; + break; + case 'M': + g_rw_percentage = tmp; + g_mix_specified = true; + break; + case 'P': + g_show_performance_ema_period = tmp; + break; + case 'S': + g_show_performance_real_time = 1; + g_show_performance_period_in_usec = tmp * 1000000; + break; + default: + return -EINVAL; + } + } + return 0; +} + +static void +bdevperf_usage(void) +{ + printf(" -q io depth\n"); + printf(" -o io size in bytes\n"); + printf(" -w io pattern type, must be one of (read, write, randread, randwrite, rw, randrw, verify, reset, unmap, flush)\n"); + printf(" -t