summaryrefslogtreecommitdiffstats
path: root/src/spdk/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:20 +0000
commit483eb2f56657e8e7f419ab1a4fab8dce9ade8609 (patch)
treee5d88d25d870d5dedacb6bbdbe2a966086a0a5cf /src/spdk/test
parentInitial commit. (diff)
downloadceph-upstream.tar.xz
ceph-upstream.zip
Adding upstream version 14.2.21.upstream/14.2.21upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/spdk/test')
-rw-r--r--src/spdk/test/Makefile47
-rw-r--r--src/spdk/test/app/Makefile44
-rw-r--r--src/spdk/test/app/bdev_svc/.gitignore1
-rw-r--r--src/spdk/test/app/bdev_svc/Makefile65
-rw-r--r--src/spdk/test/app/bdev_svc/bdev_svc.c112
-rw-r--r--src/spdk/test/app/histogram_perf/.gitignore1
-rw-r--r--src/spdk/test/app/histogram_perf/Makefile55
-rw-r--r--src/spdk/test/app/histogram_perf/histogram_perf.c102
-rw-r--r--src/spdk/test/app/jsoncat/.gitignore1
-rw-r--r--src/spdk/test/app/jsoncat/Makefile55
-rw-r--r--src/spdk/test/app/jsoncat/jsoncat.c229
-rwxr-xr-xsrc/spdk/test/app/match/match326
-rw-r--r--src/spdk/test/app/stub/.gitignore1
-rw-r--r--src/spdk/test/app/stub/Makefile58
-rw-r--r--src/spdk/test/app/stub/stub.c147
-rw-r--r--src/spdk/test/bdev/Makefile44
-rw-r--r--src/spdk/test/bdev/bdev.conf.in44
-rw-r--r--src/spdk/test/bdev/bdevio/.gitignore1
-rw-r--r--src/spdk/test/bdev/bdevio/Makefile61
-rw-r--r--src/spdk/test/bdev/bdevio/bdevio.c973
-rwxr-xr-xsrc/spdk/test/bdev/bdevjson/json_config.sh27
-rwxr-xr-xsrc/spdk/test/bdev/bdevjson/rbd_json_config.sh26
-rw-r--r--src/spdk/test/bdev/bdevperf/.gitignore1
-rw-r--r--src/spdk/test/bdev/bdevperf/Makefile61
-rw-r--r--src/spdk/test/bdev/bdevperf/bdevperf.c1035
-rwxr-xr-xsrc/spdk/test/bdev/blockdev.sh171
-rw-r--r--src/spdk/test/bdev/nbd_common.sh95
-rwxr-xr-xsrc/spdk/test/bdev/nbdjson/json_config.sh28
-rw-r--r--src/spdk/test/blobfs/Makefile49
-rw-r--r--src/spdk/test/blobfs/fuse/.gitignore1
-rw-r--r--src/spdk/test/blobfs/fuse/Makefile60
-rw-r--r--src/spdk/test/blobfs/fuse/fuse.c348
-rw-r--r--src/spdk/test/blobfs/mkfs/.gitignore1
-rw-r--r--src/spdk/test/blobfs/mkfs/Makefile59
-rw-r--r--src/spdk/test/blobfs/mkfs/mkfs.c150
-rw-r--r--src/spdk/test/blobfs/rocksdb/.gitignore1
-rw-r--r--src/spdk/test/blobfs/rocksdb/common_flags.txt27
-rwxr-xr-xsrc/spdk/test/blobfs/rocksdb/postprocess.py70
-rwxr-xr-xsrc/spdk/test/blobfs/rocksdb/rocksdb.sh134
-rwxr-xr-xsrc/spdk/test/blobfs/rocksdb/run_tests.sh197
-rw-r--r--src/spdk/test/blobfs/test_plan.md67
-rwxr-xr-xsrc/spdk/test/blobstore/blob_io_wait/blob_io_wait.sh49
-rwxr-xr-xsrc/spdk/test/blobstore/blobstore.sh41
-rw-r--r--src/spdk/test/blobstore/btest.out.ignore5
-rw-r--r--src/spdk/test/blobstore/btest.out.match130
-rw-r--r--src/spdk/test/blobstore/test.bs12
-rw-r--r--src/spdk/test/common/autotest_common.sh706
-rw-r--r--src/spdk/test/common/config/README.md99
-rw-r--r--src/spdk/test/common/config/vm_setup.conf12
-rwxr-xr-xsrc/spdk/test/common/config/vm_setup.sh424
-rw-r--r--src/spdk/test/common/lib/test_env.c469
-rw-r--r--src/spdk/test/common/lib/ut_multithread.c278
-rw-r--r--src/spdk/test/config_converter/config.ini151
-rw-r--r--src/spdk/test/config_converter/config_virtio.ini21
-rw-r--r--src/spdk/test/config_converter/spdk_config.json481
-rw-r--r--src/spdk/test/config_converter/spdk_config_virtio.json138
-rwxr-xr-xsrc/spdk/test/config_converter/test_converter.sh23
-rw-r--r--src/spdk/test/cpp_headers/.gitignore1
-rw-r--r--src/spdk/test/cpp_headers/Makefile51
-rw-r--r--src/spdk/test/env/Makefile50
-rwxr-xr-xsrc/spdk/test/env/env.sh24
-rw-r--r--src/spdk/test/env/memory/.gitignore1
-rw-r--r--src/spdk/test/env/memory/Makefile43
-rw-r--r--src/spdk/test/env/memory/memory_ut.c504
-rw-r--r--src/spdk/test/env/pci/.gitignore1
-rw-r--r--src/spdk/test/env/pci/Makefile43
-rw-r--r--src/spdk/test/env/pci/pci_ut.c94
-rw-r--r--src/spdk/test/env/vtophys/.gitignore1
-rw-r--r--src/spdk/test/env/vtophys/Makefile42
-rw-r--r--src/spdk/test/env/vtophys/vtophys.c135
-rw-r--r--src/spdk/test/event/Makefile44
-rwxr-xr-xsrc/spdk/test/event/event.sh12
-rw-r--r--src/spdk/test/event/event_perf/.gitignore1
-rw-r--r--src/spdk/test/event/event_perf/Makefile54
-rw-r--r--src/spdk/test/event/event_perf/event_perf.c180
-rw-r--r--src/spdk/test/event/reactor/.gitignore1
-rw-r--r--src/spdk/test/event/reactor/Makefile54
-rw-r--r--src/spdk/test/event/reactor/reactor.c144
-rw-r--r--src/spdk/test/event/reactor_perf/.gitignore1
-rw-r--r--src/spdk/test/event/reactor_perf/Makefile54
-rw-r--r--src/spdk/test/event/reactor_perf/reactor_perf.c144
-rwxr-xr-xsrc/spdk/test/ioat/ioat.sh20
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/bdev_io_wait/bdev_io_wait.sh57
-rw-r--r--src/spdk/test/iscsi_tgt/calsoft/auth.conf3
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/calsoft/calsoft.py115
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/calsoft/calsoft.sh71
-rw-r--r--src/spdk/test/iscsi_tgt/calsoft/iscsi.json17
-rw-r--r--src/spdk/test/iscsi_tgt/calsoft/its.conf7
-rw-r--r--src/spdk/test/iscsi_tgt/common.sh50
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/digests/digests.sh104
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/ext4test/ext4test.sh128
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/filesystem/filesystem.sh136
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/fio/fio.sh142
-rw-r--r--src/spdk/test/iscsi_tgt/fio/iscsi.conf.in16
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/fio/running_config.sh22
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/initiator/initiator.sh57
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/ip_migration/ip_migration.sh91
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/iscsi_tgt.sh76
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/iscsijson/json_config.sh43
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/lvol/iscsi_lvol.sh82
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/multiconnection/multiconnection.sh84
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/nvme_remote/fio_remote_nvme.sh112
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/pmem/iscsi_pmem.sh78
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/qos/qos.sh98
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/rbd/rbd.sh67
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/reset/reset.sh77
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/rpc_config/rpc_config.py502
-rwxr-xr-xsrc/spdk/test/iscsi_tgt/rpc_config/rpc_config.sh46
-rw-r--r--src/spdk/test/iscsi_tgt/test_plan.md41
-rwxr-xr-xsrc/spdk/test/json_config/clear_config.py212
-rw-r--r--src/spdk/test/json_config/common.sh249
-rwxr-xr-xsrc/spdk/test/json_config/config_filter.py70
-rwxr-xr-xsrc/spdk/test/lvol/lvol.sh135
-rwxr-xr-xsrc/spdk/test/lvol/lvol_test.py45
-rw-r--r--src/spdk/test/lvol/rpc_commands_lib.py241
-rw-r--r--src/spdk/test/lvol/test_cases.py2591
-rw-r--r--src/spdk/test/lvol/test_plan.md585
-rw-r--r--src/spdk/test/nvme/Makefile44
-rw-r--r--src/spdk/test/nvme/aer/.gitignore1
-rw-r--r--src/spdk/test/nvme/aer/Makefile39
-rw-r--r--src/spdk/test/nvme/aer/aer.c580
-rw-r--r--src/spdk/test/nvme/deallocated_value/.gitignore1
-rw-r--r--src/spdk/test/nvme/deallocated_value/Makefile39
-rw-r--r--src/spdk/test/nvme/deallocated_value/deallocated_value.c445
-rw-r--r--src/spdk/test/nvme/e2edp/.gitignore1
-rw-r--r--src/spdk/test/nvme/e2edp/Makefile39
-rw-r--r--src/spdk/test/nvme/e2edp/nvme_dp.c659
-rw-r--r--src/spdk/test/nvme/err_injection/.gitignore1
-rw-r--r--src/spdk/test/nvme/err_injection/Makefile39
-rw-r--r--src/spdk/test/nvme/err_injection/err_injection.c279
-rwxr-xr-xsrc/spdk/test/nvme/hotplug.sh147
-rwxr-xr-xsrc/spdk/test/nvme/nvme.sh187
-rw-r--r--src/spdk/test/nvme/overhead/.gitignore1
-rw-r--r--src/spdk/test/nvme/overhead/Makefile44
-rw-r--r--src/spdk/test/nvme/overhead/README24
-rw-r--r--src/spdk/test/nvme/overhead/overhead.c720
-rw-r--r--src/spdk/test/nvme/reset/.gitignore1
-rw-r--r--src/spdk/test/nvme/reset/Makefile39
-rw-r--r--src/spdk/test/nvme/reset/reset.c689
-rw-r--r--src/spdk/test/nvme/sgl/.gitignore1
-rw-r--r--src/spdk/test/nvme/sgl/Makefile39
-rw-r--r--src/spdk/test/nvme/sgl/sgl.c542
-rwxr-xr-xsrc/spdk/test/nvme/spdk_nvme_cli.sh58
-rwxr-xr-xsrc/spdk/test/nvmf/bdev_io_wait/bdev_io_wait.sh66
-rwxr-xr-xsrc/spdk/test/nvmf/common.sh200
-rwxr-xr-xsrc/spdk/test/nvmf/create_transport/create_transport.sh77
-rwxr-xr-xsrc/spdk/test/nvmf/discovery/discovery.sh76
-rwxr-xr-xsrc/spdk/test/nvmf/filesystem/filesystem.sh98
-rwxr-xr-xsrc/spdk/test/nvmf/fio/fio.sh108
-rwxr-xr-xsrc/spdk/test/nvmf/fio/nvmf_fio.py133
-rwxr-xr-xsrc/spdk/test/nvmf/host/aer.sh78
-rwxr-xr-xsrc/spdk/test/nvmf/host/bdevperf.sh52
-rwxr-xr-xsrc/spdk/test/nvmf/host/fio.sh92
-rwxr-xr-xsrc/spdk/test/nvmf/host/identify.sh65
-rwxr-xr-xsrc/spdk/test/nvmf/host/identify_kernel_nvmf.sh80
-rwxr-xr-xsrc/spdk/test/nvmf/host/perf.sh95
-rwxr-xr-xsrc/spdk/test/nvmf/lvol/nvmf_lvol.sh118
-rwxr-xr-xsrc/spdk/test/nvmf/multiconnection/multiconnection.sh82
-rwxr-xr-xsrc/spdk/test/nvmf/nmic/nmic.sh87
-rwxr-xr-xsrc/spdk/test/nvmf/nvme_cli/nvme_cli.sh96
-rwxr-xr-xsrc/spdk/test/nvmf/nvmf.sh64
-rwxr-xr-xsrc/spdk/test/nvmf/nvmfjson/json_config.sh40
-rwxr-xr-xsrc/spdk/test/nvmf/rpc/rpc.sh145
-rwxr-xr-xsrc/spdk/test/nvmf/shutdown/shutdown.sh88
-rw-r--r--src/spdk/test/nvmf/test_plan.md95
-rw-r--r--src/spdk/test/pmem/common.sh107
-rwxr-xr-xsrc/spdk/test/pmem/json_config/json_config.sh25
-rwxr-xr-xsrc/spdk/test/pmem/pmem.sh701
-rw-r--r--src/spdk/test/pmem/test_plan.md310
-rw-r--r--src/spdk/test/rpc_client/.gitignore1
-rw-r--r--src/spdk/test/rpc_client/Makefile56
-rwxr-xr-xsrc/spdk/test/rpc_client/rpc_client.sh36
-rw-r--r--src/spdk/test/rpc_client/rpc_client_test.c118
-rw-r--r--src/spdk/test/spdk_cunit.h56
-rw-r--r--src/spdk/test/spdkcli/common.sh26
-rwxr-xr-xsrc/spdk/test/spdkcli/iscsi.sh60
-rw-r--r--src/spdk/test/spdkcli/match_files/spdkcli_details_vhost.test.match28
-rw-r--r--src/spdk/test/spdkcli/match_files/spdkcli_iscsi.test.match53
-rw-r--r--src/spdk/test/spdkcli/match_files/spdkcli_nvmf.test.match32
-rw-r--r--src/spdk/test/spdkcli/match_files/spdkcli_pmem.test.match2
-rw-r--r--src/spdk/test/spdkcli/match_files/spdkcli_rbd.test.match2
-rw-r--r--src/spdk/test/spdkcli/match_files/spdkcli_vhost.test.match75
-rw-r--r--src/spdk/test/spdkcli/match_files/spdkcli_virtio_pci.test.match18
-rw-r--r--src/spdk/test/spdkcli/match_files/spdkcli_virtio_user.test.match8
-rwxr-xr-xsrc/spdk/test/spdkcli/nvmf.sh83
-rwxr-xr-xsrc/spdk/test/spdkcli/pmem.sh33
-rwxr-xr-xsrc/spdk/test/spdkcli/rbd.sh34
-rwxr-xr-xsrc/spdk/test/spdkcli/spdkcli_job.py38
-rwxr-xr-xsrc/spdk/test/spdkcli/vhost.sh106
-rwxr-xr-xsrc/spdk/test/spdkcli/virtio.sh74
-rw-r--r--src/spdk/test/unit/Makefile44
-rw-r--r--src/spdk/test/unit/include/Makefile44
-rw-r--r--src/spdk/test/unit/include/spdk/Makefile44
-rw-r--r--src/spdk/test/unit/include/spdk/histogram_data.h/.gitignore1
-rw-r--r--src/spdk/test/unit/include/spdk/histogram_data.h/Makefile39
-rw-r--r--src/spdk/test/unit/include/spdk/histogram_data.h/histogram_ut.c135
-rw-r--r--src/spdk/test/unit/lib/Makefile47
-rw-r--r--src/spdk/test/unit/lib/bdev/Makefile50
-rw-r--r--src/spdk/test/unit/lib/bdev/bdev.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/bdev/bdev.c/Makefile39
-rw-r--r--src/spdk/test/unit/lib/bdev/bdev.c/bdev_ut.c1214
-rw-r--r--src/spdk/test/unit/lib/bdev/bdev_raid.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/bdev/bdev_raid.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/bdev/bdev_raid.c/bdev_raid_ut.c2236
-rw-r--r--src/spdk/test/unit/lib/bdev/crypto.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/bdev/crypto.c/Makefile41
-rw-r--r--src/spdk/test/unit/lib/bdev/crypto.c/crypto_ut.c908
-rw-r--r--src/spdk/test/unit/lib/bdev/crypto.c/rte_crypto.h95
-rw-r--r--src/spdk/test/unit/lib/bdev/crypto.c/rte_cryptodev.h153
-rw-r--r--src/spdk/test/unit/lib/bdev/crypto.c/rte_mbuf.h148
-rw-r--r--src/spdk/test/unit/lib/bdev/crypto.c/rte_mempool.h145
-rw-r--r--src/spdk/test/unit/lib/bdev/gpt/Makefile44
-rw-r--r--src/spdk/test/unit/lib/bdev/gpt/gpt.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/bdev/gpt/gpt.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/bdev/gpt/gpt.c/gpt_ut.c297
-rw-r--r--src/spdk/test/unit/lib/bdev/mt/Makefile44
-rw-r--r--src/spdk/test/unit/lib/bdev/mt/bdev.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/bdev/mt/bdev.c/Makefile41
-rw-r--r--src/spdk/test/unit/lib/bdev/mt/bdev.c/bdev_ut.c1360
-rw-r--r--src/spdk/test/unit/lib/bdev/part.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/bdev/part.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/bdev/part.c/part_ut.c179
-rw-r--r--src/spdk/test/unit/lib/bdev/pmem/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/bdev/pmem/Makefile40
-rw-r--r--src/spdk/test/unit/lib/bdev/pmem/bdev_pmem_ut.c783
-rw-r--r--src/spdk/test/unit/lib/bdev/scsi_nvme.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/bdev/scsi_nvme.c/Makefile39
-rw-r--r--src/spdk/test/unit/lib/bdev/scsi_nvme.c/scsi_nvme_ut.c142
-rw-r--r--src/spdk/test/unit/lib/bdev/vbdev_lvol.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/bdev/vbdev_lvol.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c1410
-rw-r--r--src/spdk/test/unit/lib/blob/Makefile44
-rw-r--r--src/spdk/test/unit/lib/blob/blob.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/blob/blob.c/Makefile41
-rw-r--r--src/spdk/test/unit/lib/blob/blob.c/blob_ut.c5914
-rw-r--r--src/spdk/test/unit/lib/blob/bs_dev_common.c225
-rw-r--r--src/spdk/test/unit/lib/blob/bs_scheduler.c87
-rw-r--r--src/spdk/test/unit/lib/blobfs/Makefile44
-rw-r--r--src/spdk/test/unit/lib/blobfs/blobfs_async_ut/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/blobfs/blobfs_async_ut/Makefile41
-rw-r--r--src/spdk/test/unit/lib/blobfs/blobfs_async_ut/blobfs_async_ut.c522
-rw-r--r--src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/Makefile41
-rw-r--r--src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/blobfs_sync_ut.c410
-rw-r--r--src/spdk/test/unit/lib/blobfs/tree.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/blobfs/tree.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/blobfs/tree.c/tree_ut.c159
-rw-r--r--src/spdk/test/unit/lib/event/Makefile44
-rw-r--r--src/spdk/test/unit/lib/event/app.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/event/app.c/Makefile42
-rw-r--r--src/spdk/test/unit/lib/event/app.c/app_ut.c195
-rw-r--r--src/spdk/test/unit/lib/event/subsystem.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/event/subsystem.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/event/subsystem.c/subsystem_ut.c304
-rw-r--r--src/spdk/test/unit/lib/ioat/Makefile44
-rw-r--r--src/spdk/test/unit/lib/ioat/ioat.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/ioat/ioat.c/Makefile39
-rw-r--r--src/spdk/test/unit/lib/ioat/ioat.c/ioat_ut.c153
-rw-r--r--src/spdk/test/unit/lib/iscsi/Makefile44
-rw-r--r--src/spdk/test/unit/lib/iscsi/common.c256
-rw-r--r--src/spdk/test/unit/lib/iscsi/conn.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/iscsi/conn.c/Makefile42
-rw-r--r--src/spdk/test/unit/lib/iscsi/conn.c/conn_ut.c404
-rw-r--r--src/spdk/test/unit/lib/iscsi/init_grp.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/iscsi/init_grp.c/Makefile41
-rw-r--r--src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp.conf31
-rw-r--r--src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp_ut.c702
-rw-r--r--src/spdk/test/unit/lib/iscsi/iscsi.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/iscsi/iscsi.c/Makefile48
-rw-r--r--src/spdk/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c972
-rw-r--r--src/spdk/test/unit/lib/iscsi/param.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/iscsi/param.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/iscsi/param.c/param_ut.c397
-rw-r--r--src/spdk/test/unit/lib/iscsi/portal_grp.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/iscsi/portal_grp.c/Makefile42
-rw-r--r--src/spdk/test/unit/lib/iscsi/portal_grp.c/portal_grp_ut.c477
-rw-r--r--src/spdk/test/unit/lib/iscsi/tgt_node.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/iscsi/tgt_node.c/Makefile41
-rw-r--r--src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node.conf95
-rw-r--r--src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node_ut.c886
-rw-r--r--src/spdk/test/unit/lib/json/Makefile44
-rw-r--r--src/spdk/test/unit/lib/json/json_parse.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/json/json_parse.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/json/json_parse.c/json_parse_ut.c940
-rw-r--r--src/spdk/test/unit/lib/json/json_util.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/json/json_util.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/json/json_util.c/json_util_ut.c963
-rw-r--r--src/spdk/test/unit/lib/json/json_write.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/json/json_write.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/json/json_write.c/json_write_ut.c745
-rw-r--r--src/spdk/test/unit/lib/json_mock.c81
-rw-r--r--src/spdk/test/unit/lib/jsonrpc/Makefile44
-rw-r--r--src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/Makefile39
-rw-r--r--src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/jsonrpc_server_ut.c423
-rw-r--r--src/spdk/test/unit/lib/log/Makefile44
-rw-r--r--src/spdk/test/unit/lib/log/log.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/log/log.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/log/log.c/log_ut.c113
-rw-r--r--src/spdk/test/unit/lib/lvol/Makefile44
-rw-r--r--src/spdk/test/unit/lib/lvol/lvol.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/lvol/lvol.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/lvol/lvol.c/lvol_ut.c2127
-rw-r--r--src/spdk/test/unit/lib/nvme/Makefile47
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme.c/nvme_ut.c1135
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/nvme_ctrlr_ut.c1795
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/nvme_ctrlr_cmd_ut.c645
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/nvme_ctrlr_ocssd_cmd_ut.c116
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ns.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ns.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ns.c/nvme_ns_ut.c163
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/nvme_ns_cmd_ut.c1440
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/nvme_ns_ocssd_cmd_ut.c677
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_pcie.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_pcie.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_pcie.c/nvme_pcie_ut.c861
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_qpair.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_qpair.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_qpair.c/nvme_qpair_ut.c418
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_quirks.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_quirks.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_quirks.c/nvme_quirks_ut.c102
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_rdma.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_rdma.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvme/nvme_rdma.c/nvme_rdma_ut.c298
-rw-r--r--src/spdk/test/unit/lib/nvmf/Makefile44
-rw-r--r--src/spdk/test/unit/lib/nvmf/ctrlr.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvmf/ctrlr.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvmf/ctrlr.c/ctrlr_ut.c797
-rw-r--r--src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/ctrlr_bdev_ut.c260
-rw-r--r--src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/ctrlr_discovery_ut.c306
-rw-r--r--src/spdk/test/unit/lib/nvmf/request.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvmf/request.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvmf/request.c/request_ut.c153
-rw-r--r--src/spdk/test/unit/lib/nvmf/subsystem.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/nvmf/subsystem.c/Makefile38
-rw-r--r--src/spdk/test/unit/lib/nvmf/subsystem.c/subsystem_ut.c477
-rw-r--r--src/spdk/test/unit/lib/scsi/Makefile44
-rw-r--r--src/spdk/test/unit/lib/scsi/dev.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/scsi/dev.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/scsi/dev.c/dev_ut.c681
-rw-r--r--src/spdk/test/unit/lib/scsi/lun.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/scsi/lun.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/scsi/lun.c/lun_ut.c654
-rw-r--r--src/spdk/test/unit/lib/scsi/scsi.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/scsi/scsi.c/Makefile41
-rw-r--r--src/spdk/test/unit/lib/scsi/scsi.c/scsi_ut.c80
-rw-r--r--src/spdk/test/unit/lib/scsi/scsi_bdev.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/scsi/scsi_bdev.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/scsi/scsi_bdev.c/scsi_bdev_ut.c988
-rw-r--r--src/spdk/test/unit/lib/sock/Makefile44
-rw-r--r--src/spdk/test/unit/lib/sock/sock.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/sock/sock.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/sock/sock.c/sock_ut.c643
-rw-r--r--src/spdk/test/unit/lib/thread/Makefile44
-rw-r--r--src/spdk/test/unit/lib/thread/thread.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/thread/thread.c/Makefile41
-rw-r--r--src/spdk/test/unit/lib/thread/thread.c/thread_ut.c501
-rw-r--r--src/spdk/test/unit/lib/util/Makefile44
-rw-r--r--src/spdk/test/unit/lib/util/base64.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/util/base64.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/util/base64.c/base64_ut.c268
-rw-r--r--src/spdk/test/unit/lib/util/bit_array.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/util/bit_array.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/util/bit_array.c/bit_array_ut.c327
-rw-r--r--src/spdk/test/unit/lib/util/cpuset.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/util/cpuset.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/util/cpuset.c/cpuset_ut.c265
-rw-r--r--src/spdk/test/unit/lib/util/crc16.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/util/crc16.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/util/crc16.c/crc16_ut.c80
-rw-r--r--src/spdk/test/unit/lib/util/crc32_ieee.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/util/crc32_ieee.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/util/crc32_ieee.c/crc32_ieee_ut.c83
-rw-r--r--src/spdk/test/unit/lib/util/crc32c.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/util/crc32c.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/util/crc32c.c/crc32c_ut.c154
-rw-r--r--src/spdk/test/unit/lib/util/string.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/util/string.c/Makefile40
-rw-r--r--src/spdk/test/unit/lib/util/string.c/string_ut.c237
-rw-r--r--src/spdk/test/unit/lib/vhost/Makefile44
-rw-r--r--src/spdk/test/unit/lib/vhost/test_vhost.c121
-rw-r--r--src/spdk/test/unit/lib/vhost/vhost.c/.gitignore1
-rw-r--r--src/spdk/test/unit/lib/vhost/vhost.c/Makefile42
-rw-r--r--src/spdk/test/unit/lib/vhost/vhost.c/vhost_ut.c364
-rwxr-xr-xsrc/spdk/test/unit/unittest.sh173
-rw-r--r--src/spdk/test/vhost/common/autotest.config38
-rw-r--r--src/spdk/test/vhost/common/common.sh1109
-rw-r--r--src/spdk/test/vhost/common/fio_jobs/default_initiator.job9
-rw-r--r--src/spdk/test/vhost/common/fio_jobs/default_integrity.job19
-rw-r--r--src/spdk/test/vhost/common/fio_jobs/default_integrity_nightly.job23
-rw-r--r--src/spdk/test/vhost/common/fio_jobs/default_performance.job16
-rwxr-xr-xsrc/spdk/test/vhost/common/run_fio.py168
-rwxr-xr-xsrc/spdk/test/vhost/common/run_vhost.sh51
-rwxr-xr-xsrc/spdk/test/vhost/common/vm_run.sh48
-rwxr-xr-xsrc/spdk/test/vhost/common/vm_setup.sh78
-rwxr-xr-xsrc/spdk/test/vhost/common/vm_shutdown.sh66
-rwxr-xr-xsrc/spdk/test/vhost/common/vm_ssh.sh58
-rwxr-xr-xsrc/spdk/test/vhost/fiotest/autotest.sh247
-rw-r--r--src/spdk/test/vhost/fiotest/conf.json80
-rw-r--r--src/spdk/test/vhost/hotplug/blk_hotremove.sh236
-rw-r--r--src/spdk/test/vhost/hotplug/common.sh230
-rw-r--r--src/spdk/test/vhost/hotplug/fio_jobs/default_integrity.job16
-rwxr-xr-xsrc/spdk/test/vhost/hotplug/scsi_hotattach.sh104
-rwxr-xr-xsrc/spdk/test/vhost/hotplug/scsi_hotdetach.sh241
-rwxr-xr-xsrc/spdk/test/vhost/hotplug/scsi_hotplug.sh90
-rw-r--r--src/spdk/test/vhost/hotplug/scsi_hotremove.sh232
-rw-r--r--src/spdk/test/vhost/hotplug/test_plan.md86
-rw-r--r--src/spdk/test/vhost/hotplug/vhost.conf.base4
-rw-r--r--src/spdk/test/vhost/initiator/autotest.config5
-rw-r--r--src/spdk/test/vhost/initiator/bdev.conf21
-rw-r--r--src/spdk/test/vhost/initiator/bdev.fio51
-rw-r--r--src/spdk/test/vhost/initiator/bdev_pci.conf2
-rwxr-xr-xsrc/spdk/test/vhost/initiator/blockdev.sh200
-rwxr-xr-xsrc/spdk/test/vhost/initiator/json_config.sh64
-rwxr-xr-xsrc/spdk/test/vhost/integrity/integrity_start.sh97
-rwxr-xr-xsrc/spdk/test/vhost/integrity/integrity_vm.sh69
-rwxr-xr-xsrc/spdk/test/vhost/json_config/json_config.sh25
-rw-r--r--src/spdk/test/vhost/lvol/autotest.config74
-rwxr-xr-xsrc/spdk/test/vhost/lvol/lvol_test.sh286
-rw-r--r--src/spdk/test/vhost/migration/autotest.config14
-rw-r--r--src/spdk/test/vhost/migration/migration-tc1.job25
-rw-r--r--src/spdk/test/vhost/migration/migration-tc1.sh123
-rw-r--r--src/spdk/test/vhost/migration/migration-tc2.job20
-rw-r--r--src/spdk/test/vhost/migration/migration-tc2.sh209
-rw-r--r--src/spdk/test/vhost/migration/migration-tc3.job20
-rw-r--r--src/spdk/test/vhost/migration/migration-tc3a.sh227
-rw-r--r--src/spdk/test/vhost/migration/migration-tc3b.sh79
-rwxr-xr-xsrc/spdk/test/vhost/migration/migration.sh153
-rw-r--r--src/spdk/test/vhost/other/conf.json43
-rwxr-xr-xsrc/spdk/test/vhost/other/negative.sh144
-rwxr-xr-xsrc/spdk/test/vhost/perf_bench/vhost_perf.sh229
-rwxr-xr-xsrc/spdk/test/vhost/readonly/delete_partition_vm.sh42
-rwxr-xr-xsrc/spdk/test/vhost/readonly/disabled_readonly_vm.sh47
-rwxr-xr-xsrc/spdk/test/vhost/readonly/enabled_readonly_vm.sh76
-rwxr-xr-xsrc/spdk/test/vhost/readonly/readonly.sh132
-rw-r--r--src/spdk/test/vhost/readonly/test_plan.md30
-rwxr-xr-xsrc/spdk/test/vhost/spdk_vhost.sh210
-rw-r--r--src/spdk/test/vhost/test_plan.md252
-rwxr-xr-xsrc/spdk/test/vhost/vhost_boot/vhost_boot.sh111
456 files changed, 76741 insertions, 0 deletions
diff --git a/src/spdk/test/Makefile b/src/spdk/test/Makefile
new file mode 100644
index 00000000..9fcd0801
--- /dev/null
+++ b/src/spdk/test/Makefile
@@ -0,0 +1,47 @@
+#
+# 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 unit rpc_client
+
+DIRS-y = $(TESTDIRS)
+
+.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 00000000..e2ba6d0f
--- /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 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 00000000..77ddb987
--- /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 00000000..43ec095a
--- /dev/null
+++ b/src/spdk/test/app/bdev_svc/Makefile
@@ -0,0 +1,65 @@
+#
+# 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.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk
+
+APP = bdev_svc
+
+C_SRCS := bdev_svc.c
+
+SPDK_LIB_LIST = event_bdev event_copy
+SPDK_LIB_LIST += nvmf event log trace conf thread util bdev copy rpc jsonrpc json
+SPDK_LIB_LIST += app_rpc log_rpc bdev_rpc
+
+ifeq ($(OS),Linux)
+SPDK_LIB_LIST += event_nbd nbd
+endif
+
+LIBS += $(BLOCKDEV_MODULES_LINKER_ARGS) \
+ $(COPY_MODULES_LINKER_ARGS) \
+ $(SOCK_MODULES_LINKER_ARGS) \
+ $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS)
+
+all : $(APP)
+ @:
+
+$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(SPDK_WHOLE_LIBS) $(COPY_MODULES_FILES) $(BLOCKDEV_MODULES_FILES) $(SOCK_MODULES_FILES) $(LINKER_MODULES) $(ENV_LIBS)
+ $(LINK_C)
+
+clean :
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.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 00000000..2db96d6b
--- /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 void
+bdev_svc_parse_arg(int ch, char *arg)
+{
+}
+
+static void
+bdev_svc_start(void *arg1, void *arg2)
+{
+ 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;
+ opts.max_delay_us = 1000 * 1000;
+
+ 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, NULL);
+
+ spdk_app_fini();
+
+ return rc;
+}
diff --git a/src/spdk/test/app/histogram_perf/.gitignore b/src/spdk/test/app/histogram_perf/.gitignore
new file mode 100644
index 00000000..c77b0531
--- /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 00000000..7c6ecd89
--- /dev/null
+++ b/src/spdk/test/app/histogram_perf/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.app.mk
+
+APP = histogram_perf
+
+C_SRCS = histogram_perf.c
+
+SPDK_LIB_LIST = thread util log
+
+LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS)
+
+all: $(APP)
+ @:
+
+$(APP): $(OBJS) $(SPDK_LIB_FILES)
+ $(LINK_C)
+
+clean:
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.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 00000000..5d9de527
--- /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 00000000..3e6db4f0
--- /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 00000000..2bbba95b
--- /dev/null
+++ b/src/spdk/test/app/jsoncat/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.app.mk
+
+APP = jsoncat
+
+C_SRCS = jsoncat.c
+
+SPDK_LIB_LIST = json thread util log
+
+LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS)
+
+all: $(APP)
+ @:
+
+$(APP): $(OBJS) $(SPDK_LIB_FILES)
+ $(LINK_C)
+
+clean:
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk
diff --git a/src/spdk/test/app/jsoncat/jsoncat.c b/src/spdk/test/app/jsoncat/jsoncat.c
new file mode 100644
index 00000000..9984e32b
--- /dev/null
+++ b/src/spdk/test/app/jsoncat/jsoncat.c
@@ -0,0 +1,229 @@
+/*-
+ * 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"
+
+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 void *
+read_file(FILE *f, size_t *psize)
+{
+ void *buf, *newbuf;
+ size_t cur_size, buf_size, rc;
+
+ buf = NULL;
+ cur_size = 0;
+ buf_size = 128 * 1024;
+
+ while (buf_size <= 1024 * 1024 * 1024) {
+ newbuf = realloc(buf, buf_size);
+ if (newbuf == NULL) {
+ free(buf);
+ return NULL;
+ }
+ buf = newbuf;
+
+ rc = fread(buf + cur_size, 1, buf_size - cur_size, f);
+ cur_size += rc;
+
+ if (feof(f)) {
+ *psize = cur_size;
+ return buf;
+ }
+
+ if (ferror(f)) {
+ free(buf);
+ return NULL;
+ }
+
+ buf_size *= 2;
+ }
+
+ free(buf);
+ return NULL;
+}
+
+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 = read_file(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 00000000..7c1cdc45
--- /dev/null
+++ b/src/spdk/test/app/match/match
@@ -0,0 +1,326 @@
+#!/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) {
+ chop($i_line);
+ if (index($line_in, $i_line) != -1) {
+ $ignore_it = 1;
+ if ($opt_v) {
+ print "Ignoring (from $file): $line_in";
+ }
+ }
+ }
+ if ($ignore_it == 0) {
+ $output .= $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 00000000..39802f64
--- /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 00000000..9cca6b2c
--- /dev/null
+++ b/src/spdk/test/app/stub/Makefile
@@ -0,0 +1,58 @@
+#
+# 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.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk
+
+APP = stub
+
+C_SRCS := stub.c
+
+SPDK_LIB_LIST = event conf nvme log trace rpc jsonrpc json thread util
+
+LIBS += $(SOCK_MODULES_LINKER_ARGS)
+LIBS += $(SPDK_LIB_LINKER_ARGS)
+LIBS += $(ENV_LINKER_ARGS)
+
+all : $(APP)
+ @:
+
+$(APP) : $(OBJS) $(SOCK_MODULES_FILES) $(SPDK_LIB_FILES) $(ENV_LIBS)
+ $(LINK_C)
+
+clean :
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk
diff --git a/src/spdk/test/app/stub/stub.c b/src/spdk/test/app/stub/stub.c
new file mode 100644
index 00000000..c52917dd
--- /dev/null
+++ b/src/spdk/test/app/stub/stub.c
@@ -0,0 +1,147 @@
+/*-
+ * 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"
+
+static char g_path[256];
+
+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)
+{
+}
+
+static void
+stub_start(void *arg1, void *arg2)
+{
+ 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);
+ }
+}
+
+static void
+stub_shutdown(void)
+{
+ unlink(g_path);
+ spdk_app_stop(0);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+ struct spdk_app_opts opts = {};
+
+ /* 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) {
+ switch (ch) {
+ case 'i':
+ opts.shm_id = atoi(optarg);
+ break;
+ case 'm':
+ opts.reactor_mask = optarg;
+ break;
+ case 'n':
+ opts.mem_channel = atoi(optarg);
+ break;
+ case 'p':
+ opts.master_core = atoi(optarg);
+ break;
+ case 's':
+ opts.mem_size = atoi(optarg);
+ 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;
+ opts.max_delay_us = 1000 * 1000;
+
+ ch = spdk_app_start(&opts, stub_start, (void *)(intptr_t)opts.shm_id, NULL);
+ spdk_app_fini();
+
+ return ch;
+}
diff --git a/src/spdk/test/bdev/Makefile b/src/spdk/test/bdev/Makefile
new file mode 100644
index 00000000..cb15bd49
--- /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.conf.in b/src/spdk/test/bdev/bdev.conf.in
new file mode 100644
index 00000000..0439ab5e
--- /dev/null
+++ b/src/spdk/test/bdev/bdev.conf.in
@@ -0,0 +1,44 @@
+[Passthru]
+ # PT <bdev name> <vbdev name>
+ PT Malloc3 TestPT
+
+[Malloc]
+ NumberOfLuns 7
+ LunSizeInMB 32
+
+[Split]
+ # Split Malloc1 into two auto-sized halves
+ Split Malloc1 2
+
+ # Split Malloc2 into eight 4-megabyte pieces,
+ # leaving the rest of the device inaccessible
+ Split Malloc2 8 4
+
+[AIO]
+ AIO /dev/ram0 AIO0
+ AIO /tmp/aiofile AIO1 2048
+
+[QoS]
+ # QoS section defines limitation on performance
+ # metric like IOPS and bandwidth
+ #
+ # Format: Limit_IOPS Bdev_Name IOPS_Limit_Value
+ #
+ # IOPS limit must be 10000 or greater and be multiple
+ # of 10000
+ #
+ # Assign 20000 IOPS for the Malloc0 block device
+ Limit_IOPS Malloc0 20000
+ #
+ # Bandwidth limit must be 10 (MB) or greater and be
+ # multiple of 10
+ # Assign 100 (MB) bandwidth for the Malloc3 block
+ # device
+ Limit_BPS Malloc3 100
+
+[RAID0]
+ Name raid0
+ StripSize 64
+ NumDevices 2
+ RaidLevel 0
+ Devices Malloc4 Malloc5
diff --git a/src/spdk/test/bdev/bdevio/.gitignore b/src/spdk/test/bdev/bdevio/.gitignore
new file mode 100644
index 00000000..1bb55429
--- /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 00000000..d973846f
--- /dev/null
+++ b/src/spdk/test/bdev/bdevio/Makefile
@@ -0,0 +1,61 @@
+#
+# 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.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk
+
+APP = bdevio
+
+C_SRCS := bdevio.c
+
+SPDK_LIB_LIST = event_bdev event_copy
+SPDK_LIB_LIST += bdev copy event trace log conf thread util rpc jsonrpc json
+
+LIBS += $(BLOCKDEV_MODULES_LINKER_ARGS) \
+ $(COPY_MODULES_LINKER_ARGS) \
+ $(SOCK_MODULES_LINKER_ARGS)
+
+LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) -lcunit
+
+all : $(APP)
+ @:
+
+$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(COPY_MODULES_FILES) $(BLOCKDEV_MODULES_FILES) $(SOCK_MODULES_FILES) $(LINKER_MODULES) $(ENV_LIBS)
+ $(LINK_C)
+
+clean :
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk
diff --git a/src/spdk/test/bdev/bdevio/bdevio.c b/src/spdk/test/bdev/bdevio/bdevio.c
new file mode 100644
index 00000000..c139b6f2
--- /dev/null
+++ b/src/spdk/test/bdev/bdevio/bdevio.c
@@ -0,0 +1,973 @@
+/*-
+ * 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/copy_engine.h"
+#include "spdk/env.h"
+#include "spdk/log.h"
+#include "spdk/thread.h"
+#include "spdk/event.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 uint32_t g_lcore_id_init;
+static uint32_t g_lcore_id_ut;
+static uint32_t g_lcore_id_io;
+
+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;
+ int data_len;
+ uint64_t offset;
+ struct iovec iov[BUFFER_IOVS];
+ int iovcnt;
+ struct io_target *target;
+};
+
+struct io_target *g_io_targets = NULL;
+
+static void
+execute_spdk_function(spdk_event_fn fn, void *arg1, void *arg2)
+{
+ struct spdk_event *event;
+
+ event = spdk_event_allocate(g_lcore_id_io, fn, arg1, arg2);
+ pthread_mutex_lock(&g_test_mutex);
+ spdk_event_call(event);
+ 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 *arg1, void *arg2)
+{
+ struct io_target *target = arg1;
+
+ target->ch = spdk_bdev_get_io_channel(target->bdev_desc);
+ assert(target->ch);
+ wake_ut_thread();
+}
+
+static int
+bdevio_construct_targets(void)
+{
+ struct spdk_bdev *bdev;
+ struct io_target *target;
+ int rc;
+
+ printf("I/O targets:\n");
+
+ bdev = spdk_bdev_first_leaf();
+ while (bdev != NULL) {
+ 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);
+ bdev = spdk_bdev_next_leaf(bdev);
+ continue;
+ }
+
+ 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, NULL);
+ g_io_targets = target;
+
+ bdev = spdk_bdev_next_leaf(bdev);
+ }
+
+ return 0;
+}
+
+static void
+__put_io_channel(void *arg1, void *arg2)
+{
+ struct io_target *target = arg1;
+
+ 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, NULL);
+ 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_dma_zmalloc(size, 0x1000, NULL);
+ 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 *arg1, void *arg2)
+{
+ struct bdevio_request *req = arg1;
+ 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 *arg1, void *arg2)
+{
+ struct bdevio_request *req = arg1;
+ 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
+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
+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, NULL);
+}
+
+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, NULL);
+}
+
+static void
+__blockdev_read(void *arg1, void *arg2)
+{
+ struct bdevio_request *req = arg1;
+ 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, NULL);
+}
+
+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_dma_free(rx_buf);
+ spdk_dma_free(tx_buf);
+
+ return rc;
+}
+
+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_io_targets;
+ while (target != NULL) {
+ if (data_length < spdk_bdev_get_block_size(target->bdev) ||
+ data_length / spdk_bdev_get_block_size(target->bdev) > spdk_bdev_get_num_blocks(target->bdev)) {
+ target = target->next;
+ continue;
+ }
+
+ 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);
+ }
+
+ target = target->next;
+ }
+}
+
+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_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_io_targets;
+ while (target != NULL) {
+ 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);
+
+ target = target->next;
+ }
+}
+
+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_io_targets;
+ while (target != NULL) {
+ 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);
+
+ target = target->next;
+ }
+}
+
+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 *arg1, void *arg2)
+{
+ struct bdevio_request *req = arg1;
+ 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_reset(struct io_target *target)
+{
+ struct bdevio_request req;
+
+ req.target = target;
+
+ g_completion_success = false;
+
+ execute_spdk_function(__blockdev_reset, &req, NULL);
+}
+
+static void
+blockdev_test_reset(void)
+{
+ struct io_target *target;
+
+ target = g_io_targets;
+ while (target != NULL) {
+ blockdev_reset(target);
+ CU_ASSERT_EQUAL(g_completion_success, true);
+
+ target = target->next;
+ }
+}
+
+static void
+__stop_init_thread(void *arg1, void *arg2)
+{
+ unsigned num_failures = (unsigned)(uintptr_t)arg1;
+
+ bdevio_cleanup_targets();
+ spdk_app_stop(num_failures);
+}
+
+static void
+stop_init_thread(unsigned num_failures)
+{
+ struct spdk_event *event;
+
+ event = spdk_event_allocate(g_lcore_id_init, __stop_init_thread,
+ (void *)(uintptr_t)num_failures, NULL);
+ spdk_event_call(event);
+}
+
+static void
+__run_ut_thread(void *arg1, void *arg2)
+{
+ CU_pSuite suite = NULL;
+ unsigned num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ stop_init_thread(CU_get_error());
+ return;
+ }
+
+ suite = CU_add_suite("components_suite", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ stop_init_thread(CU_get_error());
+ return;
+ }
+
+ 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 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 reset",
+ blockdev_test_reset) == NULL
+ ) {
+ CU_cleanup_registry();
+ stop_init_thread(CU_get_error());
+ return;
+ }
+
+ 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);
+}
+
+static void
+test_main(void *arg1, void *arg2)
+{
+ struct spdk_event *event;
+
+ pthread_mutex_init(&g_test_mutex, NULL);
+ pthread_cond_init(&g_test_cond, NULL);
+
+ g_lcore_id_init = spdk_env_get_first_core();
+ g_lcore_id_ut = spdk_env_get_next_core(g_lcore_id_init);
+ g_lcore_id_io = spdk_env_get_next_core(g_lcore_id_ut);
+
+ if (g_lcore_id_init == SPDK_ENV_LCORE_ID_ANY ||
+ g_lcore_id_ut == SPDK_ENV_LCORE_ID_ANY ||
+ g_lcore_id_io == SPDK_ENV_LCORE_ID_ANY) {
+ SPDK_ERRLOG("Could not reserve 3 separate threads.\n");
+ spdk_app_stop(-1);
+ }
+
+ if (bdevio_construct_targets() < 0) {
+ spdk_app_stop(-1);
+ return;
+ }
+
+ event = spdk_event_allocate(g_lcore_id_ut, __run_ut_thread, NULL, NULL);
+ spdk_event_call(event);
+}
+
+static void
+bdevio_usage(void)
+{
+}
+
+static void
+bdevio_parse_arg(int ch, char *arg)
+{
+}
+
+int
+main(int argc, char **argv)
+{
+ int rc;
+ struct spdk_app_opts opts = {};
+
+ spdk_app_opts_init(&opts);
+ opts.name = "bdevtest";
+ opts.rpc_addr = NULL;
+ opts.reactor_mask = "0x7";
+
+ if ((rc = spdk_app_parse_args(argc, argv, &opts, "", NULL,
+ bdevio_parse_arg, bdevio_usage)) !=
+ SPDK_APP_PARSE_ARGS_SUCCESS) {
+ return rc;
+ }
+
+ rc = spdk_app_start(&opts, test_main, NULL, NULL);
+ spdk_app_fini();
+
+ return rc;
+}
diff --git a/src/spdk/test/bdev/bdevjson/json_config.sh b/src/spdk/test/bdev/bdevjson/json_config.sh
new file mode 100755
index 00000000..3e5d276e
--- /dev/null
+++ b/src/spdk/test/bdev/bdevjson/json_config.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+set -ex
+BDEV_JSON_DIR=$(readlink -f $(dirname $0))
+. $BDEV_JSON_DIR/../../json_config/common.sh
+
+function test_subsystems() {
+ run_spdk_tgt
+ rootdir=$(readlink -f $BDEV_JSON_DIR/../../..)
+
+ rpc_py="$spdk_rpc_py"
+ clear_config_py="$spdk_clear_config_py"
+ load_nvme
+ create_bdev_subsystem_config
+ test_json_config
+
+ clear_bdev_subsystem_config
+ test_global_params "spdk_tgt"
+ kill_targets
+}
+
+timing_enter json_config
+trap 'on_error_exit "${FUNCNAME}" "${LINENO}"' ERR
+
+test_subsystems
+
+timing_exit json_config
+report_test_completion json_config
diff --git a/src/spdk/test/bdev/bdevjson/rbd_json_config.sh b/src/spdk/test/bdev/bdevjson/rbd_json_config.sh
new file mode 100755
index 00000000..458ecb74
--- /dev/null
+++ b/src/spdk/test/bdev/bdevjson/rbd_json_config.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+set -ex
+VHOST_JSON_DIR=$(readlink -f $(dirname $0))
+. $VHOST_JSON_DIR/../../json_config/common.sh
+
+function test_subsystems() {
+ run_spdk_tgt
+ rootdir=$(readlink -f $VHOST_JSON_DIR/../../..)
+
+ rpc_py="$spdk_rpc_py"
+ clear_config_py="$spdk_clear_config_py"
+ $rpc_py start_subsystem_init
+
+ create_rbd_bdev_subsystem_config
+ test_json_config
+ clear_rbd_bdev_subsystem_config
+
+ kill_targets
+}
+
+trap 'rbd_cleanup; on_error_exit "${FUNCNAME}" "${LINENO}"' ERR
+timing_enter rbd_json_config
+
+test_subsystems
+timing_exit rbd_json_config
+report_test_completion rbd_json_config
diff --git a/src/spdk/test/bdev/bdevperf/.gitignore b/src/spdk/test/bdev/bdevperf/.gitignore
new file mode 100644
index 00000000..e14ddd84
--- /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 00000000..eb5f76ae
--- /dev/null
+++ b/src/spdk/test/bdev/bdevperf/Makefile
@@ -0,0 +1,61 @@
+#
+# 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.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk
+
+APP = bdevperf
+
+C_SRCS := bdevperf.c
+
+SPDK_LIB_LIST = event_bdev event_copy
+SPDK_LIB_LIST += bdev copy event trace log conf thread util rpc jsonrpc json
+
+LIBS += $(BLOCKDEV_MODULES_LINKER_ARGS) \
+ $(COPY_MODULES_LINKER_ARGS) \
+ $(SOCK_MODULES_LINKER_ARGS)
+
+LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS)
+
+all : $(APP)
+ @:
+
+$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(BLOCKDEV_MODULES_FILES) $(COPY_MODULES_FILES) $(SOCK_MODULES_FILES) $(ENV_LIBS)
+ $(LINK_C)
+
+clean :
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk
diff --git a/src/spdk/test/bdev/bdevperf/bdevperf.c b/src/spdk/test/bdev/bdevperf/bdevperf.c
new file mode 100644
index 00000000..1416ea27
--- /dev/null
+++ b/src/spdk/test/bdev/bdevperf/bdevperf.c
@@ -0,0 +1,1035 @@
+/*-
+ * 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/copy_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"
+
+struct bdevperf_task {
+ struct iovec iov;
+ struct io_target *target;
+ void *buf;
+ uint64_t offset_blocks;
+ 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;
+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 int g_is_random;
+static bool g_verify = false;
+static bool g_reset = false;
+static bool g_unmap = false;
+static bool g_write_zeroes = false;
+static bool g_flush = false;
+static int g_queue_depth;
+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 bool g_run_failed = false;
+static bool g_shutdown = false;
+static uint64_t g_shutdown_tsc;
+static bool g_zcopy = true;
+static unsigned g_master_core;
+static int g_time_in_sec;
+static bool g_mix_specified;
+
+static struct spdk_poller *g_perf_timer = NULL;
+
+static void bdevperf_submit_single(struct io_target *target, struct bdevperf_task *task);
+
+struct io_target {
+ char *name;
+ struct spdk_bdev *bdev;
+ struct spdk_bdev_desc *bdev_desc;
+ struct spdk_io_channel *ch;
+ struct io_target *next;
+ unsigned lcore;
+ uint64_t io_completed;
+ uint64_t prev_io_completed;
+ double ema_io_per_second;
+ int current_queue_depth;
+ uint64_t size_in_ios;
+ uint64_t offset_in_ios;
+ uint64_t io_size_blocks;
+ bool is_draining;
+ struct spdk_poller *run_timer;
+ struct spdk_poller *reset_timer;
+ TAILQ_HEAD(, bdevperf_task) task_list;
+};
+
+struct io_target **g_head;
+uint32_t *coremap;
+static int g_target_count = 0;
+
+/*
+ * Used to determine how the I/O buffers should be aligned.
+ * This alignment will be bumped up for blockdevs that
+ * require alignment based on block length - for example,
+ * AIO blockdevs.
+ */
+static size_t g_min_alignment = 8;
+
+static int
+blockdev_heads_init(void)
+{
+ uint32_t i, idx = 0;
+ uint32_t core_count = spdk_env_get_core_count();
+
+ g_head = calloc(core_count, sizeof(struct io_target *));
+ if (!g_head) {
+ fprintf(stderr, "Cannot allocate g_head array with size=%u\n",
+ core_count);
+ return -1;
+ }
+
+ coremap = calloc(core_count, sizeof(uint32_t));
+ if (!coremap) {
+ free(g_head);
+ fprintf(stderr, "Cannot allocate coremap array with size=%u\n",
+ core_count);
+ return -1;
+ }
+
+ SPDK_ENV_FOREACH_CORE(i) {
+ coremap[idx++] = i;
+ }
+
+ return 0;
+}
+
+static void
+bdevperf_free_target(struct io_target *target)
+{
+ struct bdevperf_task *task, *tmp;
+
+ TAILQ_FOREACH_SAFE(task, &target->task_list, link, tmp) {
+ TAILQ_REMOVE(&target->task_list, task, link);
+ spdk_dma_free(task->buf);
+ free(task);
+ }
+
+ free(target->name);
+ free(target);
+}
+
+static void
+blockdev_heads_destroy(void)
+{
+ uint32_t i, core_count;
+ struct io_target *target, *next_target;
+
+ if (!g_head) {
+ return;
+ }
+
+ core_count = spdk_env_get_core_count();
+ for (i = 0; i < core_count; i++) {
+ target = g_head[i];
+ while (target != NULL) {
+ next_target = target->next;
+ bdevperf_free_target(target);
+ target = next_target;
+ }
+ }
+
+ free(g_head);
+ free(coremap);
+}
+
+static void
+bdevperf_construct_targets(void)
+{
+ int index = 0;
+ struct spdk_bdev *bdev;
+ struct io_target *target;
+ size_t align;
+ int rc;
+
+ bdev = spdk_bdev_first_leaf();
+ while (bdev != NULL) {
+
+ if (g_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));
+ bdev = spdk_bdev_next_leaf(bdev);
+ continue;
+ }
+
+ target = malloc(sizeof(struct io_target));
+ if (!target) {
+ fprintf(stderr, "Unable to allocate memory for new target.\n");
+ /* Return immediately because all mallocs will presumably fail after this */
+ return;
+ }
+
+ target->name = strdup(spdk_bdev_get_name(bdev));
+ if (!target->name) {
+ fprintf(stderr, "Unable to allocate memory for target name.\n");
+ free(target);
+ /* Return immediately because all mallocs will presumably fail after this */
+ return;
+ }
+
+ rc = spdk_bdev_open(bdev, true, NULL, NULL, &target->bdev_desc);
+ if (rc != 0) {
+ SPDK_ERRLOG("Could not open leaf bdev %s, error=%d\n", spdk_bdev_get_name(bdev), rc);
+ free(target->name);
+ free(target);
+ bdev = spdk_bdev_next_leaf(bdev);
+ continue;
+ }
+
+ target->bdev = bdev;
+ /* Mapping each target to lcore */
+ index = g_target_count % spdk_env_get_core_count();
+ target->next = g_head[index];
+ target->lcore = coremap[index];
+ target->io_completed = 0;
+ target->current_queue_depth = 0;
+ target->offset_in_ios = 0;
+ target->io_size_blocks = g_io_size / spdk_bdev_get_block_size(bdev);
+ if (target->io_size_blocks == 0 ||
+ (g_io_size % spdk_bdev_get_block_size(bdev)) != 0) {
+ SPDK_ERRLOG("IO size (%d) is bigger than blocksize of bdev %s (%"PRIu32") or not a blocksize multiple\n",
+ g_io_size, spdk_bdev_get_name(bdev), spdk_bdev_get_block_size(bdev));
+ spdk_bdev_close(target->bdev_desc);
+ free(target->name);
+ free(target);
+ bdev = spdk_bdev_next_leaf(bdev);
+ continue;
+ }
+
+ target->size_in_ios = spdk_bdev_get_num_blocks(bdev) / target->io_size_blocks;
+ align = spdk_bdev_get_buf_align(bdev);
+ /*
+ * TODO: This should actually use the LCM of align and g_min_alignment, but
+ * it is fairly safe to assume all alignments are powers of two for now.
+ */
+ g_min_alignment = spdk_max(g_min_alignment, align);
+
+ target->is_draining = false;
+ target->run_timer = NULL;
+ target->reset_timer = NULL;
+ TAILQ_INIT(&target->task_list);
+
+ g_head[index] = target;
+ g_target_count++;
+
+ bdev = spdk_bdev_next_leaf(bdev);
+ }
+}
+
+static void
+end_run(void *arg1, void *arg2)
+{
+ struct io_target *target = arg1;
+
+ spdk_put_io_channel(target->ch);
+ spdk_bdev_close(target->bdev_desc);
+ if (--g_target_count == 0) {
+ if (g_show_performance_real_time) {
+ spdk_poller_unregister(&g_perf_timer);
+ }
+ if (g_run_failed) {
+ spdk_app_stop(1);
+ } else {
+ spdk_app_stop(0);
+ }
+ }
+}
+
+static void
+bdevperf_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
+{
+ struct io_target *target;
+ struct bdevperf_task *task = cb_arg;
+ struct spdk_event *complete;
+ struct iovec *iovs;
+ int iovcnt;
+
+ target = task->target;
+
+ if (!success) {
+ if (!g_reset) {
+ target->is_draining = true;
+ g_run_failed = true;
+ printf("task offset: %lu on target bdev=%s fails\n",
+ task->offset_blocks, target->name);
+ }
+ } else if (g_verify || g_reset) {
+ spdk_bdev_io_get_iovec(bdev_io, &iovs, &iovcnt);
+ assert(iovcnt == 1);
+ assert(iovs != NULL);
+ if (memcmp(task->buf, iovs[0].iov_base, g_io_size) != 0) {
+ printf("Buffer mismatch! Disk Offset: %lu\n", task->offset_blocks);
+ target->is_draining = true;
+ g_run_failed = true;
+ }
+ }
+
+ target->current_queue_depth--;
+
+ if (success) {
+ target->io_completed++;
+ }
+
+ 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 (!target->is_draining) {
+ bdevperf_submit_single(target, task);
+ } else {
+ TAILQ_INSERT_TAIL(&target->task_list, task, link);
+ if (target->current_queue_depth == 0) {
+ complete = spdk_event_allocate(g_master_core, end_run, target, NULL);
+ spdk_event_call(complete);
+ }
+ }
+}
+
+static void
+bdevperf_verify_submit_read(void *cb_arg)
+{
+ struct io_target *target;
+ struct bdevperf_task *task = cb_arg;
+ int rc;
+
+ target = task->target;
+
+ /* Read the data back in */
+ rc = spdk_bdev_read_blocks(target->bdev_desc, target->ch, NULL, task->offset_blocks,
+ target->io_size_blocks, bdevperf_complete, task);
+ if (rc == -ENOMEM) {
+ task->bdev_io_wait.bdev = target->bdev;
+ task->bdev_io_wait.cb_fn = bdevperf_verify_submit_read;
+ task->bdev_io_wait.cb_arg = task;
+ spdk_bdev_queue_io_wait(target->bdev, target->ch, &task->bdev_io_wait);
+ } else if (rc != 0) {
+ printf("Failed to submit read: %d\n", rc);
+ target->is_draining = true;
+ g_run_failed = true;
+ }
+}
+
+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 __thread unsigned int seed = 0;
+
+static void
+bdevperf_prep_task(struct bdevperf_task *task)
+{
+ struct io_target *target = task->target;
+ uint64_t offset_in_ios;
+
+ if (g_is_random) {
+ offset_in_ios = rand_r(&seed) % target->size_in_ios;
+ } else {
+ offset_in_ios = target->offset_in_ios++;
+ if (target->offset_in_ios == target->size_in_ios) {
+ target->offset_in_ios = 0;
+ }
+ }
+
+ task->offset_blocks = offset_in_ios * target->io_size_blocks;
+ if (g_verify || g_reset) {
+ memset(task->buf, rand_r(&seed) % 256, g_io_size);
+ task->iov.iov_base = task->buf;
+ task->iov.iov_len = g_io_size;
+ task->io_type = SPDK_BDEV_IO_TYPE_WRITE;
+ } else if (g_flush) {
+ task->io_type = SPDK_BDEV_IO_TYPE_FLUSH;
+ } else if (g_unmap) {
+ task->io_type = SPDK_BDEV_IO_TYPE_UNMAP;
+ } else if (g_write_zeroes) {
+ task->io_type = SPDK_BDEV_IO_TYPE_WRITE_ZEROES;
+ } else if ((g_rw_percentage == 100) ||
+ (g_rw_percentage != 0 && ((rand_r(&seed) % 100) < g_rw_percentage))) {
+ task->io_type = SPDK_BDEV_IO_TYPE_READ;
+ } else {
+ task->iov.iov_base = task->buf;
+ task->iov.iov_len = g_io_size;
+ task->io_type = SPDK_BDEV_IO_TYPE_WRITE;
+ }
+}
+
+static void
+bdevperf_submit_task(void *arg)
+{
+ struct bdevperf_task *task = arg;
+ struct io_target *target = task->target;
+ struct spdk_bdev_desc *desc;
+ struct spdk_io_channel *ch;
+ spdk_bdev_io_completion_cb cb_fn;
+ void *rbuf;
+ int rc;
+
+ desc = target->bdev_desc;
+ ch = target->ch;
+
+ switch (task->io_type) {
+ case SPDK_BDEV_IO_TYPE_WRITE:
+ cb_fn = (g_verify || g_reset) ? bdevperf_verify_write_complete : bdevperf_complete;
+ rc = spdk_bdev_writev_blocks(desc, ch, &task->iov, 1, task->offset_blocks,
+ target->io_size_blocks, cb_fn, task);
+ break;
+ case SPDK_BDEV_IO_TYPE_FLUSH:
+ rc = spdk_bdev_flush_blocks(desc, ch, task->offset_blocks,
+ target->io_size_blocks, bdevperf_complete, task);
+ break;
+ case SPDK_BDEV_IO_TYPE_UNMAP:
+ rc = spdk_bdev_unmap_blocks(desc, ch, task->offset_blocks,
+ target->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,
+ target->io_size_blocks, bdevperf_complete, task);
+ break;
+ case SPDK_BDEV_IO_TYPE_READ:
+ rbuf = g_zcopy ? NULL : task->buf;
+ rc = spdk_bdev_read_blocks(desc, ch, rbuf, task->offset_blocks,
+ target->io_size_blocks, bdevperf_complete, task);
+ break;
+ default:
+ assert(false);
+ rc = -EINVAL;
+ break;
+ }
+
+ if (rc == -ENOMEM) {
+ task->bdev_io_wait.bdev = target->bdev;
+ task->bdev_io_wait.cb_fn = bdevperf_submit_task;
+ task->bdev_io_wait.cb_arg = task;
+ spdk_bdev_queue_io_wait(target->bdev, ch, &task->bdev_io_wait);
+ return;
+ } else if (rc != 0) {
+ printf("Failed to submit bdev_io: %d\n", rc);
+ target->is_draining = true;
+ g_run_failed = true;
+ return;
+ }
+
+ target->current_queue_depth++;
+}
+
+static void
+bdevperf_submit_single(struct io_target *target, struct bdevperf_task *task)
+{
+ if (!task) {
+ if (!TAILQ_EMPTY(&target->task_list)) {
+ task = TAILQ_FIRST(&target->task_list);
+ TAILQ_REMOVE(&target->task_list, task, link);
+ } else {
+ printf("Task allocation failed\n");
+ abort();
+ }
+ }
+
+ bdevperf_prep_task(task);
+ bdevperf_submit_task(task);
+}
+
+static void
+bdevperf_submit_io(struct io_target *target, int queue_depth)
+{
+ while (queue_depth-- > 0) {
+ bdevperf_submit_single(target, NULL);
+ }
+}
+
+static int
+end_target(void *arg)
+{
+ struct io_target *target = arg;
+
+ spdk_poller_unregister(&target->run_timer);
+ if (g_reset) {
+ spdk_poller_unregister(&target->reset_timer);
+ }
+
+ target->is_draining = true;
+
+ return -1;
+}
+
+static int reset_target(void *arg);
+
+static void
+reset_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
+{
+ struct bdevperf_task *task = cb_arg;
+ struct io_target *target = task->target;
+
+ if (!success) {
+ printf("Reset blockdev=%s failed\n", spdk_bdev_get_name(target->bdev));
+ target->is_draining = true;
+ g_run_failed = true;
+ }
+
+ TAILQ_INSERT_TAIL(&target->task_list, task, link);
+ spdk_bdev_free_io(bdev_io);
+
+ target->reset_timer = spdk_poller_register(reset_target, target,
+ 10 * 1000000);
+}
+
+static int
+reset_target(void *arg)
+{
+ struct io_target *target = arg;
+ struct bdevperf_task *task = NULL;
+ int rc;
+
+ spdk_poller_unregister(&target->reset_timer);
+
+ /* Do reset. */
+ task = TAILQ_FIRST(&target->task_list);
+ if (!task) {
+ printf("Task allocation failed\n");
+ abort();
+ }
+ TAILQ_REMOVE(&target->task_list, task, link);
+
+ rc = spdk_bdev_reset(target->bdev_desc, target->ch,
+ reset_cb, task);
+ if (rc) {
+ printf("Reset failed: %d\n", rc);
+ target->is_draining = true;
+ g_run_failed = true;
+ }
+
+ return -1;
+}
+
+static void
+bdevperf_submit_on_core(void *arg1, void *arg2)
+{
+ struct io_target *target = arg1;
+
+ /* Submit initial I/O for each block device. Each time one
+ * completes, another will be submitted. */
+ while (target != NULL) {
+ target->ch = spdk_bdev_get_io_channel(target->bdev_desc);
+ if (!target->ch) {
+ printf("Skip this device (%s) as IO channel not setup.\n",
+ spdk_bdev_get_name(target->bdev));
+ g_target_count--;
+ g_run_failed = true;
+ spdk_bdev_close(target->bdev_desc);
+
+ target = target->next;
+ continue;
+ }
+
+ /* Start a timer to stop this I/O chain when the run is over */
+ target->run_timer = spdk_poller_register(end_target, target,
+ g_time_in_usec);
+ if (g_reset) {
+ target->reset_timer = spdk_poller_register(reset_target, target,
+ 10 * 1000000);
+ }
+ bdevperf_submit_io(target, g_queue_depth);
+ target = target->next;
+ }
+}
+
+static void
+bdevperf_usage(void)
+{
+ printf(" -q <depth> io depth\n");
+ printf(" -o <size> io size in bytes\n");
+ printf(" -w <type> io pattern type, must be one of (read, write, randread, randwrite, rw, randrw, verify, reset, unmap, flush)\n");
+ printf(" -t <time> time in seconds\n");
+ printf(" -M <percent> rwmixread (100 for reads, 0 for writes)\n");
+ printf(" -P <num> number of moving average period\n");
+ printf("\t\t(If set to n, show weighted mean of the previous n IO/s in real time)\n");
+ printf("\t\t(Formula: M = 2 / (n + 1), EMA[i+1] = IO/s * M + (1 - M) * EMA[i])\n");
+ printf("\t\t(only valid with -S)\n");
+ printf(" -S show performance result in real time in seconds\n");
+}
+
+/*
+ * 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 io_target *target, uint64_t io_time_in_usec)
+{
+ return (double)target->io_completed * 1000000 / io_time_in_usec;
+}
+
+static double
+get_ema_io_per_second(struct io_target *target, uint64_t ema_period)
+{
+ double io_completed, io_per_second;
+
+ io_completed = target->io_completed;
+ io_per_second = (double)(io_completed - target->prev_io_completed) * 1000000
+ / g_show_performance_period_in_usec;
+ target->prev_io_completed = io_completed;
+
+ target->ema_io_per_second += (io_per_second - target->ema_io_per_second) * 2
+ / (ema_period + 1);
+ return target->ema_io_per_second;
+}
+
+static void
+performance_dump(uint64_t io_time_in_usec, uint64_t ema_period)
+{
+ uint32_t index;
+ unsigned lcore_id;
+ double io_per_second, mb_per_second;
+ double total_io_per_second, total_mb_per_second;
+ struct io_target *target;
+
+ total_io_per_second = 0;
+ total_mb_per_second = 0;
+ for (index = 0; index < spdk_env_get_core_count(); index++) {
+ target = g_head[index];
+ if (target != NULL) {
+ lcore_id = target->lcore;
+ printf("\r Logical core: %u\n", lcore_id);
+ }
+ while (target != NULL) {
+ if (ema_period == 0) {
+ io_per_second = get_cma_io_per_second(target, io_time_in_usec);
+ } else {
+ io_per_second = get_ema_io_per_second(target, ema_period);
+ }
+ mb_per_second = io_per_second * g_io_size / (1024 * 1024);
+ printf("\r %-20s: %10.2f IO/s %10.2f MB/s\n",
+ target->name, io_per_second, mb_per_second);
+ total_io_per_second += io_per_second;
+ total_mb_per_second += mb_per_second;
+ target = target->next;
+ }
+ }
+
+ printf("\r =====================================================\n");
+ printf("\r %-20s: %10.2f IO/s %10.2f MB/s\n",
+ "Total", total_io_per_second, total_mb_per_second);
+ fflush(stdout);
+
+}
+
+static int
+performance_statistics_thread(void *arg)
+{
+ g_show_performance_period_num++;
+ performance_dump(g_show_performance_period_num * g_show_performance_period_in_usec,
+ g_show_performance_ema_period);
+ return -1;
+}
+
+static int
+bdevperf_construct_targets_tasks(void)
+{
+ uint32_t i;
+ struct io_target *target;
+ struct bdevperf_task *task;
+ int j, task_num = g_queue_depth;
+
+ /*
+ * Create the task pool after we have enumerated the targets, so that we know
+ * the min buffer alignment. Some backends such as AIO have alignment restrictions
+ * that must be accounted for.
+ */
+ if (g_reset) {
+ task_num += 1;
+ }
+
+ /* Initialize task list for each target */
+ for (i = 0; i < spdk_env_get_core_count(); i++) {
+ target = g_head[i];
+ if (!target) {
+ break;
+ }
+ while (target != NULL) {
+ for (j = 0; j < task_num; j++) {
+ task = calloc(1, sizeof(struct bdevperf_task));
+ if (!task) {
+ fprintf(stderr, "Failed to allocate task from memory\n");
+ goto ret;
+ }
+
+ task->buf = spdk_dma_zmalloc(g_io_size, g_min_alignment, NULL);
+ if (!task->buf) {
+ fprintf(stderr, "Cannot allocate buf for task=%p\n", task);
+ free(task);
+ goto ret;
+ }
+
+ task->target = target;
+ TAILQ_INSERT_TAIL(&target->task_list, task, link);
+ }
+ target = target->next;
+ }
+ }
+
+ return 0;
+
+ret:
+ fprintf(stderr, "Bdevperf program exits due to memory allocation issue\n");
+ fprintf(stderr, "Use -d XXX to allocate more huge pages, e.g., -d 4096\n");
+ return -1;
+}
+
+static void
+bdevperf_run(void *arg1, void *arg2)
+{
+ uint32_t i;
+ struct io_target *target;
+ struct spdk_event *event;
+ int rc;
+
+ rc = blockdev_heads_init();
+ if (rc) {
+ spdk_app_stop(1);
+ return;
+ }
+
+ bdevperf_construct_targets();
+
+ if (g_target_count == 0) {
+ fprintf(stderr, "No valid bdevs found.\n");
+ spdk_app_stop(1);
+ return;
+ }
+
+ rc = bdevperf_construct_targets_tasks();
+ if (rc) {
+ blockdev_heads_destroy();
+ spdk_app_stop(1);
+ return;
+ }
+
+ 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);
+ }
+
+ g_master_core = spdk_env_get_current_core();
+ /* Send events to start all I/O */
+ for (i = 0; i < spdk_env_get_core_count(); i++) {
+ target = g_head[i];
+ if (target == NULL) {
+ break;
+ }
+ event = spdk_event_allocate(target->lcore, bdevperf_submit_on_core,
+ target, NULL);
+ spdk_event_call(event);
+ }
+}
+
+static void
+bdevperf_stop_io_on_core(void *arg1, void *arg2)
+{
+ struct io_target *target = arg1;
+
+ /* Stop I/O for each block device. */
+ while (target != NULL) {
+ end_target(target);
+ target = target->next;
+ }
+}
+
+static void
+spdk_bdevperf_shutdown_cb(void)
+{
+ uint32_t i;
+ struct io_target *target;
+ struct spdk_event *event;
+
+ g_shutdown = true;
+ g_shutdown_tsc = spdk_get_ticks() - g_shutdown_tsc;
+
+ /* Send events to stop all I/O on each core */
+ for (i = 0; i < spdk_env_get_core_count(); i++) {
+ if (g_head == NULL) {
+ break;
+ }
+ target = g_head[i];
+ if (target == NULL) {
+ break;
+ }
+ event = spdk_event_allocate(target->lcore, bdevperf_stop_io_on_core,
+ target, NULL);
+ spdk_event_call(event);
+ }
+}
+
+static void
+bdevperf_parse_arg(int ch, char *arg)
+{
+ switch (ch) {
+ case 'q':
+ g_queue_depth = atoi(optarg);
+ break;
+ case 'o':
+ g_io_size = atoi(optarg);
+ break;
+ case 't':
+ g_time_in_sec = atoi(optarg);
+ break;
+ case 'w':
+ g_workload_type = optarg;
+ break;
+ case 'M':
+ g_rw_percentage = atoi(optarg);
+ g_mix_specified = true;
+ break;
+ case 'P':
+ g_show_performance_ema_period = atoi(optarg);
+ break;
+ case 'S':
+ g_show_performance_real_time = 1;
+ g_show_performance_period_in_usec = atoi(optarg) * 1000000;
+ g_show_performance_period_in_usec = spdk_max(g_show_performance_period_in_usec,
+ g_show_performance_period_in_usec);
+ break;
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ struct spdk_app_opts opts = {};
+ int rc;
+
+ spdk_app_opts_init(&opts);
+ opts.name = "bdevperf";
+ opts.rpc_addr = NULL;
+ opts.reactor_mask = NULL;
+ opts.mem_size = 1024;
+ opts.shutdown_cb = spdk_bdevperf_shutdown_cb;
+
+ /* default value */
+ g_queue_depth = 0;
+ g_io_size = 0;
+ g_workload_type = NULL;
+ g_time_in_sec = 0;
+ g_mix_specified = false;
+
+ if ((rc = spdk_app_parse_args(argc, argv, &opts, "q:o:t:w:M:P:S:", NULL,
+ bdevperf_parse_arg, bdevperf_usage)) !=
+ SPDK_APP_PARSE_ARGS_SUCCESS) {
+ return rc;
+ }
+
+ if (g_queue_depth <= 0) {
+ spdk_app_usage();
+ bdevperf_usage();
+ exit(1);
+ }
+ if (g_io_size <= 0) {
+ spdk_app_usage();
+ bdevperf_usage();
+ exit(1);
+ }
+ if (!g_workload_type) {
+ spdk_app_usage();
+ bdevperf_usage();
+ exit(1);
+ }
+ if (g_time_in_sec <= 0) {
+ spdk_app_usage();
+ bdevperf_usage();
+ exit(1);
+ }
+ g_time_in_usec = g_time_in_sec * 1000000LL;
+
+ if (g_show_performance_ema_period > 0 &&
+ g_show_performance_real_time == 0) {
+ fprintf(stderr, "-P option must be specified with -S option\n");
+ exit(1);
+ }
+
+ if (strcmp(g_workload_type, "read") &&
+ strcmp(g_workload_type, "write") &&
+ strcmp(g_workload_type, "randread") &&
+ strcmp(g_workload_type, "randwrite") &&
+ strcmp(g_workload_type, "rw") &&
+ strcmp(g_workload_type, "randrw") &&
+ strcmp(g_workload_type, "verify") &&
+ strcmp(g_workload_type, "reset") &&
+ strcmp(g_workload_type, "unmap") &&
+ strcmp(g_workload_type, "write_zeroes") &&
+ strcmp(g_workload_type, "flush")) {
+ fprintf(stderr,
+ "io pattern type must be one of\n"
+ "(read, write, randread, randwrite, rw, randrw, verify, reset, unmap, flush)\n");
+ exit(1);
+ }
+
+ if (!strcmp(g_workload_type, "read") ||
+ !strcmp(g_workload_type, "randread")) {
+ g_rw_percentage = 100;
+ }
+
+ if (!strcmp(g_workload_type, "write") ||
+ !strcmp(g_workload_type, "randwrite")) {
+ g_rw_percentage = 0;
+ }
+
+ if (!strcmp(g_workload_type, "unmap")) {
+ g_unmap = true;
+ }
+
+ if (!strcmp(g_workload_type, "write_zeroes")) {
+ g_write_zeroes = true;
+ }
+
+ if (!strcmp(g_workload_type, "flush")) {
+ g_flush = true;
+ }
+
+ if (!strcmp(g_workload_type, "verify") ||
+ !strcmp(g_workload_type, "reset")) {
+ g_rw_percentage = 50;
+ if (g_io_size > SPDK_BDEV_LARGE_BUF_MAX_SIZE) {
+ fprintf(stderr, "Unable to exceed max I/O size of %d for verify. (%d provided).\n",
+ SPDK_BDEV_LARGE_BUF_MAX_SIZE, g_io_size);
+ exit(1);
+ }
+ if (opts.reactor_mask) {
+ fprintf(stderr, "Ignoring -m option. Verify can only run with a single core.\n");
+ opts.reactor_mask = NULL;
+ }
+ g_verify = true;
+ if (!strcmp(g_workload_type, "reset")) {
+ g_reset = true;
+ }
+ }
+
+ if (!strcmp(g_workload_type, "read") ||
+ !strcmp(g_workload_type, "randread") ||
+ !strcmp(g_workload_type, "write") ||
+ !strcmp(g_workload_type, "randwrite") ||
+ !strcmp(g_workload_type, "verify") ||
+ !strcmp(g_workload_type, "reset") ||
+ !strcmp(g_workload_type, "unmap") ||
+ !strcmp(g_workload_type, "write_zeroes") ||
+ !strcmp(g_workload_type, "flush")) {
+ if (g_mix_specified) {
+ fprintf(stderr, "Ignoring -M option... Please use -M option"
+ " only when using rw or randrw.\n");
+ }
+ }
+
+ if (!strcmp(g_workload_type, "rw") ||
+ !strcmp(g_workload_type, "randrw")) {
+ if (g_rw_percentage < 0 || g_rw_percentage > 100) {
+ fprintf(stderr,
+ "-M must be specified to value from 0 to 100 "
+ "for rw or randrw.\n");
+ exit(1);
+ }
+ }
+
+ if (!strcmp(g_workload_type, "read") ||
+ !strcmp(g_workload_type, "write") ||
+ !strcmp(g_workload_type, "rw") ||
+ !strcmp(g_workload_type, "verify") ||
+ !strcmp(g_workload_type, "reset") ||
+ !strcmp(g_workload_type, "unmap") ||
+ !strcmp(g_workload_type, "write_zeroes")) {
+ g_is_random = 0;
+ } else {
+ g_is_random = 1;
+ }
+
+ if (g_io_size > SPDK_BDEV_LARGE_BUF_MAX_SIZE) {
+ printf("I/O size of %d is greater than zero copy threshold (%d).\n",
+ g_io_size, SPDK_BDEV_LARGE_BUF_MAX_SIZE);
+ printf("Zero copy mechanism will not be used.\n");
+ g_zcopy = false;
+ }
+
+ rc = spdk_app_start(&opts, bdevperf_run, NULL, NULL);
+ if (rc) {
+ g_run_failed = true;
+ }
+
+ if (g_shutdown) {
+ g_time_in_usec = g_shutdown_tsc * 1000000 / spdk_get_ticks_hz();
+ printf("Received shutdown signal, test time is about %.6f seconds\n",
+ (double)g_time_in_usec / 1000000);
+ }
+
+ if (g_time_in_usec) {
+ if (!g_run_failed) {
+ performance_dump(g_time_in_usec, 0);
+ }
+ } else {
+ printf("Test time less than one microsecond, no performance data will be shown\n");
+ }
+
+ blockdev_heads_destroy();
+ spdk_app_fini();
+ return g_run_failed;
+}
diff --git a/src/spdk/test/bdev/blockdev.sh b/src/spdk/test/bdev/blockdev.sh
new file mode 100755
index 00000000..bf3e006b
--- /dev/null
+++ b/src/spdk/test/bdev/blockdev.sh
@@ -0,0 +1,171 @@
+#!/usr/bin/env bash
+
+set -e
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+plugindir=$rootdir/examples/bdev/fio_plugin
+rpc_py="$rootdir/scripts/rpc.py"
+
+function run_fio()
+{
+ if [ $RUN_NIGHTLY -eq 0 ]; then
+ LD_PRELOAD=$plugindir/fio_plugin /usr/src/fio/fio --ioengine=spdk_bdev --iodepth=8 --bs=4k --runtime=10 $testdir/bdev.fio "$@"
+ elif [ $RUN_NIGHTLY_FAILING -eq 1 ]; then
+ # Use size 192KB which both exceeds typical 128KB max NVMe I/O
+ # size and will cross 128KB Intel DC P3700 stripe boundaries.
+ LD_PRELOAD=$plugindir/fio_plugin /usr/src/fio/fio --ioengine=spdk_bdev --iodepth=128 --bs=192k --runtime=100 $testdir/bdev.fio "$@"
+ fi
+}
+
+source $rootdir/test/common/autotest_common.sh
+source $testdir/nbd_common.sh
+
+function nbd_function_test() {
+ if [ $(uname -s) = Linux ] && modprobe -n nbd; then
+ local rpc_server=/var/tmp/spdk-nbd.sock
+ local conf=$1
+ local nbd_num=6
+ local nbd_all=(`ls /dev/nbd* | grep -v p`)
+ local bdev_all=($bdevs_name)
+ local nbd_list=(${nbd_all[@]:0:$nbd_num})
+ local bdev_list=(${bdev_all[@]:0:$nbd_num})
+
+ if [ ! -e $conf ]; then
+ return 1
+ fi
+
+ modprobe nbd
+ $rootdir/test/app/bdev_svc/bdev_svc -r $rpc_server -i 0 -c ${conf} &
+ nbd_pid=$!
+ echo "Process nbd pid: $nbd_pid"
+ waitforlisten $nbd_pid $rpc_server
+
+ nbd_rpc_data_verify $rpc_server "${bdev_list[*]}" "${nbd_list[*]}"
+
+ $rpc_py -s $rpc_server delete_passthru_bdev TestPT
+
+ killprocess $nbd_pid
+ fi
+
+ return 0
+}
+
+timing_enter bdev
+
+# Create a file to be used as an AIO backend
+dd if=/dev/zero of=/tmp/aiofile bs=2048 count=5000
+
+cp $testdir/bdev.conf.in $testdir/bdev.conf
+$rootdir/scripts/gen_nvme.sh >> $testdir/bdev.conf
+
+if [ $SPDK_TEST_RBD -eq 1 ]; then
+ timing_enter rbd_setup
+ rbd_setup 127.0.0.1
+ timing_exit rbd_setup
+
+ $rootdir/scripts/gen_rbd.sh >> $testdir/bdev.conf
+fi
+
+if [ $SPDK_TEST_CRYPTO -eq 1 ]; then
+ $rootdir/scripts/gen_crypto.sh Malloc6 >> $testdir/bdev.conf
+fi
+
+if hash pmempool; then
+ rm -f /tmp/spdk-pmem-pool
+ pmempool create blk --size=32M 512 /tmp/spdk-pmem-pool
+ echo "[Pmem]" >> $testdir/bdev.conf
+ echo " Blk /tmp/spdk-pmem-pool Pmem0" >> $testdir/bdev.conf
+fi
+
+timing_enter hello_bdev
+if grep -q Nvme0 $testdir/bdev.conf; then
+ $rootdir/examples/bdev/hello_world/hello_bdev -c $testdir/bdev.conf -b Nvme0n1
+fi
+timing_exit hello_bdev
+
+timing_enter bounds
+$testdir/bdevio/bdevio -c $testdir/bdev.conf
+timing_exit bounds
+
+timing_enter nbd_gpt
+if grep -q Nvme0 $testdir/bdev.conf; then
+ part_dev_by_gpt $testdir/bdev.conf Nvme0n1 $rootdir
+fi
+timing_exit nbd_gpt
+
+timing_enter bdev_svc
+bdevs=$(discover_bdevs $rootdir $testdir/bdev.conf | jq -r '.[] | select(.claimed == false)')
+timing_exit bdev_svc
+
+timing_enter nbd
+bdevs_name=$(echo $bdevs | jq -r '.name')
+nbd_function_test $testdir/bdev.conf "$bdevs_name"
+timing_exit nbd
+
+if [ -d /usr/src/fio ] && [ $SPDK_RUN_ASAN -eq 0 ]; then
+ timing_enter fio
+
+ timing_enter fio_rw_verify
+ # Generate the fio config file given the list of all unclaimed bdevs
+ fio_config_gen $testdir/bdev.fio verify
+ for b in $(echo $bdevs | jq -r '.name'); do
+ fio_config_add_job $testdir/bdev.fio $b
+ done
+
+ run_fio --spdk_conf=./test/bdev/bdev.conf
+
+ rm -f *.state
+ rm -f $testdir/bdev.fio
+ timing_exit fio_rw_verify
+
+ timing_enter fio_trim
+ # Generate the fio config file given the list of all unclaimed bdevs that support unmap
+ fio_config_gen $testdir/bdev.fio trim
+ for b in $(echo $bdevs | jq -r 'select(.supported_io_types.unmap == true) | .name'); do
+ fio_config_add_job $testdir/bdev.fio $b
+ done
+
+ run_fio --spdk_conf=./test/bdev/bdev.conf
+
+ rm -f *.state
+ rm -f $testdir/bdev.fio
+ timing_exit fio_trim
+ report_test_completion "bdev_fio"
+ timing_exit fio
+fi
+
+# Create conf file for bdevperf with gpt
+cat > $testdir/bdev_gpt.conf << EOL
+[Gpt]
+ Disable No
+EOL
+
+# Get Nvme info through filtering gen_nvme.sh's result
+$rootdir/scripts/gen_nvme.sh >> $testdir/bdev_gpt.conf
+
+# Run bdevperf with gpt
+$testdir/bdevperf/bdevperf -c $testdir/bdev_gpt.conf -q 128 -o 4096 -w verify -t 5
+$testdir/bdevperf/bdevperf -c $testdir/bdev_gpt.conf -q 128 -o 4096 -w write_zeroes -t 1
+rm -f $testdir/bdev_gpt.conf
+
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ # Temporarily disabled - infinite loop
+ timing_enter reset
+ #$testdir/bdevperf/bdevperf -c $testdir/bdev.conf -q 16 -w reset -o 4096 -t 60
+ timing_exit reset
+ report_test_completion "nightly_bdev_reset"
+fi
+
+
+if grep -q Nvme0 $testdir/bdev.conf; then
+ part_dev_by_gpt $testdir/bdev.conf Nvme0n1 $rootdir reset
+fi
+
+rm -f /tmp/aiofile
+rm -f /tmp/spdk-pmem-pool
+rm -f $testdir/bdev.conf
+trap - SIGINT SIGTERM EXIT
+rbd_cleanup
+report_test_completion "bdev"
+timing_exit bdev
diff --git a/src/spdk/test/bdev/nbd_common.sh b/src/spdk/test/bdev/nbd_common.sh
new file mode 100644
index 00000000..df8caac6
--- /dev/null
+++ b/src/spdk/test/bdev/nbd_common.sh
@@ -0,0 +1,95 @@
+set -e
+
+function nbd_start_disks() {
+ local rpc_server=$1
+ local bdev_list=($2)
+ local nbd_list=($3)
+
+ for (( i=0; i<${#nbd_list[@]}; i++ )); do
+ $rootdir/scripts/rpc.py -s $rpc_server start_nbd_disk \
+ ${bdev_list[$i]} ${nbd_list[$i]}
+ done
+ # Wait for nbd devices ready
+ for i in ${nbd_list[@]}; do
+ waitfornbd ${i:5}
+ done
+}
+
+function waitfornbd_exit() {
+ nbd_name=$1
+
+ for ((i=1; i<=20; i++)); do
+ if grep -q -w $nbd_name /proc/partitions; then
+ sleep 0.1
+ else
+ break
+ fi
+ done
+
+ return 0
+}
+
+function nbd_stop_disks() {
+ local rpc_server=$1
+ local nbd_list=($2)
+
+ for i in ${nbd_list[@]}; do
+ $rootdir/scripts/rpc.py -s $rpc_server stop_nbd_disk $i
+ done
+ for i in ${nbd_list[@]}; do
+ waitfornbd_exit ${i:5}
+ done
+}
+
+function nbd_get_count() {
+ # return = count of spdk nbd devices
+ local rpc_server=$1
+
+ nbd_disks_json=`$rootdir/scripts/rpc.py -s $rpc_server get_nbd_disks`
+ nbd_disks_name=`echo "${nbd_disks_json}" | jq -r '.[] | .nbd_device'`
+ count=`echo "${nbd_disks_name}" | grep -c /dev/nbd || true`
+ echo $count
+}
+
+function nbd_dd_data_verify() {
+ local nbd_list=($1)
+ local operation=$2
+ local tmp_file=/tmp/nbdrandtest
+
+ if [ "$operation" = "write" ]; then
+ # data write
+ dd if=/dev/urandom of=$tmp_file bs=4096 count=256
+ for i in ${nbd_list[@]}; do
+ dd if=$tmp_file of=$i bs=4096 count=256 oflag=direct
+ done
+ elif [ "$operation" = "verify" ]; then
+ # data read and verify
+ for i in ${nbd_list[@]}; do
+ cmp -b -n 1M $tmp_file $i
+ done
+ rm $tmp_file
+ fi
+}
+
+function nbd_rpc_data_verify() {
+ local rpc_server=$1
+ local bdev_list=($2)
+ local nbd_list=($3)
+
+ nbd_start_disks $rpc_server "${bdev_list[*]}" "${nbd_list[*]}"
+ count=$(nbd_get_count $rpc_server)
+ if [ $count -ne ${#nbd_list[@]} ]; then
+ return -1
+ fi
+
+ nbd_dd_data_verify "${nbd_list[*]}" "write"
+ nbd_dd_data_verify "${nbd_list[*]}" "verify"
+
+ nbd_stop_disks $rpc_server "${nbd_list[*]}"
+ count=$(nbd_get_count $rpc_server)
+ if [ $count -ne 0 ]; then
+ return -1
+ fi
+
+ return 0
+}
diff --git a/src/spdk/test/bdev/nbdjson/json_config.sh b/src/spdk/test/bdev/nbdjson/json_config.sh
new file mode 100755
index 00000000..ccd7006c
--- /dev/null
+++ b/src/spdk/test/bdev/nbdjson/json_config.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+set -xe
+NBD_JSON_DIR=$(readlink -f $(dirname $0))
+. $NBD_JSON_DIR/../../json_config/common.sh
+rpc_py="$spdk_rpc_py"
+clear_config_py="$spdk_clear_config_py"
+trap 'on_error_exit "${FUNCNAME}" "${LINENO}"' ERR
+
+timing_enter nbd_json_config
+run_spdk_tgt
+load_nvme
+modprobe nbd
+
+timing_enter nbd_json_config_create_setup
+$rpc_py construct_malloc_bdev 128 512 --name Malloc0
+$rpc_py start_nbd_disk Malloc0 /dev/nbd0
+$rpc_py start_nbd_disk Nvme0n1 /dev/nbd1
+timing_exit nbd_json_config_create_setup
+
+timing_enter nbd_json_config_test
+test_json_config
+timing_exit nbd_json_config_test
+
+$clear_config_py clear_config
+kill_targets
+rmmod nbd
+timing_exit nbd_json_config
+report_test_completion nbd_json_config
diff --git a/src/spdk/test/blobfs/Makefile b/src/spdk/test/blobfs/Makefile
new file mode 100644
index 00000000..1a9dfefa
--- /dev/null
+++ b/src/spdk/test/blobfs/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
+
+DIRS-y = mkfs
+
+# TODO: do not check a hardcoded path here
+ifneq (,$(wildcard /usr/local/include/fuse3))
+DIRS-y += fuse
+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/blobfs/fuse/.gitignore b/src/spdk/test/blobfs/fuse/.gitignore
new file mode 100644
index 00000000..a517c488
--- /dev/null
+++ b/src/spdk/test/blobfs/fuse/.gitignore
@@ -0,0 +1 @@
+fuse
diff --git a/src/spdk/test/blobfs/fuse/Makefile b/src/spdk/test/blobfs/fuse/Makefile
new file mode 100644
index 00000000..847da50e
--- /dev/null
+++ b/src/spdk/test/blobfs/fuse/Makefile
@@ -0,0 +1,60 @@
+#
+# 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.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk
+
+APP = fuse
+
+C_SRCS := fuse.c
+
+SPDK_LIB_LIST = event_bdev event_copy
+SPDK_LIB_LIST += blobfs blob bdev blob_bdev copy event thread util conf trace \
+ log jsonrpc json rpc
+
+LIBS += $(COPY_MODULES_LINKER_ARGS) $(BLOCKDEV_MODULES_LINKER_ARGS)
+LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS)
+LIBS+= -L/usr/local/lib -lfuse3
+
+all : $(APP)
+ @:
+
+$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(COPY_MODULES_FILES) $(BLOCKDEV_MODULES_FILES) $(ENV_LIBS)
+ $(LINK_C)
+
+clean :
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk
diff --git a/src/spdk/test/blobfs/fuse/fuse.c b/src/spdk/test/blobfs/fuse/fuse.c
new file mode 100644
index 00000000..9d11dfa0
--- /dev/null
+++ b/src/spdk/test/blobfs/fuse/fuse.c
@@ -0,0 +1,348 @@
+/*-
+ * 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"
+
+#define FUSE_USE_VERSION 30
+#include "fuse3/fuse.h"
+#include "fuse3/fuse_lowlevel.h"
+
+#include "spdk/blobfs.h"
+#include "spdk/bdev.h"
+#include "spdk/event.h"
+#include "spdk/thread.h"
+#include "spdk/blob_bdev.h"
+#include "spdk/log.h"
+
+struct fuse *g_fuse;
+char *g_bdev_name;
+char *g_mountpoint;
+pthread_t g_fuse_thread;
+
+struct spdk_bs_dev *g_bs_dev;
+struct spdk_filesystem *g_fs;
+struct spdk_io_channel *g_channel;
+struct spdk_file *g_file;
+int g_fserrno;
+int g_fuse_argc = 0;
+char **g_fuse_argv = NULL;
+
+static void
+__call_fn(void *arg1, void *arg2)
+{
+ fs_request_fn fn;
+
+ fn = (fs_request_fn)arg1;
+ fn(arg2);
+}
+
+static void
+__send_request(fs_request_fn fn, void *arg)
+{
+ struct spdk_event *event;
+
+ event = spdk_event_allocate(0, __call_fn, (void *)fn, arg);
+ spdk_event_call(event);
+}
+
+static int
+spdk_fuse_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
+{
+ struct spdk_file_stat stat;
+ int rc;
+
+ if (!strcmp(path, "/")) {
+ stbuf->st_mode = S_IFDIR | 0755;
+ stbuf->st_nlink = 2;
+ return 0;
+ }
+
+ rc = spdk_fs_file_stat(g_fs, g_channel, path, &stat);
+ if (rc == 0) {
+ stbuf->st_mode = S_IFREG | 0644;
+ stbuf->st_nlink = 1;
+ stbuf->st_size = stat.size;
+ }
+
+ return rc;
+}
+
+static int
+spdk_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+ off_t offset, struct fuse_file_info *fi,
+ enum fuse_readdir_flags flags)
+{
+ struct spdk_file *file;
+ const char *filename;
+ spdk_fs_iter iter;
+
+ filler(buf, ".", NULL, 0, 0);
+ filler(buf, "..", NULL, 0, 0);
+
+ iter = spdk_fs_iter_first(g_fs);
+ while (iter != NULL) {
+ file = spdk_fs_iter_get_file(iter);
+ iter = spdk_fs_iter_next(iter);
+ filename = spdk_file_get_name(file);
+ filler(buf, &filename[1], NULL, 0, 0);
+ }
+
+ return 0;
+}
+
+static int
+spdk_fuse_mknod(const char *path, mode_t mode, dev_t rdev)
+{
+ return spdk_fs_create_file(g_fs, g_channel, path);
+}
+
+static int
+spdk_fuse_unlink(const char *path)
+{
+ return spdk_fs_delete_file(g_fs, g_channel, path);
+}
+
+static int
+spdk_fuse_truncate(const char *path, off_t size, struct fuse_file_info *fi)
+{
+ struct spdk_file *file;
+ int rc;
+
+ rc = spdk_fs_open_file(g_fs, g_channel, path, 0, &file);
+ if (rc != 0) {
+ return -rc;
+ }
+
+ rc = spdk_file_truncate(file, g_channel, size);
+ if (rc != 0) {
+ return -rc;
+ }
+
+ spdk_file_close(file, g_channel);
+
+ return 0;
+}
+
+static int
+spdk_fuse_utimens(const char *path, const struct timespec tv[2], struct fuse_file_info *fi)
+{
+ return 0;
+}
+
+static int
+spdk_fuse_open(const char *path, struct fuse_file_info *info)
+{
+ struct spdk_file *file;
+ int rc;
+
+ rc = spdk_fs_open_file(g_fs, g_channel, path, 0, &file);
+ if (rc != 0) {
+ return -rc;
+ }
+
+ info->fh = (uintptr_t)file;
+ return 0;
+}
+
+static int
+spdk_fuse_release(const char *path, struct fuse_file_info *info)
+{
+ struct spdk_file *file = (struct spdk_file *)info->fh;
+
+ return spdk_file_close(file, g_channel);
+}
+
+static int
+spdk_fuse_read(const char *path, char *buf, size_t len, off_t offset, struct fuse_file_info *info)
+{
+ struct spdk_file *file = (struct spdk_file *)info->fh;
+
+ return spdk_file_read(file, g_channel, buf, offset, len);
+}
+
+static int
+spdk_fuse_write(const char *path, const char *buf, size_t len, off_t offset,
+ struct fuse_file_info *info)
+{
+ struct spdk_file *file = (struct spdk_file *)info->fh;
+ int rc;
+
+ rc = spdk_file_write(file, g_channel, (void *)buf, offset, len);
+ if (rc == 0) {
+ return len;
+ } else {
+ return rc;
+ }
+}
+
+static int
+spdk_fuse_flush(const char *path, struct fuse_file_info *info)
+{
+ return 0;
+}
+
+static int
+spdk_fuse_fsync(const char *path, int datasync, struct fuse_file_info *info)
+{
+ return 0;
+}
+
+static int
+spdk_fuse_rename(const char *old_path, const char *new_path, unsigned int flags)
+{
+ return spdk_fs_rename_file(g_fs, g_channel, old_path, new_path);
+}
+
+static struct fuse_operations spdk_fuse_oper = {
+ .getattr = spdk_fuse_getattr,
+ .readdir = spdk_fuse_readdir,
+ .mknod = spdk_fuse_mknod,
+ .unlink = spdk_fuse_unlink,
+ .truncate = spdk_fuse_truncate,
+ .utimens = spdk_fuse_utimens,
+ .open = spdk_fuse_open,
+ .release = spdk_fuse_release,
+ .read = spdk_fuse_read,
+ .write = spdk_fuse_write,
+ .flush = spdk_fuse_flush,
+ .fsync = spdk_fuse_fsync,
+ .rename = spdk_fuse_rename,
+};
+
+static void
+construct_targets(void)
+{
+ struct spdk_bdev *bdev;
+
+ bdev = spdk_bdev_get_by_name(g_bdev_name);
+ if (bdev == NULL) {
+ SPDK_ERRLOG("bdev %s not found\n", g_bdev_name);
+ exit(1);
+ }
+
+ g_bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL);
+
+ printf("Mounting BlobFS on bdev %s\n", spdk_bdev_get_name(bdev));
+}
+
+static void
+start_fuse_fn(void *arg1, void *arg2)
+{
+ struct fuse_args args = FUSE_ARGS_INIT(g_fuse_argc, g_fuse_argv);
+ int rc;
+ struct fuse_cmdline_opts opts = {};
+
+ g_fuse_thread = pthread_self();
+ rc = fuse_parse_cmdline(&args, &opts);
+ if (rc != 0) {
+ spdk_app_stop(-1);
+ fuse_opt_free_args(&args);
+ return;
+ }
+ g_fuse = fuse_new(&args, &spdk_fuse_oper, sizeof(spdk_fuse_oper), NULL);
+ fuse_opt_free_args(&args);
+
+ rc = fuse_mount(g_fuse, g_mountpoint);
+ if (rc != 0) {
+ spdk_app_stop(-1);
+ return;
+ }
+
+ fuse_daemonize(true /* true = run in foreground */);
+
+ fuse_loop(g_fuse);
+
+ fuse_unmount(g_fuse);
+ fuse_destroy(g_fuse);
+}
+
+static void
+init_cb(void *ctx, struct spdk_filesystem *fs, int fserrno)
+{
+ struct spdk_event *event;
+
+ g_fs = fs;
+ g_channel = spdk_fs_alloc_io_channel_sync(g_fs);
+ event = spdk_event_allocate(1, start_fuse_fn, NULL, NULL);
+ spdk_event_call(event);
+}
+
+static void
+spdk_fuse_run(void *arg1, void *arg2)
+{
+ construct_targets();
+ spdk_fs_load(g_bs_dev, __send_request, init_cb, NULL);
+}
+
+static void
+shutdown_cb(void *ctx, int fserrno)
+{
+ fuse_session_exit(fuse_get_session(g_fuse));
+ pthread_kill(g_fuse_thread, SIGINT);
+ spdk_fs_free_io_channel(g_channel);
+ spdk_app_stop(0);
+}
+
+static void
+spdk_fuse_shutdown(void)
+{
+ spdk_fs_unload(g_fs, shutdown_cb, NULL);
+}
+
+int main(int argc, char **argv)
+{
+ struct spdk_app_opts opts = {};
+ int rc = 0;
+
+ if (argc < 4) {
+ fprintf(stderr, "usage: %s <conffile> <bdev name> <mountpoint>\n", argv[0]);
+ exit(1);
+ }
+
+ spdk_app_opts_init(&opts);
+ opts.name = "spdk_fuse";
+ opts.config_file = argv[1];
+ opts.reactor_mask = "0x3";
+ opts.mem_size = 6144;
+ opts.shutdown_cb = spdk_fuse_shutdown;
+
+ g_bdev_name = argv[2];
+ g_mountpoint = argv[3];
+ g_fuse_argc = argc - 2;
+ g_fuse_argv = &argv[2];
+
+ rc = spdk_app_start(&opts, spdk_fuse_run, NULL, NULL);
+ spdk_app_fini();
+
+ return rc;
+}
diff --git a/src/spdk/test/blobfs/mkfs/.gitignore b/src/spdk/test/blobfs/mkfs/.gitignore
new file mode 100644
index 00000000..54e292c6
--- /dev/null
+++ b/src/spdk/test/blobfs/mkfs/.gitignore
@@ -0,0 +1 @@
+mkfs
diff --git a/src/spdk/test/blobfs/mkfs/Makefile b/src/spdk/test/blobfs/mkfs/Makefile
new file mode 100644
index 00000000..8367571e
--- /dev/null
+++ b/src/spdk/test/blobfs/mkfs/Makefile
@@ -0,0 +1,59 @@
+#
+# 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.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk
+
+APP = mkfs
+
+C_SRCS := mkfs.c
+
+SPDK_LIB_LIST = event_bdev event_copy
+SPDK_LIB_LIST += blobfs blob bdev blob_bdev copy event thread util conf trace \
+ log jsonrpc json rpc
+
+LIBS += $(COPY_MODULES_LINKER_ARGS) $(BLOCKDEV_MODULES_LINKER_ARGS) $(SOCK_MODULES_LINKER_ARGS)
+LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS)
+
+all : $(APP)
+ @:
+
+$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(COPY_MODULES_FILES) $(BLOCKDEV_MODULES_FILES) $(SOCK_MODULES_FILES) $(ENV_LIBS)
+ $(LINK_C)
+
+clean :
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk
diff --git a/src/spdk/test/blobfs/mkfs/mkfs.c b/src/spdk/test/blobfs/mkfs/mkfs.c
new file mode 100644
index 00000000..0bedd84a
--- /dev/null
+++ b/src/spdk/test/blobfs/mkfs/mkfs.c
@@ -0,0 +1,150 @@
+/*-
+ * 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/blobfs.h"
+#include "spdk/bdev.h"
+#include "spdk/event.h"
+#include "spdk/blob_bdev.h"
+#include "spdk/log.h"
+#include "spdk/string.h"
+
+struct spdk_bs_dev *g_bs_dev;
+const char *g_bdev_name;
+static uint64_t g_cluster_size;
+
+static void
+stop_cb(void *ctx, int fserrno)
+{
+ spdk_app_stop(0);
+}
+
+static void
+shutdown_cb(void *arg1, void *arg2)
+{
+ struct spdk_filesystem *fs = arg1;
+
+ printf("done.\n");
+ spdk_fs_unload(fs, stop_cb, NULL);
+}
+
+static void
+init_cb(void *ctx, struct spdk_filesystem *fs, int fserrno)
+{
+ struct spdk_event *event;
+
+ event = spdk_event_allocate(0, shutdown_cb, fs, NULL);
+ spdk_event_call(event);
+}
+
+static void
+spdk_mkfs_run(void *arg1, void *arg2)
+{
+ struct spdk_bdev *bdev;
+ struct spdk_blobfs_opts blobfs_opt;
+
+ bdev = spdk_bdev_get_by_name(g_bdev_name);
+
+ if (bdev == NULL) {
+ SPDK_ERRLOG("bdev %s not found\n", g_bdev_name);
+ spdk_app_stop(-1);
+ return;
+ }
+
+ printf("Initializing filesystem on bdev %s...", g_bdev_name);
+ fflush(stdout);
+
+ spdk_fs_opts_init(&blobfs_opt);
+ if (g_cluster_size) {
+ blobfs_opt.cluster_sz = g_cluster_size;
+ }
+ g_bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL);
+ if (blobfs_opt.cluster_sz) {
+ spdk_fs_init(g_bs_dev, &blobfs_opt, NULL, init_cb, NULL);
+ } else {
+ spdk_fs_init(g_bs_dev, NULL, NULL, init_cb, NULL);
+ }
+}
+
+static void
+mkfs_usage(void)
+{
+ printf(" -C <size> cluster size\n");
+}
+
+static void
+mkfs_parse_arg(int ch, char *arg)
+{
+ bool has_prefix;
+
+ switch (ch) {
+ case 'C':
+ spdk_parse_capacity(arg, &g_cluster_size, &has_prefix);
+ break;
+ default:
+ break;
+ }
+
+}
+
+int main(int argc, char **argv)
+{
+ struct spdk_app_opts opts = {};
+ int rc = 0;
+
+ if (argc < 3) {
+ SPDK_ERRLOG("usage: %s <conffile> <bdevname>\n", argv[0]);
+ exit(1);
+ }
+
+ spdk_app_opts_init(&opts);
+ opts.name = "spdk_mkfs";
+ opts.config_file = argv[1];
+ opts.reactor_mask = "0x3";
+ opts.mem_size = 1024;
+ opts.shutdown_cb = NULL;
+
+ spdk_fs_set_cache_size(512);
+ g_bdev_name = argv[2];
+ if ((rc = spdk_app_parse_args(argc, argv, &opts, "C:", NULL,
+ mkfs_parse_arg, mkfs_usage)) !=
+ SPDK_APP_PARSE_ARGS_SUCCESS) {
+ exit(rc);
+ }
+
+ rc = spdk_app_start(&opts, spdk_mkfs_run, NULL, NULL);
+ spdk_app_fini();
+
+ return rc;
+}
diff --git a/src/spdk/test/blobfs/rocksdb/.gitignore b/src/spdk/test/blobfs/rocksdb/.gitignore
new file mode 100644
index 00000000..1a06816d
--- /dev/null
+++ b/src/spdk/test/blobfs/rocksdb/.gitignore
@@ -0,0 +1 @@
+results
diff --git a/src/spdk/test/blobfs/rocksdb/common_flags.txt b/src/spdk/test/blobfs/rocksdb/common_flags.txt
new file mode 100644
index 00000000..6390c7a4
--- /dev/null
+++ b/src/spdk/test/blobfs/rocksdb/common_flags.txt
@@ -0,0 +1,27 @@
+--disable_seek_compaction=1
+--mmap_read=0
+--statistics=1
+--histogram=1
+--key_size=16
+--value_size=1000
+--block_size=4096
+--cache_size=0
+--bloom_bits=10
+--cache_numshardbits=4
+--open_files=500000
+--verify_checksum=1
+--db=/mnt/rocksdb
+--sync=0
+--compression_type=none
+--stats_interval=1000000
+--compression_ratio=1
+--disable_data_sync=0
+--target_file_size_base=67108864
+--max_write_buffer_number=3
+--max_bytes_for_level_multiplier=10
+--max_background_compactions=10
+--num_levels=10
+--delete_obsolete_files_period_micros=3000000
+--max_grandparent_overlap_factor=10
+--stats_per_interval=1
+--max_bytes_for_level_base=10485760
diff --git a/src/spdk/test/blobfs/rocksdb/postprocess.py b/src/spdk/test/blobfs/rocksdb/postprocess.py
new file mode 100755
index 00000000..1ba8a730
--- /dev/null
+++ b/src/spdk/test/blobfs/rocksdb/postprocess.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+from collections import namedtuple
+from itertools import islice
+import operator
+import sys
+
+total_samples = 0
+thread_module_samples = {}
+function_module_samples = {}
+module_samples = {}
+threads = set()
+
+ThreadModule = namedtuple('ThreadModule', ['thread', 'module'])
+FunctionModule = namedtuple('FunctionModule', ['function', 'module'])
+
+with open(sys.argv[1] + "/" + sys.argv[2] + ".perf.txt") as f:
+ for line in f:
+ fields = line.split()
+ total_samples += int(fields[1])
+ key = ThreadModule(fields[2], fields[3])
+ thread_module_samples.setdefault(key, 0)
+ thread_module_samples[key] += int(fields[1])
+ key = FunctionModule(fields[5], fields[3])
+ function_module_samples.setdefault(key, 0)
+ function_module_samples[key] += int(fields[1])
+ threads.add(fields[2])
+
+ key = fields[3]
+ module_samples.setdefault(key, 0)
+ module_samples[key] += int(fields[1])
+
+for thread in sorted(threads):
+ thread_pct = 0
+ print("")
+ print("Thread: {:s}".format(thread))
+ print(" Percent Module")
+ print("============================")
+ for key, value in sorted(list(thread_module_samples.items()), key=operator.itemgetter(1), reverse=True):
+ if key.thread == thread:
+ print("{:8.4f} {:20s}".format(float(value) * 100 / total_samples, key.module))
+ thread_pct += float(value) * 100 / total_samples
+ print("============================")
+ print("{:8.4f} Total".format(thread_pct))
+
+print("")
+print(" Percent Module Function")
+print("=================================================================")
+for key, value in islice(sorted(list(function_module_samples.items()), key=operator.itemgetter(1), reverse=True), 100):
+ print(("{:8.4f} {:20s} {:s}".format(float(value) * 100 / total_samples, key.module, key.function)))
+
+print("")
+print("")
+print(" Percent Module")
+print("=================================")
+for key, value in sorted(list(module_samples.items()), key=operator.itemgetter(1), reverse=True):
+ print("{:8.4f} {:s}".format(float(value) * 100 / total_samples, key))
+
+print("")
+with open(sys.argv[1] + "/" + sys.argv[2] + "_db_bench.txt") as f:
+ for line in f:
+ if "maxresident" in line:
+ fields = line.split()
+ print("Wall time elapsed: {:s}".format(fields[2].split("e")[0]))
+ print("CPU utilization: {:s}".format(fields[3].split('C')[0]))
+ user = float(fields[0].split('u')[0])
+ system = float(fields[1].split('s')[0])
+ print("User: {:8.2f} ({:5.2f}%)".format(user, user * 100 / (user + system)))
+ print("System: {:8.2f} ({:5.2f}%)".format(system, system * 100 / (user + system)))
+
+print("")
diff --git a/src/spdk/test/blobfs/rocksdb/rocksdb.sh b/src/spdk/test/blobfs/rocksdb/rocksdb.sh
new file mode 100755
index 00000000..1a73ee55
--- /dev/null
+++ b/src/spdk/test/blobfs/rocksdb/rocksdb.sh
@@ -0,0 +1,134 @@
+#!/usr/bin/env bash
+
+set -ex
+
+run_step() {
+ if [ -z "$1" ]; then
+ echo run_step called with no parameter
+ exit 1
+ fi
+
+ echo "--spdk=$ROCKSDB_CONF" >> "$1"_flags.txt
+ echo "--spdk_bdev=Nvme0n1" >> "$1"_flags.txt
+ echo "--spdk_cache_size=$CACHE_SIZE" >> "$1"_flags.txt
+
+ echo -n Start $1 test phase...
+ /usr/bin/time taskset 0xFF $DB_BENCH --flagfile="$1"_flags.txt &> "$1"_db_bench.txt
+ echo done.
+}
+
+run_bsdump() {
+ $rootdir/examples/blob/cli/blobcli -c $ROCKSDB_CONF -b Nvme0n1 -D &> bsdump.txt
+}
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+
+DB_BENCH_DIR=/usr/src/rocksdb
+DB_BENCH=$DB_BENCH_DIR/db_bench
+ROCKSDB_CONF=$testdir/rocksdb.conf
+
+if [ ! -e $DB_BENCH_DIR ]; then
+ echo $DB_BENCH_DIR does not exist, skipping rocksdb tests
+ exit 0
+fi
+
+timing_enter rocksdb
+
+timing_enter db_bench_build
+
+pushd $DB_BENCH_DIR
+git clean -x -f -d
+$MAKE db_bench $MAKEFLAGS $MAKECONFIG DEBUG_LEVEL=0 SPDK_DIR=$rootdir
+popd
+
+timing_exit db_bench_build
+
+$rootdir/scripts/gen_nvme.sh > $ROCKSDB_CONF
+
+trap 'run_bsdump; rm -f $ROCKSDB_CONF; exit 1' SIGINT SIGTERM EXIT
+
+timing_enter mkfs
+$rootdir/test/blobfs/mkfs/mkfs $ROCKSDB_CONF Nvme0n1
+timing_exit mkfs
+
+mkdir $output_dir/rocksdb
+RESULTS_DIR=$output_dir/rocksdb
+CACHE_SIZE=4096
+if [ $RUN_NIGHTLY_FAILING -eq 1 ]; then
+ DURATION=60
+ NUM_KEYS=100000000
+else
+ DURATION=20
+ NUM_KEYS=20000000
+fi
+
+cd $RESULTS_DIR
+cp $testdir/common_flags.txt insert_flags.txt
+echo "--benchmarks=fillseq" >> insert_flags.txt
+echo "--threads=1" >> insert_flags.txt
+echo "--disable_wal=1" >> insert_flags.txt
+echo "--use_existing_db=0" >> insert_flags.txt
+echo "--num=$NUM_KEYS" >> insert_flags.txt
+
+cp $testdir/common_flags.txt randread_flags.txt
+echo "--benchmarks=readrandom" >> randread_flags.txt
+echo "--threads=16" >> randread_flags.txt
+echo "--duration=$DURATION" >> randread_flags.txt
+echo "--disable_wal=1" >> randread_flags.txt
+echo "--use_existing_db=1" >> randread_flags.txt
+echo "--num=$NUM_KEYS" >> randread_flags.txt
+
+cp $testdir/common_flags.txt overwrite_flags.txt
+echo "--benchmarks=overwrite" >> overwrite_flags.txt
+echo "--threads=1" >> overwrite_flags.txt
+echo "--duration=$DURATION" >> overwrite_flags.txt
+echo "--disable_wal=1" >> overwrite_flags.txt
+echo "--use_existing_db=1" >> overwrite_flags.txt
+echo "--num=$NUM_KEYS" >> overwrite_flags.txt
+
+cp $testdir/common_flags.txt readwrite_flags.txt
+echo "--benchmarks=readwhilewriting" >> readwrite_flags.txt
+echo "--threads=4" >> readwrite_flags.txt
+echo "--duration=$DURATION" >> readwrite_flags.txt
+echo "--disable_wal=1" >> readwrite_flags.txt
+echo "--use_existing_db=1" >> readwrite_flags.txt
+echo "--num=$NUM_KEYS" >> readwrite_flags.txt
+
+cp $testdir/common_flags.txt writesync_flags.txt
+echo "--benchmarks=overwrite" >> writesync_flags.txt
+echo "--threads=1" >> writesync_flags.txt
+echo "--duration=$DURATION" >> writesync_flags.txt
+echo "--disable_wal=0" >> writesync_flags.txt
+echo "--use_existing_db=1" >> writesync_flags.txt
+echo "--sync=1" >> writesync_flags.txt
+echo "--num=$NUM_KEYS" >> writesync_flags.txt
+
+timing_enter rocksdb_insert
+run_step insert
+timing_exit rocksdb_insert
+
+timing_enter rocksdb_overwrite
+run_step overwrite
+timing_exit rocksdb_overwrite
+
+timing_enter rocksdb_readwrite
+run_step readwrite
+timing_exit rocksdb_readwrite
+
+timing_enter rocksdb_writesync
+run_step writesync
+timing_exit rocksdb_writesync
+
+timing_enter rocksdb_randread
+run_step randread
+timing_exit rocksdb_randread
+
+trap - SIGINT SIGTERM EXIT
+
+run_bsdump
+rm -f $ROCKSDB_CONF
+
+report_test_completion "blobfs"
+timing_exit rocksdb
diff --git a/src/spdk/test/blobfs/rocksdb/run_tests.sh b/src/spdk/test/blobfs/rocksdb/run_tests.sh
new file mode 100755
index 00000000..d65d3b10
--- /dev/null
+++ b/src/spdk/test/blobfs/rocksdb/run_tests.sh
@@ -0,0 +1,197 @@
+#!/bin/bash
+set -e
+
+if [ $# -eq 0 ]
+then
+ echo "usage: $0 <location of db_bench>"
+ exit 1
+fi
+
+DB_BENCH=$(readlink -f $1)
+[ -e $DB_BENCH ] || (echo "$DB_BENCH does not exist - needs to be built" && exit 1)
+
+hash mkfs.xfs
+: ${USE_PERF:=1}
+if ! hash perf; then
+ USE_PERF=0
+fi
+hash python
+[ -e /usr/include/gflags/gflags.h ] || (echo "gflags not installed." && exit 1)
+
+# Increase max number of file descriptors. This will be inherited
+# by processes spawned from this script.
+ulimit -n 16384
+
+TESTDIR=$(readlink -f $(dirname $0))
+
+if ls $TESTDIR/results/testrun_* &> /dev/null; then
+ mkdir -p $TESTDIR/results/old
+ mv $TESTDIR/results/testrun_* $TESTDIR/results/old
+fi
+
+if [ -z "$RESULTS_DIR" ]; then
+ RESULTS_DIR=$TESTDIR/results/testrun_`date +%Y%m%d_%H%M%S`
+ mkdir -p $RESULTS_DIR
+ rm -f $TESTDIR/results/last
+ ln -s $RESULTS_DIR $TESTDIR/results/last
+fi
+
+: ${CACHE_SIZE:=4096}
+: ${DURATION:=120}
+: ${NUM_KEYS:=500000000}
+: ${ROCKSDB_CONF:=/usr/local/etc/spdk/rocksdb.conf}
+
+if [ "$NO_SPDK" = "1" ]
+then
+ [ -e /dev/nvme0n1 ] || (echo "No /dev/nvme0n1 device node found." && exit 1)
+else
+ [ -e /dev/nvme0n1 ] && (echo "/dev/nvme0n1 device found - need to run SPDK setup.sh script to bind to UIO." && exit 1)
+fi
+
+cd $RESULTS_DIR
+
+SYSINFO_FILE=sysinfo.txt
+COMMAND="hostname"
+echo ">> $COMMAND : " >> $SYSINFO_FILE
+$COMMAND >> $SYSINFO_FILE
+echo >> $SYSINFO_FILE
+
+COMMAND="cat /proc/cpuinfo"
+echo ">> $COMMAND : " >> $SYSINFO_FILE
+$COMMAND >> $SYSINFO_FILE
+echo >> $SYSINFO_FILE
+
+COMMAND="cat /proc/meminfo"
+echo ">> $COMMAND : " >> $SYSINFO_FILE
+$COMMAND >> $SYSINFO_FILE
+echo >> $SYSINFO_FILE
+
+if [ "$NO_SPDK" = "1" ]
+then
+ echo -n Creating and mounting XFS filesystem...
+ sudo mkdir -p /mnt/rocksdb
+ sudo umount /mnt/rocksdb || true &> /dev/null
+ sudo mkfs.xfs -d agcount=32 -l su=4096 -f /dev/nvme0n1 &> mkfs_xfs.txt
+ sudo mount -o discard /dev/nvme0n1 /mnt/rocksdb
+ sudo chown $USER /mnt/rocksdb
+ echo done.
+fi
+
+cp $TESTDIR/common_flags.txt insert_flags.txt
+echo "--benchmarks=fillseq" >> insert_flags.txt
+echo "--threads=1" >> insert_flags.txt
+echo "--disable_wal=1" >> insert_flags.txt
+echo "--use_existing_db=0" >> insert_flags.txt
+echo "--num=$NUM_KEYS" >> insert_flags.txt
+
+cp $TESTDIR/common_flags.txt randread_flags.txt
+echo "--benchmarks=readrandom" >> randread_flags.txt
+echo "--threads=16" >> randread_flags.txt
+echo "--duration=$DURATION" >> randread_flags.txt
+echo "--disable_wal=1" >> randread_flags.txt
+echo "--use_existing_db=1" >> randread_flags.txt
+echo "--num=$NUM_KEYS" >> randread_flags.txt
+
+cp $TESTDIR/common_flags.txt overwrite_flags.txt
+echo "--benchmarks=overwrite" >> overwrite_flags.txt
+echo "--threads=1" >> overwrite_flags.txt
+echo "--duration=$DURATION" >> overwrite_flags.txt
+echo "--disable_wal=1" >> overwrite_flags.txt
+echo "--use_existing_db=1" >> overwrite_flags.txt
+echo "--num=$NUM_KEYS" >> overwrite_flags.txt
+
+cp $TESTDIR/common_flags.txt readwrite_flags.txt
+echo "--benchmarks=readwhilewriting" >> readwrite_flags.txt
+echo "--threads=4" >> readwrite_flags.txt
+echo "--duration=$DURATION" >> readwrite_flags.txt
+echo "--disable_wal=1" >> readwrite_flags.txt
+echo "--use_existing_db=1" >> readwrite_flags.txt
+echo "--num=$NUM_KEYS" >> readwrite_flags.txt
+
+cp $TESTDIR/common_flags.txt writesync_flags.txt
+echo "--benchmarks=overwrite" >> writesync_flags.txt
+echo "--threads=1" >> writesync_flags.txt
+echo "--duration=$DURATION" >> writesync_flags.txt
+echo "--disable_wal=0" >> writesync_flags.txt
+echo "--use_existing_db=1" >> writesync_flags.txt
+echo "--sync=1" >> writesync_flags.txt
+echo "--num=$NUM_KEYS" >> writesync_flags.txt
+
+run_step() {
+ if [ -z "$1" ]
+ then
+ echo run_step called with no parameter
+ exit 1
+ fi
+
+ if [ -z "$NO_SPDK" ]
+ then
+ echo "--spdk=$ROCKSDB_CONF" >> "$1"_flags.txt
+ echo "--spdk_bdev=Nvme0n1" >> "$1"_flags.txt
+ echo "--spdk_cache_size=$CACHE_SIZE" >> "$1"_flags.txt
+ fi
+
+ if [ "$NO_SPDK" = "1" ]
+ then
+ echo "--bytes_per_sync=262144" >> "$1"_flags.txt
+ cat /sys/block/nvme0n1/stat > "$1"_blockdev_stats.txt
+ fi
+
+ echo -n Start $1 test phase...
+ if [ "$USE_PERF" = "1" ]
+ then
+ sudo /usr/bin/time taskset 0xFF perf record $DB_BENCH --flagfile="$1"_flags.txt &> "$1"_db_bench.txt
+ else
+ sudo /usr/bin/time taskset 0xFF $DB_BENCH --flagfile="$1"_flags.txt &> "$1"_db_bench.txt
+ fi
+ echo done.
+
+ if [ "$NO_SPDK" = "1" ]
+ then
+ drop_caches
+ cat /sys/block/nvme0n1/stat >> "$1"_blockdev_stats.txt
+ fi
+
+ if [ "$USE_PERF" = "1" ]
+ then
+ echo -n Generating perf report for $1 test phase...
+ sudo perf report -f -n | sed '/#/d' | sed '/%/!d' | sort -r > $1.perf.txt
+ sudo rm perf.data
+ $TESTDIR/postprocess.py `pwd` $1 > $1_summary.txt
+ echo done.
+ fi
+}
+
+drop_caches() {
+ echo -n Cleaning Page Cache...
+ echo 3 > /proc/sys/vm/drop_caches
+ echo done.
+}
+
+if [ -z "$SKIP_INSERT" ]
+then
+ run_step insert
+fi
+if [ -z "$SKIP_OVERWRITE" ]
+then
+ run_step overwrite
+fi
+if [ -z "$SKIP_READWRITE" ]
+then
+ run_step readwrite
+fi
+if [ -z "$SKIP_WRITESYNC" ]
+then
+ run_step writesync
+fi
+if [ -z "$SKIP_RANDREAD" ]
+then
+ run_step randread
+fi
+
+if [ "$NO_SPDK" = "1" ]
+then
+ echo -n Unmounting XFS filesystem...
+ sudo umount /mnt/rocksdb || true &> /dev/null
+ echo done.
+fi
diff --git a/src/spdk/test/blobfs/test_plan.md b/src/spdk/test/blobfs/test_plan.md
new file mode 100644
index 00000000..080427b2
--- /dev/null
+++ b/src/spdk/test/blobfs/test_plan.md
@@ -0,0 +1,67 @@
+# SPDK BlobFS Test Plan
+
+## Current Tests
+
+# Unit tests (asynchronous API)
+
+- Tests BlobFS w/ Blobstore with no dependencies on SPDK bdev layer or event framework.
+ Uses simple DRAM buffer to simulate a block device - all block operations are immediately
+ completed so no special event handling is required.
+- Current tests include:
+ - basic fs initialization and unload
+ - open non-existent file fails if SPDK_BLOBFS_OPEN_CREATE not specified
+ - open non-existent file creates the file if SPDK_BLOBFS_OPEN_CREATE is specified
+ - close a file fails if there are still open references
+ - closing a file with no open references fails
+ - files can be truncated up and down in length
+ - three-way rename
+ - operations for inserting and traversing buffers in a cache tree
+ - allocating and freeing I/O channels
+
+# Unit tests (synchronous API)
+
+- Tests BlobFS w/ Blobstore with no dependencies on SPDK bdev layer or event framework.
+ The synchronous API requires a separate thread to handle any asynchronous handoffs such as
+ I/O to disk.
+ - basic read/write I/O operations
+ - appending to a file whose cache has been flushed and evicted
+
+# RocksDB
+
+- Tests BlobFS as the backing store for a RocksDB database. BlobFS uses the SPDK NVMe driver
+ through the SPDK bdev layer as its block device. Uses RocksDB db_bench utility to drive
+ the workloads. Each workload (after the initial sequential insert) reloads the database
+ which validates metadata operations completed correctly in the previous run via the
+ RocksDB MANIFEST file. RocksDB also runs checksums on key/value blocks read from disk,
+ verifying data integrity.
+ - initialize BlobFS filesystem on NVMe SSD
+ - bulk sequential insert of up to 500M keys (16B key, 1000B value)
+ - overwrite test - randomly overwrite one of the keys in the database (driving both
+ flush and compaction traffic)
+ - readwrite test - one thread randomly overwrites a key in the database, up to 16
+ threads randomly read a key in the database.
+ - writesync - same as overwrite, but enables a WAL (write-ahead log)
+ - randread - up to 16 threads randomly read a key in the database
+
+## Future tests to add
+
+# Unit tests
+
+- Corrupt data in DRAM buffer, and confirm subsequent operations such as BlobFS load or
+ opening a blob fail as expected (no panics, etc.)
+- Test synchronous API with multiple synchronous threads. May be implemented separately
+ from existing synchronous unit tests to allow for more sophisticated thread
+ synchronization.
+- Add tests for out of capacity (no more space on disk for additional blobs/files)
+- Pending addition of BlobFS superblob, verify that BlobFS load fails with missing or
+ corrupt superblob
+- Additional tests to reach 100% unit test coverage
+
+# System/integration tests
+
+- Use fio with BlobFS fuse module for more focused data integrity testing on individual
+ files.
+- Pending directory support (via an SPDK btree module), use BlobFS fuse module to do
+ things like a Linux kernel compilation. Performance may be poor but this will heavily
+ stress the mechanics of BlobFS.
+- Run RocksDB tests with varying amounts of BlobFS cache
diff --git a/src/spdk/test/blobstore/blob_io_wait/blob_io_wait.sh b/src/spdk/test/blobstore/blob_io_wait/blob_io_wait.sh
new file mode 100755
index 00000000..b44a3525
--- /dev/null
+++ b/src/spdk/test/blobstore/blob_io_wait/blob_io_wait.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+
+SYSTEM=`uname -s`
+if [ $SYSTEM = "FreeBSD" ] ; then
+ echo "blob_io_wait.sh cannot run on FreeBSD currently."
+ exit 0
+fi
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+rpc_py="python $rootdir/scripts/rpc.py"
+set -e
+
+timing_enter blob_bdev_io_wait
+
+truncate -s 64M $testdir/aio.bdev
+
+$rootdir/test/app/bdev_svc/bdev_svc &
+bdev_svc_pid=$!
+
+trap "killprocess $bdev_svc_pid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $bdev_svc_pid
+$rpc_py construct_aio_bdev $testdir/aio.bdev aio0 4096
+$rpc_py construct_lvol_store aio0 lvs0
+$rpc_py construct_lvol_bdev -l lvs0 lvol0 32
+
+killprocess $bdev_svc_pid
+
+# Minimal number of bdev io pool (128) and cache (1)
+echo "[Bdev]" > $testdir/bdevperf.conf
+echo "BdevIoPoolSize 128" >> $testdir/bdevperf.conf
+echo "BdevIoCacheSize 1" >> $testdir/bdevperf.conf
+echo "[AIO]" >> $testdir/bdevperf.conf
+echo "AIO $testdir/aio.bdev aio0 4096" >> $testdir/bdevperf.conf
+
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w write -t 1
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w read -t 1
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w unmap -t 1
+
+sync
+rm -rf $testdir/bdevperf.conf
+rm -rf $testdir/aio.bdev
+trap - SIGINT SIGTERM EXIT
+
+report_test_completion "blob_io_wait"
+timing_exit bdev_io_wait
diff --git a/src/spdk/test/blobstore/blobstore.sh b/src/spdk/test/blobstore/blobstore.sh
new file mode 100755
index 00000000..4ae413a1
--- /dev/null
+++ b/src/spdk/test/blobstore/blobstore.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+SYSTEM=`uname -s`
+if [ $SYSTEM = "FreeBSD" ] ; then
+ echo "blobstore.sh cannot run on FreeBSD currently."
+ exit 0
+fi
+
+set -xe
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/test/common/autotest_common.sh
+timing_enter blobstore
+
+set -e
+
+# Nvme0 target configuration
+$rootdir/scripts/gen_nvme.sh > $testdir/blobcli.conf
+
+# generate random data file for import/export diff
+dd if=/dev/urandom of=$testdir/test.pattern bs=1M count=1
+
+cd $testdir
+$rootdir/examples/blob/cli/blobcli -c $testdir/blobcli.conf -b Nvme0n1 -T $testdir/test.bs > $testdir/btest.out
+cd -
+
+# the tool leaves some trailing whitespaces that we need to strip out
+sed -i 's/[[:space:]]*$//' $testdir/btest.out
+
+# the test script will import the test pattern generated by dd and then export
+# it to a file so we can compare and confirm basic read and write
+$rootdir/test/app/match/match -v $testdir/btest.out.match
+diff $testdir/test.pattern $testdir/test.pattern.blob
+
+rm -rf $testdir/btest.out
+rm -rf $testdir/blobcli.conf
+rm -rf $testdir/*.blob
+rm -rf $testdir/test.pattern
+
+timing_exit blobstore
diff --git a/src/spdk/test/blobstore/btest.out.ignore b/src/spdk/test/blobstore/btest.out.ignore
new file mode 100644
index 00000000..d7aa1120
--- /dev/null
+++ b/src/spdk/test/blobstore/btest.out.ignore
@@ -0,0 +1,5 @@
+DPDK
+EAL
+CRYPTODEV
+
+....
diff --git a/src/spdk/test/blobstore/btest.out.match b/src/spdk/test/blobstore/btest.out.match
new file mode 100644
index 00000000..62e49287
--- /dev/null
+++ b/src/spdk/test/blobstore/btest.out.match
@@ -0,0 +1,130 @@
+Starting DPDK 17.11.0 initialization...
+[ DPDK EAL parameters: blobcli -c 0x1 --file-prefix=spdk_pid5072 ]
+EAL: Probing VFIO support...
+EAL: PCI device 0000:00:04.0 on NUMA socket 0
+EAL: probe driver: 8086:2f20 spdk_ioat
+EAL: PCI device 0000:00:04.1 on NUMA socket 0
+EAL: probe driver: 8086:2f21 spdk_ioat
+EAL: PCI device 0000:00:04.2 on NUMA socket 0
+EAL: probe driver: 8086:2f22 spdk_ioat
+EAL: PCI device 0000:00:04.3 on NUMA socket 0
+EAL: probe driver: 8086:2f23 spdk_ioat
+EAL: PCI device 0000:00:04.4 on NUMA socket 0
+EAL: probe driver: 8086:2f24 spdk_ioat
+EAL: PCI device 0000:00:04.5 on NUMA socket 0
+EAL: probe driver: 8086:2f25 spdk_ioat
+EAL: PCI device 0000:00:04.6 on NUMA socket 0
+EAL: probe driver: 8086:2f26 spdk_ioat
+EAL: PCI device 0000:00:04.7 on NUMA socket 0
+CRYPTODEV: [crypto_aesni_mb] - Creating cryptodev crypto_aesni_mb
+
+CRYPTODEV: [crypto_aesni_mb] - Initialisation parameters - name: crypto_aesni_mb,socket id: 0, max queue pairs: 8
+$(OPT)cryptodev_aesni_mb_create() line 976: IPSec Multi-buffer library version used: 0.49.0
+
+EAL: probe driver: 8086:2f27 spdk_ioat
+EAL: PCI device 0000:80:04.0 on NUMA socket 1
+EAL: probe driver: 8086:2f20 spdk_ioat
+EAL: PCI device 0000:80:04.1 on NUMA socket 1
+EAL: probe driver: 8086:2f21 spdk_ioat
+EAL: PCI device 0000:80:04.2 on NUMA socket 1
+EAL: probe driver: 8086:2f22 spdk_ioat
+EAL: PCI device 0000:80:04.3 on NUMA socket 1
+EAL: probe driver: 8086:2f23 spdk_ioat
+EAL: PCI device 0000:80:04.4 on NUMA socket 1
+EAL: probe driver: 8086:2f24 spdk_ioat
+EAL: PCI device 0000:80:04.5 on NUMA socket 1
+EAL: probe driver: 8086:2f25 spdk_ioat
+EAL: PCI device 0000:80:04.6 on NUMA socket 1
+EAL: probe driver: 8086:2f26 spdk_ioat
+EAL: PCI device 0000:80:04.7 on NUMA socket 1
+EAL: probe driver: 8086:2f27 spdk_ioat
+EAL: PCI device 0000:06:00.0 on NUMA socket 0
+EAL: probe driver: 8086:953 spdk_nvme
+
+SCRIPT NOW PROCESSING: -i
+Init blobstore using bdev Product Name: NVMe disk
+blobstore init'd: ($(XX))
+
+SCRIPT NOW PROCESSING: -l bdevs
+
+List bdevs:
+ bdev Name: Nvme0n1
+ bdev Product Name: NVMe disk
+
+
+SCRIPT NOW PROCESSING: -n 1
+New blob id 4294967296
+blob now has USED clusters of 1
+
+SCRIPT NOW PROCESSING: -p $B0
+Super Blob ID has been set.
+
+SCRIPT NOW PROCESSING: -n 1
+New blob id 4294967297
+blob now has USED clusters of 1
+
+SCRIPT NOW PROCESSING: -m $B1 test.pattern
+Working...............................................................................................................................................................................................................................................................
+Blob import complete (from test.pattern).
+
+SCRIPT NOW PROCESSING: -d $B1 test.pattern.blob
+Working................................................................................................................................................................................................................................................................
+File write complete (to test.pattern.blob).
+
+SCRIPT NOW PROCESSING: -x $B1 key val
+Xattr has been set.
+
+SCRIPT NOW PROCESSING: -s bs
+Blobstore Public Info:
+ Using bdev Product Name: NVMe disk
+ API Version: $(N)
+ super blob ID: 4294967296
+ page size: $(N)
+ io unit size: $(N)
+ cluster size: 1048576
+ # free clusters: $(N)
+ blobstore type:
+00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+
+Blobstore Private Info:
+ Metadata start (pages): $(N)
+ Metadata length (pages): $(N)
+
+SCRIPT NOW PROCESSING: -s $B1
+Blob Public Info:
+blob ID: 4294967297
+# of clusters: 1
+# of bytes: 1048576
+# of pages: 256
+# of xattrs: 1
+xattrs:
+
+(0) Name:key
+(0) Value:
+
+00000000 76 61 6c 00 val.
+
+Blob Private Info:
+state: CLEAN
+open ref count: 1
+
+SCRIPT NOW PROCESSING: -r $B1 key
+Xattr has been removed.
+
+SCRIPT NOW PROCESSING: -s bs
+Blobstore Public Info:
+ Using bdev Product Name: NVMe disk
+ API Version: 3
+ super blob ID: 4294967296
+ page size: $(N)
+ io unit size: $(N)
+ cluster size: 1048576
+ # free clusters: $(N)
+ blobstore type:
+00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+
+Blobstore Private Info:
+ Metadata start (pages): $(N)
+ Metadata length (pages): $(N)
+
+SCRIPT NOW PROCESSING: -X
diff --git a/src/spdk/test/blobstore/test.bs b/src/spdk/test/blobstore/test.bs
new file mode 100644
index 00000000..dcc64861
--- /dev/null
+++ b/src/spdk/test/blobstore/test.bs
@@ -0,0 +1,12 @@
+-i
+-l bdevs
+-n 1
+-p $B0
+-n 1
+-m $B1 test.pattern
+-d $B1 test.pattern.blob
+-x $B1 key val
+-s bs
+-s $B1
+-r $B1 key
+-s bs
diff --git a/src/spdk/test/common/autotest_common.sh b/src/spdk/test/common/autotest_common.sh
new file mode 100644
index 00000000..86511ccf
--- /dev/null
+++ b/src/spdk/test/common/autotest_common.sh
@@ -0,0 +1,706 @@
+: ${SPDK_AUTOTEST_X=true}; export SPDK_AUTOTEST_X
+
+if $SPDK_AUTOTEST_X; then
+ set -x
+fi
+
+set -e
+
+# Export flag to skip the known bug that exists in librados
+# Bug is reported on ceph bug tracker with number 24078
+export ASAN_OPTIONS=new_delete_type_mismatch=0
+
+PS4=' \t \$ '
+ulimit -c unlimited
+
+: ${RUN_NIGHTLY:=0}
+export RUN_NIGHTLY
+
+: ${RUN_NIGHTLY_FAILING:=0}
+export RUN_NIGHTLY_FAILING
+
+if [[ ! -z $1 ]]; then
+ if [ -f $1 ]; then
+ source $1
+ fi
+fi
+
+# If certain utilities are not installed, preemptively disable the tests
+if ! hash ceph; then
+ SPDK_TEST_RBD=0
+fi
+
+if ! hash pmempool; then
+ SPDK_TEST_PMDK=0
+fi
+
+# Set defaults for missing test config options
+: ${SPDK_BUILD_DOC=1}; export SPDK_BUILD_DOC
+: ${SPDK_BUILD_SHARED_OBJECT=1}; export SPDK_BUILD_SHARED_OBJECT
+: ${SPDK_RUN_CHECK_FORMAT=1}; export SPDK_RUN_CHECK_FORMAT
+: ${SPDK_RUN_SCANBUILD=1}; export SPDK_RUN_SCANBUILD
+: ${SPDK_RUN_VALGRIND=1}; export SPDK_RUN_VALGRIND
+: ${SPDK_TEST_UNITTEST=1}; export SPDK_TEST_UNITTEST
+: ${SPDK_TEST_ISCSI=1}; export SPDK_TEST_ISCSI
+: ${SPDK_TEST_ISCSI_INITIATOR=1}; export SPDK_TEST_ISCSI_INITIATOR
+: ${SPDK_TEST_NVME=1}; export SPDK_TEST_NVME
+: ${SPDK_TEST_NVME_CLI=1}; export SPDK_TEST_NVME_CLI
+: ${SPDK_TEST_NVMF=1}; export SPDK_TEST_NVMF
+: ${SPDK_TEST_RBD=1}; export SPDK_TEST_RBD
+: ${SPDK_TEST_VHOST=1}; export SPDK_TEST_VHOST
+: ${SPDK_TEST_BLOCKDEV=1}; export SPDK_TEST_BLOCKDEV
+: ${SPDK_TEST_IOAT=1}; export SPDK_TEST_IOAT
+: ${SPDK_TEST_EVENT=1}; export SPDK_TEST_EVENT
+: ${SPDK_TEST_BLOBFS=1}; export SPDK_TEST_BLOBFS
+: ${SPDK_TEST_VHOST_INIT=1}; export SPDK_TEST_VHOST_INIT
+: ${SPDK_TEST_PMDK=1}; export SPDK_TEST_PMDK
+: ${SPDK_TEST_LVOL=1}; export SPDK_TEST_LVOL
+: ${SPDK_TEST_JSON=1}; export SPDK_TEST_JSON
+: ${SPDK_RUN_ASAN=1}; export SPDK_RUN_ASAN
+: ${SPDK_RUN_UBSAN=1}; export SPDK_RUN_UBSAN
+: ${SPDK_RUN_INSTALLED_DPDK=1}; export SPDK_RUN_INSTALLED_DPDK
+: ${SPDK_TEST_CRYPTO=1}; export SPDK_TEST_CRYPTO
+
+if [ -z "$DEPENDENCY_DIR" ]; then
+ export DEPENDENCY_DIR=/home/sys_sgsw
+else
+ export DEPENDENCY_DIR
+fi
+
+if [ ! -z "$HUGEMEM" ]; then
+ export HUGEMEM
+fi
+
+# pass our valgrind desire on to unittest.sh
+if [ $SPDK_RUN_VALGRIND -eq 0 ]; then
+ export valgrind=''
+fi
+
+config_params='--enable-debug --enable-werror'
+
+if echo -e "#include <libunwind.h>\nint main(int argc, char *argv[]) {return 0;}\n" | \
+ gcc -o /dev/null -lunwind -x c - 2>/dev/null; then
+ config_params+=' --enable-log-bt'
+fi
+
+if [ $SPDK_TEST_CRYPTO -eq 1 ]; then
+ config_params+=' --with-crypto'
+fi
+
+export UBSAN_OPTIONS='halt_on_error=1:print_stacktrace=1:abort_on_error=1'
+
+# On Linux systems, override the default HUGEMEM in scripts/setup.sh to
+# allocate 8GB in hugepages.
+# FreeBSD runs a much more limited set of tests, so keep the default 2GB.
+if [ `uname -s` = "Linux" ]; then
+ export HUGEMEM=8192
+fi
+
+DEFAULT_RPC_ADDR=/var/tmp/spdk.sock
+
+case `uname` in
+ FreeBSD)
+ DPDK_FREEBSD_DIR=/usr/local/share/dpdk/x86_64-native-bsdapp-clang
+ if [ -d $DPDK_FREEBSD_DIR ] && [ $SPDK_RUN_INSTALLED_DPDK -eq 1 ]; then
+ WITH_DPDK_DIR=$DPDK_FREEBSD_DIR
+ fi
+ MAKE=gmake
+ MAKEFLAGS=${MAKEFLAGS:--j$(sysctl -a | egrep -i 'hw.ncpu' | awk '{print $2}')}
+ SPDK_RUN_ASAN=0
+ SPDK_RUN_UBSAN=0
+ ;;
+ Linux)
+ DPDK_LINUX_DIR=/usr/share/dpdk/x86_64-default-linuxapp-gcc
+ if [ -d $DPDK_LINUX_DIR ] && [ $SPDK_RUN_INSTALLED_DPDK -eq 1 ]; then
+ WITH_DPDK_DIR=$DPDK_LINUX_DIR
+ fi
+ MAKE=make
+ MAKEFLAGS=${MAKEFLAGS:--j$(nproc)}
+ config_params+=' --enable-coverage'
+ if [ $SPDK_RUN_UBSAN -eq 1 ]; then
+ config_params+=' --enable-ubsan'
+ fi
+ if [ $SPDK_RUN_ASAN -eq 1 ]; then
+ if ldconfig -p | grep -q asan; then
+ config_params+=' --enable-asan'
+ else
+ SPDK_RUN_ASAN=0
+ fi
+ fi
+ ;;
+ *)
+ echo "Unknown OS in $0"
+ exit 1
+ ;;
+esac
+
+# By default, --with-dpdk is not set meaning the SPDK build will use the DPDK submodule.
+# If a DPDK installation is found in a well-known location though, WITH_DPDK_DIR will be
+# set which will override the default and use that DPDK installation instead.
+if [ ! -z "$WITH_DPDK_DIR" ]; then
+ config_params+=" --with-dpdk=$WITH_DPDK_DIR"
+fi
+
+if [ -f /usr/include/infiniband/verbs.h ]; then
+ config_params+=' --with-rdma'
+fi
+
+if [ -f /usr/include/libpmemblk.h ]; then
+ config_params+=' --with-pmdk'
+else
+ # PMDK not installed so disable PMDK tests explicitly here
+ SPDK_TEST_PMDK=0; export SPDK_TEST_PMDK
+fi
+
+if [ -d /usr/src/fio ]; then
+ config_params+=' --with-fio=/usr/src/fio'
+fi
+
+if [ -d ${DEPENDENCY_DIR}/vtune_codes ]; then
+ config_params+=' --with-vtune='${DEPENDENCY_DIR}'/vtune_codes'
+fi
+
+if [ -d /usr/include/rbd ] && [ -d /usr/include/rados ]; then
+ config_params+=' --with-rbd'
+fi
+
+if [ -d /usr/include/iscsi ]; then
+ libiscsi_version=`grep LIBISCSI_API_VERSION /usr/include/iscsi/iscsi.h | head -1 | awk '{print $3}' | awk -F '(' '{print $2}' | awk -F ')' '{print $1}'`
+ if [ $libiscsi_version -ge 20150621 ]; then
+ config_params+=' --with-iscsi-initiator'
+ else
+ export SPDK_TEST_ISCSI_INITIATOR=0
+ fi
+else
+ export SPDK_TEST_ISCSI_INITIATOR=0
+fi
+
+if [ ! -d "${DEPENDENCY_DIR}/nvme-cli" ]; then
+ export SPDK_TEST_NVME_CLI=0
+fi
+
+export config_params
+
+if [ -z "$output_dir" ]; then
+ if [ -z "$rootdir" ] || [ ! -d "$rootdir/../output" ]; then
+ output_dir=.
+ else
+ output_dir=$rootdir/../output
+ fi
+ export output_dir
+fi
+
+function timing() {
+ direction="$1"
+ testname="$2"
+
+ now=$(date +%s)
+
+ if [ "$direction" = "enter" ]; then
+ export timing_stack="${timing_stack};${now}"
+ export test_stack="${test_stack};${testname}"
+ else
+ child_time=$(grep "^${test_stack:1};" $output_dir/timing.txt | awk '{s+=$2} END {print s}')
+
+ start_time=$(echo "$timing_stack" | sed -e 's@^.*;@@')
+ timing_stack=$(echo "$timing_stack" | sed -e 's@;[^;]*$@@')
+
+ elapsed=$((now - start_time - child_time))
+ echo "${test_stack:1} $elapsed" >> $output_dir/timing.txt
+
+ test_stack=$(echo "$test_stack" | sed -e 's@;[^;]*$@@')
+ fi
+}
+
+function timing_enter() {
+ set +x
+ timing "enter" "$1"
+ set -x
+}
+
+function timing_exit() {
+ set +x
+ timing "exit" "$1"
+ set -x
+}
+
+function timing_finish() {
+ flamegraph='/usr/local/FlameGraph/flamegraph.pl'
+ if [ -x "$flamegraph" ]; then
+ "$flamegraph" \
+ --title 'Build Timing' \
+ --nametype 'Step:' \
+ --countname seconds \
+ $output_dir/timing.txt \
+ >$output_dir/timing.svg
+ fi
+}
+
+function create_test_list() {
+ grep -rsh --exclude="autotest_common.sh" --exclude="$rootdir/test/common/autotest_common.sh" -e "report_test_completion" $rootdir | sed 's/report_test_completion//g; s/[[:blank:]]//g; s/"//g;' > $output_dir/all_tests.txt || true
+}
+
+function report_test_completion() {
+ echo "$1" >> $output_dir/test_completions.txt
+}
+
+function process_core() {
+ ret=0
+ for core in $(find . -type f \( -name 'core*' -o -name '*.core' \)); do
+ exe=$(eu-readelf -n "$core" | grep psargs | sed "s/.*psargs: \([^ \'\" ]*\).*/\1/")
+ if [[ ! -f "$exe" ]]; then
+ exe=$(eu-readelf -n "$core" | grep -oP -m1 "$exe.+")
+ fi
+ echo "exe for $core is $exe"
+ if [[ ! -z "$exe" ]]; then
+ if hash gdb; then
+ gdb -batch -ex "thread apply all bt full" $exe $core
+ fi
+ cp $exe $output_dir
+ fi
+ mv $core $output_dir
+ chmod a+r $output_dir/$core
+ ret=1
+ done
+ return $ret
+}
+
+function process_shm() {
+ type=$1
+ id=$2
+ if [ "$type" = "--pid" ]; then
+ id="pid${id}"
+ elif [ "$type" = "--id" ]; then
+ id="${id}"
+ else
+ echo "Please specify to search for pid or shared memory id."
+ return 1
+ fi
+
+ shm_files=$(find /dev/shm -name "*.${id}" -printf "%f\n")
+
+ if [[ -z $shm_files ]]; then
+ echo "SHM File for specified PID or shared memory id: ${id} not found!"
+ return 1
+ fi
+ for n in $shm_files; do
+ tar -C /dev/shm/ -cvzf $output_dir/${n}_shm.tar.gz ${n}
+ done
+ return 0
+}
+
+function waitforlisten() {
+ # $1 = process pid
+ if [ -z "$1" ]; then
+ exit 1
+ fi
+
+ rpc_addr="${2:-$DEFAULT_RPC_ADDR}"
+
+ echo "Waiting for process to start up and listen on UNIX domain socket $rpc_addr..."
+ # turn off trace for this loop
+ set +x
+ ret=1
+ while [ $ret -ne 0 ]; do
+ # if the process is no longer running, then exit the script
+ # since it means the application crashed
+ if ! kill -s 0 $1; then
+ exit 1
+ fi
+
+ namespace=$(ip netns identify $1)
+ if [ -n "$namespace" ]; then
+ ns_cmd="ip netns exec $namespace"
+ fi
+
+ if hash ss; then
+ if $ns_cmd ss -lx | grep -q $rpc_addr; then
+ ret=0
+ fi
+ else
+ # if system doesn't have ss, just assume it has netstat
+ if $ns_cmd netstat -an -x | grep -iw LISTENING | grep -q $rpc_addr; then
+ ret=0
+ fi
+ fi
+ done
+ set -x
+}
+
+function waitfornbd() {
+ nbd_name=$1
+
+ for ((i=1; i<=20; i++)); do
+ if grep -q -w $nbd_name /proc/partitions; then
+ break
+ else
+ sleep 0.1
+ fi
+ done
+
+ # The nbd device is now recognized as a block device, but there can be
+ # a small delay before we can start I/O to that block device. So loop
+ # here trying to read the first block of the nbd block device to a temp
+ # file. Note that dd returns success when reading an empty file, so we
+ # need to check the size of the output file instead.
+ for ((i=1; i<=20; i++)); do
+ dd if=/dev/$nbd_name of=/tmp/nbdtest bs=4096 count=1 iflag=direct
+ size=`stat -c %s /tmp/nbdtest`
+ rm -f /tmp/nbdtest
+ if [ "$size" != "0" ]; then
+ return 0
+ else
+ sleep 0.1
+ fi
+ done
+
+ return 1
+}
+
+function killprocess() {
+ # $1 = process pid
+ if [ -z "$1" ]; then
+ exit 1
+ fi
+
+ if kill -0 $1; then
+ echo "killing process with pid $1"
+ kill $1
+ wait $1
+ fi
+}
+
+function iscsicleanup() {
+ echo "Cleaning up iSCSI connection"
+ iscsiadm -m node --logout || true
+ iscsiadm -m node -o delete || true
+}
+
+function stop_iscsi_service() {
+ if cat /etc/*-release | grep Ubuntu; then
+ service open-iscsi stop
+ else
+ service iscsid stop
+ fi
+}
+
+function start_iscsi_service() {
+ if cat /etc/*-release | grep Ubuntu; then
+ service open-iscsi start
+ else
+ service iscsid start
+ fi
+}
+
+function rbd_setup() {
+ # $1 = monitor ip address
+ # $2 = name of the namespace
+ if [ -z "$1" ]; then
+ echo "No monitor IP address provided for ceph"
+ exit 1
+ fi
+ if [ -n "$2" ]; then
+ if ip netns list | grep "$2"; then
+ NS_CMD="ip netns exec $2"
+ else
+ echo "No namespace $2 exists"
+ exit 1
+ fi
+ fi
+
+ if hash ceph; then
+ export PG_NUM=128
+ export RBD_POOL=rbd
+ export RBD_NAME=foo
+ $NS_CMD $rootdir/scripts/ceph/start.sh $1
+
+ $NS_CMD ceph osd pool create $RBD_POOL $PG_NUM || true
+ $NS_CMD rbd create $RBD_NAME --size 1000
+ fi
+}
+
+function rbd_cleanup() {
+ if hash ceph; then
+ $rootdir/scripts/ceph/stop.sh || true
+ fi
+}
+
+function start_stub() {
+ # Disable ASLR for multi-process testing. SPDK does support using DPDK multi-process,
+ # but ASLR can still be unreliable in some cases.
+ # We will reenable it again after multi-process testing is complete in kill_stub()
+ echo 0 > /proc/sys/kernel/randomize_va_space
+ $rootdir/test/app/stub/stub $1 &
+ stubpid=$!
+ echo Waiting for stub to ready for secondary processes...
+ while ! [ -e /var/run/spdk_stub0 ]; do
+ sleep 1s
+ done
+ # dump process memory map contents to help debug random ASLR failures
+ pmap -pX $stubpid || pmap -x $stubpid || true
+ echo done.
+}
+
+function kill_stub() {
+ kill $stubpid
+ wait $stubpid
+ rm -f /var/run/spdk_stub0
+ # Re-enable ASLR now that we are done with multi-process testing
+ # Note: "1" enables ASLR w/o randomizing data segments, "2" adds data segment
+ # randomizing and is the default on all recent Linux kernels
+ echo 2 > /proc/sys/kernel/randomize_va_space
+}
+
+function run_test() {
+ set +x
+ local test_type="$(echo $1 | tr 'a-z' 'A-Z')"
+ shift
+ echo "************************************"
+ echo "START TEST $test_type $@"
+ echo "************************************"
+ set -x
+ time "$@"
+ set +x
+ echo "************************************"
+ echo "END TEST $test_type $@"
+ echo "************************************"
+ set -x
+}
+
+function print_backtrace() {
+ # if errexit is not enabled, don't print a backtrace
+ [[ "$-" =~ e ]] || return 0
+
+ local shell_options="$-"
+ set +x
+ echo "========== Backtrace start: =========="
+ echo ""
+ for i in $(seq 1 $((${#FUNCNAME[@]} - 1))); do
+ local func="${FUNCNAME[$i]}"
+ local line_nr="${BASH_LINENO[$((i - 1))]}"
+ local src="${BASH_SOURCE[$i]}"
+ echo "in $src:$line_nr -> $func()"
+ echo " ..."
+ nl -w 4 -ba -nln $src | grep -B 5 -A 5 "^$line_nr[^0-9]" | \
+ sed "s/^/ /g" | sed "s/^ $line_nr /=> $line_nr /g"
+ echo " ..."
+ done
+ echo ""
+ echo "========== Backtrace end =========="
+ [[ "$shell_options" =~ x ]] && set -x
+ return 0
+}
+
+function part_dev_by_gpt () {
+ if [ $(uname -s) = Linux ] && hash sgdisk && modprobe nbd; then
+ conf=$1
+ devname=$2
+ rootdir=$3
+ operation=$4
+ local nbd_path=/dev/nbd0
+ local rpc_server=/var/tmp/spdk-gpt-bdevs.sock
+
+ if [ ! -e $conf ]; then
+ return 1
+ fi
+
+ if [ -z "$operation" ]; then
+ operation="create"
+ fi
+
+ cp $conf ${conf}.gpt
+ echo "[Gpt]" >> ${conf}.gpt
+ echo " Disable Yes" >> ${conf}.gpt
+
+ $rootdir/test/app/bdev_svc/bdev_svc -r $rpc_server -i 0 -c ${conf}.gpt &
+ nbd_pid=$!
+ echo "Process nbd pid: $nbd_pid"
+ waitforlisten $nbd_pid $rpc_server
+
+ # Start bdev as a nbd device
+ $rootdir/scripts/rpc.py -s "$rpc_server" start_nbd_disk $devname $nbd_path
+
+ waitfornbd ${nbd_path:5}
+
+ if [ "$operation" = create ]; then
+ parted -s $nbd_path mklabel gpt mkpart first '0%' '50%' mkpart second '50%' '100%'
+
+ # change the GUID to SPDK GUID value
+ SPDK_GPT_GUID=`grep SPDK_GPT_PART_TYPE_GUID $rootdir/lib/bdev/gpt/gpt.h \
+ | awk -F "(" '{ print $2}' | sed 's/)//g' \
+ | awk -F ", " '{ print $1 "-" $2 "-" $3 "-" $4 "-" $5}' | sed 's/0x//g'`
+ sgdisk -t 1:$SPDK_GPT_GUID $nbd_path
+ sgdisk -t 2:$SPDK_GPT_GUID $nbd_path
+ elif [ "$operation" = reset ]; then
+ # clear the partition table
+ dd if=/dev/zero of=$nbd_path bs=4096 count=8 oflag=direct
+ fi
+
+ $rootdir/scripts/rpc.py -s "$rpc_server" stop_nbd_disk $nbd_path
+
+ killprocess $nbd_pid
+ rm -f ${conf}.gpt
+ fi
+
+ return 0
+}
+
+function discover_bdevs()
+{
+ local rootdir=$1
+ local config_file=$2
+ local rpc_server=/var/tmp/spdk-discover-bdevs.sock
+
+ if [ ! -e $config_file ]; then
+ echo "Invalid Configuration File: $config_file"
+ return -1
+ fi
+
+ # Start the bdev service to query for the list of available
+ # bdevs.
+ $rootdir/test/app/bdev_svc/bdev_svc -r $rpc_server -i 0 \
+ -c $config_file &>/dev/null &
+ stubpid=$!
+ while ! [ -e /var/run/spdk_bdev0 ]; do
+ sleep 1
+ done
+
+ # Get all of the bdevs
+ if [ -z "$rpc_server" ]; then
+ $rootdir/scripts/rpc.py get_bdevs
+ else
+ $rootdir/scripts/rpc.py -s "$rpc_server" get_bdevs
+ fi
+
+ # Shut down the bdev service
+ kill $stubpid
+ wait $stubpid
+ rm -f /var/run/spdk_bdev0
+}
+
+function waitforblk()
+{
+ local i=0
+ while ! lsblk -l -o NAME | grep -q -w $1; do
+ [ $i -lt 15 ] || break
+ i=$[$i+1]
+ sleep 1
+ done
+
+ if ! lsblk -l -o NAME | grep -q -w $1; then
+ return 1
+ fi
+
+ return 0
+}
+
+function fio_config_gen()
+{
+ local config_file=$1
+ local workload=$2
+
+ if [ -e "$config_file" ]; then
+ echo "Configuration File Already Exists!: $config_file"
+ return -1
+ fi
+
+ if [ -z "$workload" ]; then
+ workload=randrw
+ fi
+
+ touch $1
+
+ cat > $1 << EOL
+[global]
+thread=1
+group_reporting=1
+direct=1
+norandommap=1
+percentile_list=50:99:99.9:99.99:99.999
+time_based=1
+ramp_time=0
+EOL
+
+ if [ "$workload" == "verify" ]; then
+ echo "verify=sha1" >> $config_file
+ echo "rw=randwrite" >> $config_file
+ elif [ "$workload" == "trim" ]; then
+ echo "rw=trimwrite" >> $config_file
+ else
+ echo "rw=$workload" >> $config_file
+ fi
+}
+
+function fio_config_add_job()
+{
+ config_file=$1
+ filename=$2
+
+ if [ ! -e "$config_file" ]; then
+ echo "Configuration File Doesn't Exist: $config_file"
+ return -1
+ fi
+
+ if [ -z "$filename" ]; then
+ echo "No filename provided"
+ return -1
+ fi
+
+ echo "[job_$filename]" >> $config_file
+ echo "filename=$filename" >> $config_file
+}
+
+function get_lvs_free_mb()
+{
+ local lvs_uuid=$1
+ local lvs_info=$($rpc_py get_lvol_stores)
+ local fc=$(jq ".[] | select(.uuid==\"$lvs_uuid\") .free_clusters" <<< "$lvs_info")
+ local cs=$(jq ".[] | select(.uuid==\"$lvs_uuid\") .cluster_size" <<< "$lvs_info")
+
+ # Change to MB's
+ free_mb=$((fc*cs/1024/1024))
+ echo "$free_mb"
+}
+
+function get_bdev_size()
+{
+ local bdev_name=$1
+ local bdev_info=$($rpc_py get_bdevs -b $bdev_name)
+ local bs=$(jq ".[] .block_size" <<< "$bdev_info")
+ local nb=$(jq ".[] .num_blocks" <<< "$bdev_info")
+
+ # Change to MB's
+ bdev_size=$((bs*nb/1024/1024))
+ echo "$bdev_size"
+}
+
+function autotest_cleanup()
+{
+ $rootdir/scripts/setup.sh reset
+ $rootdir/scripts/setup.sh cleanup
+ if [ $(uname -s) = "Linux" ]; then
+ if grep -q '#define SPDK_CONFIG_IGB_UIO_DRIVER 1' $rootdir/include/spdk/config.h; then
+ rmmod igb_uio
+ else
+ modprobe -r uio_pci_generic
+ fi
+ fi
+}
+
+function freebsd_update_contigmem_mod()
+{
+ if [ `uname` = FreeBSD ]; then
+ kldunload contigmem.ko || true
+ if [ ! -z "$WITH_DPDK_DIR" ]; then
+ echo "Warning: SPDK only works on FreeBSD with patches that only exist in SPDK's dpdk submodule"
+ cp -f "$WITH_DPDK_DIR/kmod/contigmem.ko" /boot/modules/
+ cp -f "$WITH_DPDK_DIR/kmod/contigmem.ko" /boot/kernel/
+ else
+ cp -f "$rootdir/dpdk/build/kmod/contigmem.ko" /boot/modules/
+ cp -f "$rootdir/dpdk/build/kmod/contigmem.ko" /boot/kernel/
+ fi
+ fi
+}
+
+set -o errtrace
+trap "trap - ERR; print_backtrace >&2" ERR
diff --git a/src/spdk/test/common/config/README.md b/src/spdk/test/common/config/README.md
new file mode 100644
index 00000000..609f9de8
--- /dev/null
+++ b/src/spdk/test/common/config/README.md
@@ -0,0 +1,99 @@
+# Virtual Test Configuration
+
+This readme and the associated bash script, vm_setup.sh, are intended to assist developers in quickly
+preparing a virtual test environment on which to run the SPDK validation tests rooted at autorun.sh.
+This file contains basic information about SPDK environment requirements, an introduction to the
+autorun-spdk.conf files used to moderate which tests are run by autorun.sh, and step-by-step instructions
+for spinning up a VM capable of running the SPDK test suite.
+There is no need for external hardware to run these tests. The linux kernel comes with the drivers necessary
+to emulate an RDMA enabled NIC. NVMe controllers can also be virtualized in emulators such as QEMU.
+
+
+## VM Envronment Requirements (Host):
+- 8 GiB of RAM (for DPDK)
+- Enable intel_kvm on the host machine from the bios.
+- Enable nesting for VMs in kernel command line (for vhost tests).
+ - In `/etc/default/grub` append the following to the GRUB_CMDLINE_LINUX line: intel_iommu=on kvm-intel.nested=1.
+
+## VM Specs
+When creating the user during the fedora installation, it is best to use the name sys_sgsw. Efforts are being made
+to remove all references to this user, or files specific to this user from the codebase, but there are still some
+trailing references to it.
+
+## Autorun-spdk.conf
+Every machine that runs the autotest scripts should include a file titled autorun-spdk.conf in the home directory
+of the user that will run them. This file consists of several lines of the form 'variable_name=0/1'. autorun.sh sources
+this file each time it is run, and determines which tests to attempt based on which variables are defined in the
+configuration file. For a full list of the variable declarations available for autorun-spdk.conf, please see
+`test/common/autotest_common.sh` starting at line 13.
+
+## Steps for Configuring the VM
+1. Download a fresh Fedora 26 image.
+2. Perform the installation of Fedora 26 server.
+3. Create an admin user sys_sgsw (enabling passwordless sudo for this account will make life easier during the tests).
+4. Run the vm_setup.sh script which will install all proper dependencies.
+5. Modify the autorun-spdk.conf file in the home directory.
+6. Reboot the VM.
+7. Run autorun.sh for SPDK. Any output files will be placed in `~/spdk_repo/output/`.
+
+## Additional Steps for Preparing the Vhost Tests
+The Vhost tests also require the creation of a second virtual machine nested inside of the test VM.
+Please follow the directions below to complete that installation. Note that host refers to the Fedora VM
+created above and guest or VM refer to the Ubuntu VM created in this section.
+
+1. Follow instructions from spdk/scripts/vagrant/README.md
+ - install all needed packages mentioned in "Mac OSX Setup" or "Windows 10 Setup" sections
+ - follow steps from "Configure Vagrant" section
+
+2. Use Vagrant scripts located in spdk/scripts/vagrant to automatically generate
+ VM image to use in SPDK vhost tests.
+ Example command:
+ ~~~{.sh}
+ spdk/scripts/vagrant/create_vhost_vm.sh --move-to-def-dirs ubuntu16
+ ~~~
+ This command will:
+ - Download a Ubuntu 16.04 image file
+ - upgrade the system and install needed dependencies (fio, sg3-utils, bc)
+ - add entry to VM's ~/.ssh/autorized_keys
+ - add appropriate options to GRUB command line and update grub
+ - convert the image to .qcow2 format
+ - move .qcow2 file and ssh keys to default locations used by vhost test scripts
+
+Alternatively it is possible to create the VM image manually using following steps:
+1. Create an image file for the VM. It does not have to be large, about 3.5G should suffice.
+2. Create an ssh keypair for host-guest communications (performed on the host):
+ - Generate an ssh keypair with the name spdk_vhost_id_rsa and save it in `/root/.ssh`.
+ - Make sure that only root has read access to the private key.
+3. Install the OS in the VM image (performed on guest):
+ - Use the latest Ubuntu server (Currently 16.04 LTS).
+ - When partitioning the disk, make one partion that consumes the whole disk mounted at /. Do not encrypt the disk or enable LVM.
+ - Choose the OpenSSH server packages during install.
+4. Post installation configuration (performed on guest):
+ - Run the following commands to enable all necessary dependencies:
+ ~~~{.sh}
+ sudo apt update
+ sudo apt upgrade
+ sudo apt install fio sg3-utils bc
+ ~~~
+ - Enable the root user: "sudo passwd root -> root".
+ - Enable root login over ssh: vim `/etc/ssh/sshd_config` -> PermitRootLogin=yes.
+ - Disable DNS for ssh: `/etc/ssh/sshd_config` -> UseDNS=no.
+ - Add the spdk_vhost key to root's known hosts: `/root/.ssh/authorized_keys` -> add spdk_vhost_id_rsa.pub key to authorized keys.
+ Remember to save the private key in `~/.ssh/spdk_vhost_id_rsa` on the host.
+ - Change the grub boot options for the guest as follows:
+ - Add "console=ttyS0 earlyprintk=ttyS0" to the boot options in `/etc/default/grub` (for serial output redirect).
+ - Add "scsi_mod.use_blk_mq=1" to boot options in `/etc/default/grub`.
+ ~~~{.sh}
+ sudo update-grub
+ ~~~
+ - Reboot the VM.
+ - Remove any unnecessary packages (this is to make booting the VM faster):
+ ~~~{.sh}
+ apt purge snapd
+ apt purge Ubuntu-core-launcher
+ apt purge squashfs-tools
+ apt purge unattended-upgrades
+ ~~~
+5. Copy the fio binary from the guest location `/usr/bin/fio` to the host location `/home/sys_sgsw/fio_ubuntu`.
+6. Place the guest VM in the host at the following location: `/home/sys_sgsw/vhost_vm_image.qcow2`.
+7. On the host, edit the `~/autorun-spdk.conf` file to include the following line: SPDK_TEST_VHOST=1.
diff --git a/src/spdk/test/common/config/vm_setup.conf b/src/spdk/test/common/config/vm_setup.conf
new file mode 100644
index 00000000..40acea66
--- /dev/null
+++ b/src/spdk/test/common/config/vm_setup.conf
@@ -0,0 +1,12 @@
+# This configuration file is provided for reference purposes.
+GIT_REPO_SPDK=https://review.gerrithub.io/spdk/spdk
+GIT_REPO_DPDK=https://github.com/spdk/dpdk.git
+GIT_REPO_LIBRXE=https://github.com/SoftRoCE/librxe-dev.git
+GIT_REPO_OPEN_ISCSI=https://github.com/open-iscsi/open-iscsi
+GIT_REPO_ROCKSDB=https://review.gerrithub.io/spdk/rocksdb
+GIT_REPO_FIO=http://git.kernel.dk/fio.git
+GIT_REPO_FLAMEGRAPH=https://github.com/brendangregg/FlameGraph.git
+GIT_REPO_QEMU=https://github.com/spdk/qemu
+GIT_REPO_VPP=https://gerrit.fd.io/r/vpp
+GIT_REPO_LIBISCSI=https://github.com/sahlberg/libiscsi
+GIT_REPO_SPDK_NVME_CLI=https://github.com/spdk/nvme-cli
diff --git a/src/spdk/test/common/config/vm_setup.sh b/src/spdk/test/common/config/vm_setup.sh
new file mode 100755
index 00000000..e01b8879
--- /dev/null
+++ b/src/spdk/test/common/config/vm_setup.sh
@@ -0,0 +1,424 @@
+#!/usr/bin/env bash
+
+# Virtual Machine environment requirements:
+# 8 GiB of RAM (for DPDK)
+# enable intel_kvm on your host machine
+
+# The purpose of this script is to provide a simple procedure for spinning up a new
+# virtual test environment capable of running our whole test suite. This script, when
+# applied to a fresh install of fedora 26 server will install all of the necessary dependencies
+# to run almost the complete test suite. The main exception being VHost. Vhost requires the
+# configuration of a second virtual machine. instructions for how to configure
+# that vm are included in the file TEST_ENV_SETUP_README inside this repository
+
+# it is important to enable nesting for vms in kernel command line of your machine for the vhost tests.
+# in /etc/default/grub
+# append the following to the GRUB_CMDLINE_LINUX line
+# intel_iommu=on kvm-intel.nested=1
+
+# We have made a lot of progress with removing hardcoded paths from the tests,
+
+set -e
+
+VM_SETUP_PATH=$(readlink -f ${BASH_SOURCE%/*})
+
+UPGRADE=false
+INSTALL=false
+CONF="librxe,iscsi,rocksdb,fio,flamegraph,tsocks,qemu,vpp,libiscsi,nvmecli"
+
+function install_rxe_cfg()
+{
+ if echo $CONF | grep -q librxe; then
+ # rxe_cfg is used in the NVMe-oF tests
+ # The librxe-dev repository provides a command line tool called rxe_cfg which makes it
+ # very easy to use Soft-RoCE. The build pool utilizes this command line tool in the absence
+ # of any real RDMA NICs to simulate one for the NVMe-oF tests.
+ if hash rxe_cfg 2> /dev/null; then
+ echo "rxe_cfg is already installed. skipping"
+ else
+ if [ -d librxe-dev ]; then
+ echo "librxe-dev source already present, not cloning"
+ else
+ git clone "${GIT_REPO_LIBRXE}"
+ fi
+
+ ./librxe-dev/configure --libdir=/usr/lib64/ --prefix=
+ make -C librxe-dev -j${jobs}
+ sudo make -C librxe-dev install
+ fi
+ fi
+}
+
+function install_iscsi_adm()
+{
+ if echo $CONF | grep -q iscsi; then
+ # iscsiadm is used in the iscsi_tgt tests
+ # The version of iscsiadm that ships with fedora 26 was broken as of November 3 2017.
+ # There is already a bug report out about it, and hopefully it is fixed soon, but in the event that
+ # that version is still broken when you do your setup, the below steps will fix the issue.
+ CURRENT_VERSION=$(iscsiadm --version)
+ OPEN_ISCSI_VER='iscsiadm version 6.2.0.874'
+ if [ "$CURRENT_VERSION" == "$OPEN_ISCSI_VER" ]; then
+ if [ ! -d open-iscsi-install ]; then
+ mkdir -p open-iscsi-install/patches
+ sudo dnf download --downloaddir=./open-iscsi-install --source iscsi-initiator-utils
+ rpm2cpio open-iscsi-install/$(ls ~/open-iscsi-install) | cpio -D open-iscsi-install -idmv
+ mv open-iscsi-install/00* open-iscsi-install/patches/
+ git clone "${GIT_REPO_OPEN_ISCSI}" open-iscsi-install/open-iscsi
+
+ # the configurations of username and email are needed for applying patches to iscsiadm.
+ git -C open-iscsi-install/open-iscsi config user.name none
+ git -C open-iscsi-install/open-iscsi config user.email none
+
+ git -C open-iscsi-install/open-iscsi checkout 86e8892
+ for patch in `ls open-iscsi-install/patches`; do
+ git -C open-iscsi-install/open-iscsi am ../patches/$patch
+ done
+ sed -i '427s/.*/-1);/' open-iscsi-install/open-iscsi/usr/session_info.c
+ make -C open-iscsi-install/open-iscsi -j${jobs}
+ sudo make -C open-iscsi-install/open-iscsi install
+ else
+ echo "custom open-iscsi install located, not reinstalling"
+ fi
+ fi
+ fi
+}
+
+function install_rocksdb()
+{
+ if echo $CONF | grep -q rocksdb; then
+ # Rocksdb is installed for use with the blobfs tests.
+ if [ ! -d /usr/src/rocksdb ]; then
+ git clone "${GIT_REPO_ROCKSDB}"
+ git -C ./rocksdb checkout spdk-v5.6.1
+ sudo mv rocksdb /usr/src/
+ else
+ sudo git -C /usr/src/rocksdb checkout spdk-v5.6.1
+ echo "rocksdb already in /usr/src. Not checking out again"
+ fi
+ fi
+}
+
+function install_fio()
+{
+ if echo $CONF | grep -q fio; then
+ # This version of fio is installed in /usr/src/fio to enable
+ # building the spdk fio plugin.
+ if [ ! -d /usr/src/fio ]; then
+ if [ ! -d fio ]; then
+ git clone "${GIT_REPO_FIO}"
+ sudo mv fio /usr/src/
+ else
+ sudo mv fio /usr/src/
+ fi
+ (
+ git -C /usr/src/fio checkout master &&
+ git -C /usr/src/fio pull &&
+ git -C /usr/src/fio checkout fio-3.3 &&
+ make -C /usr/src/fio -j${jobs} &&
+ sudo make -C /usr/src/fio install
+ )
+ else
+ echo "fio already in /usr/src/fio. Not installing"
+ fi
+ fi
+}
+
+function install_flamegraph()
+{
+ if echo $CONF | grep -q flamegraph; then
+ # Flamegraph is used when printing out timing graphs for the tests.
+ if [ ! -d /usr/local/FlameGraph ]; then
+ git clone "${GIT_REPO_FLAMEGRAPH}"
+ mkdir -p /usr/local
+ sudo mv FlameGraph /usr/local/FlameGraph
+ else
+ echo "flamegraph already installed. Skipping"
+ fi
+ fi
+}
+
+function install_qemu()
+{
+ if echo $CONF | grep -q qemu; then
+ # Qemu is used in the vhost tests.
+ SPDK_QEMU_BRANCH=spdk-2.12
+ mkdir -p qemu
+ if [ ! -d "qemu/$SPDK_QEMU_BRANCH" ]; then
+ git -C ./qemu clone "${GIT_REPO_QEMU}" -b "$SPDK_QEMU_BRANCH" "$SPDK_QEMU_BRANCH"
+ else
+ echo "qemu already checked out. Skipping"
+ fi
+
+ declare -a opt_params=("--prefix=/usr/local/qemu/$SPDK_QEMU_BRANCH")
+
+ # Most tsocks proxies rely on a configuration file in /etc/tsocks.conf.
+ # If using tsocks, please make sure to complete this config before trying to build qemu.
+ if echo $CONF | grep -q tsocks; then
+ if hash tsocks 2> /dev/null; then
+ opt_params+=(--with-git='tsocks git')
+ fi
+ fi
+
+ # The qemu configure script places several output files in the CWD.
+ (cd qemu/$SPDK_QEMU_BRANCH && ./configure "${opt_params[@]}" --target-list="x86_64-softmmu" --enable-kvm --enable-linux-aio --enable-numa)
+
+ make -C ./qemu/$SPDK_QEMU_BRANCH -j${jobs}
+ sudo make -C ./qemu/$SPDK_QEMU_BRANCH install
+ fi
+}
+
+function install_vpp()
+{
+ if echo $CONF | grep -q vpp; then
+ # Vector packet processing (VPP) is installed for use with iSCSI tests.
+ # At least on fedora 28, the yum setup that vpp uses is deprecated and fails.
+ # The actions taken under the vpp_setup script are necessary to fix this issue.
+ if [ -d vpp_setup ]; then
+ echo "vpp setup already done."
+ else
+ echo "%_topdir $HOME/vpp_setup/src/rpm" >> ~/.rpmmacros
+ sudo dnf install -y perl-generators
+ mkdir -p ~/vpp_setup/src/rpm
+ mkdir -p vpp_setup/src/rpm/BUILD vpp_setup/src/rpm/RPMS vpp_setup/src/rpm/SOURCES \
+ vpp_setup/src/rpm/SPECS vpp_setup/src/rpm/SRPMS
+ dnf download --downloaddir=./vpp_setup/src/rpm --source redhat-rpm-config
+ rpm -ivh ~/vpp_setup/src/rpm/redhat-rpm-config*
+ sed -i s/"Requires: (annobin if gcc)"//g ~/vpp_setup/src/rpm/SPECS/redhat-rpm-config.spec
+ rpmbuild -ba ~/vpp_setup/src/rpm/SPECS/*.spec
+ sudo dnf remove -y --noautoremove redhat-rpm-config
+ sudo rpm -Uvh ~/vpp_setup/src/rpm/RPMS/noarch/*
+ fi
+
+ if [ -d vpp ]; then
+ echo "vpp already cloned."
+ if [ ! -d vpp/build-root ]; then
+ echo "build-root has not been done"
+ echo "remove the `pwd` and start again"
+ exit 1
+ fi
+ else
+ git clone "${GIT_REPO_VPP}"
+ git -C ./vpp checkout v18.01.1
+ # VPP 18.01.1 does not support OpenSSL 1.1.
+ # For compilation, a compatibility package is used temporarily.
+ sudo dnf install -y --allowerasing compat-openssl10-devel
+ # Installing required dependencies for building VPP
+ yes | make -C ./vpp install-dep
+
+ make -C ./vpp pkg-rpm -j${jobs}
+ # Reinstall latest OpenSSL devel package.
+ sudo dnf install -y --allowerasing openssl-devel
+ sudo dnf install -y \
+ ./vpp/build_root/vpp-lib-18.01.1-release.x86_64.rpm \
+ ./vpp/build_root/vpp-devel-18.01.1-release.x86_64.rpm \
+ ./vpp/build_root/vpp-18.01.1-release.x86_64.rpm
+ # Since hugepage configuration is done via spdk/scripts/setup.sh,
+ # this default config is not needed.
+ #
+ # NOTE: Parameters kernel.shmmax and vm.max_map_count are set to
+ # very low count and cause issues with hugepage total sizes above 1GB.
+ sudo rm -f /etc/sysctl.d/80-vpp.conf
+ fi
+ fi
+}
+
+function install_nvmecli()
+{
+ if echo $CONF | grep -q nvmecli; then
+ SPDK_NVME_CLI_BRANCH=spdk-1.6
+ if [ ! -d nvme-cli ]; then
+ git clone "${GIT_REPO_SPDK_NVME_CLI}" -b "$SPDK_NVME_CLI_BRANCH"
+ else
+ echo "nvme-cli already checked out. Skipping"
+ fi
+ fi
+}
+
+function install_libiscsi()
+{
+ if echo $CONF | grep -q libiscsi; then
+ # We currently don't make any changes to the libiscsi repository for our tests, but it is possible that we will need
+ # to later. Cloning from git is just future proofing the machines.
+ if [ ! -d libiscsi ]; then
+ git clone "${GIT_REPO_LIBISCSI}"
+ else
+ echo "libiscsi already checked out. Skipping"
+ fi
+ ( cd libiscsi && ./autogen.sh && ./configure --prefix=/usr/local/libiscsi)
+ make -C ./libiscsi -j${jobs}
+ sudo make -C ./libiscsi install
+ fi
+}
+
+function usage()
+{
+ echo "This script is intended to automate the environment setup for a fedora linux virtual machine."
+ echo "Please run this script as your regular user. The script will make calls to sudo as needed."
+ echo ""
+ echo "./vm_setup.sh"
+ echo " -h --help"
+ echo " -u --upgrade Run dnf upgrade"
+ echo " -i --install-deps Install dnf based dependencies"
+ echo " -t --test-conf List of test configurations to enable (${CONF})"
+ echo " -c --conf-path Path to configuration file"
+ exit 0
+}
+
+while getopts 'iuht:c:-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage;;
+ upgrade) UPGRADE=true;;
+ install-deps) INSTALL=true;;
+ test-conf=*) CONF="${OPTARG#*=}";;
+ conf-path=*) CONF_PATH="${OPTARG#*=}";;
+ *) echo "Invalid argument '$OPTARG'"
+ usage;;
+ esac
+ ;;
+ h) usage;;
+ u) UPGRADE=true;;
+ i) INSTALL=true;;
+ t) CONF="$OPTARG";;
+ c) CONF_PATH="$OPTARG";;
+ *) echo "Invalid argument '$OPTARG'"
+ usage;;
+ esac
+done
+
+if [ ! -z "$CONF_PATH" ]; then
+ if [ ! -f "$CONF_PATH" ]; then
+ echo Configuration file does not exist: "$CONF_PATH"
+ exit 1
+ else
+ source "$CONF_PATH"
+ fi
+fi
+
+cd ~
+
+: ${GIT_REPO_SPDK=https://review.gerrithub.io/spdk/spdk}; export GIT_REPO_SPDK
+: ${GIT_REPO_DPDK=https://github.com/spdk/dpdk.git}; export GIT_REPO_DPDK
+: ${GIT_REPO_LIBRXE=https://github.com/SoftRoCE/librxe-dev.git}; export GIT_REPO_LIBRXE
+: ${GIT_REPO_OPEN_ISCSI=https://github.com/open-iscsi/open-iscsi}; export GIT_REPO_OPEN_ISCSI
+: ${GIT_REPO_ROCKSDB=https://review.gerrithub.io/spdk/rocksdb}; export GIT_REPO_ROCKSDB
+: ${GIT_REPO_FIO=http://git.kernel.dk/fio.git}; export GIT_REPO_FIO
+: ${GIT_REPO_FLAMEGRAPH=https://github.com/brendangregg/FlameGraph.git}; export GIT_REPO_FLAMEGRAPH
+: ${GIT_REPO_QEMU=https://github.com/spdk/qemu}; export GIT_REPO_QEMU
+: ${GIT_REPO_VPP=https://gerrit.fd.io/r/vpp}; export GIT_REPO_VPP
+: ${GIT_REPO_LIBISCSI=https://github.com/sahlberg/libiscsi}; export GIT_REPO_LIBISCSI
+: ${GIT_REPO_SPDK_NVME_CLI=https://github.com/spdk/nvme-cli}; export GIT_REPO_SPDK_NVME_CLI
+
+jobs=$(($(nproc)*2))
+
+if $UPGRADE; then
+ sudo dnf upgrade -y
+fi
+
+if $INSTALL; then
+ sudo dnf install -y git
+fi
+
+mkdir -p spdk_repo/output
+
+if [ -d spdk_repo/spdk ]; then
+ echo "spdk source already present, not cloning"
+else
+ git -C spdk_repo clone "${GIT_REPO_SPDK}"
+fi
+git -C spdk_repo/spdk config submodule.dpdk.url "${GIT_REPO_DPDK}"
+git -C spdk_repo/spdk submodule update --init --recursive
+
+if $INSTALL; then
+ sudo ./scripts/pkgdep.sh
+
+ if echo $CONF | grep -q tsocks; then
+ sudo dnf install -y tsocks
+ fi
+
+ sudo dnf install -y \
+ valgrind \
+ jq \
+ nvme-cli \
+ ceph \
+ gdb \
+ fio \
+ librbd-devel \
+ kernel-devel \
+ gflags-devel \
+ libasan \
+ libubsan \
+ autoconf \
+ automake \
+ libtool \
+ libmount-devel \
+ iscsi-initiator-utils \
+ isns-utils-devel \
+ pmempool \
+ perl-open \
+ glib2-devel \
+ pixman-devel \
+ astyle-devel \
+ elfutils \
+ elfutils-libelf-devel \
+ flex \
+ bison \
+ targetcli \
+ perl-Switch \
+ librdmacm-utils \
+ libibverbs-utils \
+ gdisk \
+ socat \
+ sshfs
+fi
+
+sudo mkdir -p /usr/src
+
+install_rxe_cfg&
+install_iscsi_adm&
+install_rocksdb&
+install_fio&
+install_flamegraph&
+install_qemu&
+install_vpp&
+install_nvmecli&
+install_libiscsi&
+
+wait
+# create autorun-spdk.conf in home folder. This is sourced by the autotest_common.sh file.
+# By setting any one of the values below to 0, you can skip that specific test. If you are
+# using your autotest platform to do sanity checks before uploading to the build pool, it is
+# probably best to only run the tests that you believe your changes have modified along with
+# Scanbuild and check format. This is because running the whole suite of tests in series can
+# take ~40 minutes to complete.
+if [ ! -e ~/autorun-spdk.conf ]; then
+ cat > ~/autorun-spdk.conf << EOF
+# assign a value of 1 to all of the pertinent tests
+SPDK_BUILD_DOC=1
+SPDK_RUN_CHECK_FORMAT=1
+SPDK_RUN_SCANBUILD=1
+SPDK_RUN_VALGRIND=1
+SPDK_TEST_UNITTEST=1
+SPDK_TEST_ISCSI=1
+SPDK_TEST_ISCSI_INITIATOR=1
+# nvme and nvme-cli cannot be run at the same time on a VM.
+SPDK_TEST_NVME=1
+SPDK_TEST_NVME_CLI=0
+SPDK_TEST_NVMF=1
+SPDK_TEST_RBD=1
+# requires some extra configuration. see TEST_ENV_SETUP_README
+SPDK_TEST_VHOST=0
+SPDK_TEST_VHOST_INIT=0
+SPDK_TEST_BLOCKDEV=1
+# doesn't work on vm
+SPDK_TEST_IOAT=0
+SPDK_TEST_EVENT=1
+SPDK_TEST_BLOBFS=1
+SPDK_TEST_PMDK=1
+SPDK_TEST_LVOL=1
+SPDK_RUN_ASAN=1
+SPDK_RUN_UBSAN=1
+EOF
+fi
diff --git a/src/spdk/test/common/lib/test_env.c b/src/spdk/test/common/lib/test_env.c
new file mode 100644
index 00000000..4c600516
--- /dev/null
+++ b/src/spdk/test/common/lib/test_env.c
@@ -0,0 +1,469 @@
+/*-
+ * 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_internal/mock.h"
+
+#include "spdk/env.h"
+#include "spdk/queue.h"
+
+DEFINE_STUB(spdk_process_is_primary, bool, (void), true)
+DEFINE_STUB(spdk_memzone_lookup, void *, (const char *name), NULL)
+
+/*
+ * These mocks don't use the DEFINE_STUB macros because
+ * their default implementation is more complex.
+ */
+
+DEFINE_RETURN_MOCK(spdk_memzone_reserve, void *);
+void *
+spdk_memzone_reserve(const char *name, size_t len, int socket_id, unsigned flags)
+{
+ HANDLE_RETURN_MOCK(spdk_memzone_reserve);
+
+ return malloc(len);
+}
+
+DEFINE_RETURN_MOCK(spdk_memzone_reserve_aligned, void *);
+void *
+spdk_memzone_reserve_aligned(const char *name, size_t len, int socket_id,
+ unsigned flags, unsigned align)
+{
+ HANDLE_RETURN_MOCK(spdk_memzone_reserve_aligned);
+
+ return malloc(len);
+}
+
+DEFINE_RETURN_MOCK(spdk_malloc, void *);
+void *
+spdk_malloc(size_t size, size_t align, uint64_t *phys_addr, int socket_id, uint32_t flags)
+{
+ HANDLE_RETURN_MOCK(spdk_malloc);
+
+ void *buf = NULL;
+ if (posix_memalign(&buf, align, size)) {
+ return NULL;
+ }
+ if (phys_addr) {
+ *phys_addr = (uint64_t)buf;
+ }
+
+ return buf;
+}
+
+DEFINE_RETURN_MOCK(spdk_zmalloc, void *);
+void *
+spdk_zmalloc(size_t size, size_t align, uint64_t *phys_addr, int socket_id, uint32_t flags)
+{
+ HANDLE_RETURN_MOCK(spdk_zmalloc);
+
+ void *buf = spdk_malloc(size, align, phys_addr, -1, 1);
+
+ if (buf != NULL) {
+ memset(buf, 0, size);
+ }
+ return buf;
+}
+
+DEFINE_RETURN_MOCK(spdk_dma_malloc, void *);
+void *
+spdk_dma_malloc(size_t size, size_t align, uint64_t *phys_addr)
+{
+ HANDLE_RETURN_MOCK(spdk_dma_malloc);
+
+ return spdk_malloc(size, align, phys_addr, -1, 1);
+}
+
+DEFINE_RETURN_MOCK(spdk_dma_zmalloc, void *);
+void *
+spdk_dma_zmalloc(size_t size, size_t align, uint64_t *phys_addr)
+{
+ HANDLE_RETURN_MOCK(spdk_dma_zmalloc);
+
+ return spdk_zmalloc(size, align, phys_addr, -1, 1);
+}
+
+DEFINE_RETURN_MOCK(spdk_dma_malloc_socket, void *);
+void *
+spdk_dma_malloc_socket(size_t size, size_t align, uint64_t *phys_addr, int socket_id)
+{
+ HANDLE_RETURN_MOCK(spdk_dma_malloc_socket);
+
+ return spdk_dma_malloc(size, align, phys_addr);
+}
+
+DEFINE_RETURN_MOCK(spdk_dma_zmalloc_socket, void *);
+void *
+spdk_dma_zmalloc_socket(size_t size, size_t align, uint64_t *phys_addr, int socket_id)
+{
+ HANDLE_RETURN_MOCK(spdk_dma_zmalloc_socket);
+
+ return spdk_dma_zmalloc(size, align, phys_addr);
+}
+
+DEFINE_RETURN_MOCK(spdk_dma_realloc, void *);
+void *
+spdk_dma_realloc(void *buf, size_t size, size_t align, uint64_t *phys_addr)
+{
+ HANDLE_RETURN_MOCK(spdk_dma_realloc);
+
+ return realloc(buf, size);
+}
+
+void
+spdk_free(void *buf)
+{
+ free(buf);
+}
+
+void
+spdk_dma_free(void *buf)
+{
+ return spdk_free(buf);
+}
+
+DEFINE_RETURN_MOCK(spdk_vtophys, uint64_t);
+uint64_t
+spdk_vtophys(void *buf)
+{
+ HANDLE_RETURN_MOCK(spdk_vtophys);
+
+ return (uintptr_t)buf;
+}
+
+void
+spdk_memzone_dump(FILE *f)
+{
+ return;
+}
+
+DEFINE_RETURN_MOCK(spdk_memzone_free, int);
+int
+spdk_memzone_free(const char *name)
+{
+ HANDLE_RETURN_MOCK(spdk_memzone_free);
+
+ return 0;
+}
+
+struct test_mempool {
+ size_t count;
+};
+
+DEFINE_RETURN_MOCK(spdk_mempool_create, struct spdk_mempool *);
+struct spdk_mempool *
+spdk_mempool_create(const char *name, size_t count,
+ size_t ele_size, size_t cache_size, int socket_id)
+{
+ struct test_mempool *mp;
+
+ HANDLE_RETURN_MOCK(spdk_mempool_create);
+
+ mp = calloc(1, sizeof(*mp));
+ if (mp == NULL) {
+ return NULL;
+ }
+
+ mp->count = count;
+
+ return (struct spdk_mempool *)mp;
+}
+
+void
+spdk_mempool_free(struct spdk_mempool *_mp)
+{
+ struct test_mempool *mp = (struct test_mempool *)_mp;
+
+ free(mp);
+}
+
+DEFINE_RETURN_MOCK(spdk_mempool_get, void *);
+void *
+spdk_mempool_get(struct spdk_mempool *_mp)
+{
+ struct test_mempool *mp = (struct test_mempool *)_mp;
+ void *buf;
+
+ HANDLE_RETURN_MOCK(spdk_mempool_get);
+
+ if (mp && mp->count == 0) {
+ return NULL;
+ }
+
+ if (posix_memalign(&buf, 64, 0x1000)) {
+ return NULL;
+ } else {
+ if (mp) {
+ mp->count--;
+ }
+ return buf;
+ }
+}
+
+int
+spdk_mempool_get_bulk(struct spdk_mempool *mp, void **ele_arr, size_t count)
+{
+ for (size_t i = 0; i < count; i++) {
+ ele_arr[i] = spdk_mempool_get(mp);
+ if (ele_arr[i] == NULL) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+void
+spdk_mempool_put(struct spdk_mempool *_mp, void *ele)
+{
+ struct test_mempool *mp = (struct test_mempool *)_mp;
+
+ if (mp) {
+ mp->count++;
+ }
+ free(ele);
+}
+
+void
+spdk_mempool_put_bulk(struct spdk_mempool *mp, void **ele_arr, size_t count)
+{
+ for (size_t i = 0; i < count; i++) {
+ spdk_mempool_put(mp, ele_arr[i]);
+ }
+}
+
+DEFINE_RETURN_MOCK(spdk_mempool_count, size_t);
+size_t
+spdk_mempool_count(const struct spdk_mempool *_mp)
+{
+ struct test_mempool *mp = (struct test_mempool *)_mp;
+
+ HANDLE_RETURN_MOCK(spdk_mempool_count);
+
+ if (mp) {
+ return mp->count;
+ } else {
+ return 1024;
+ }
+}
+
+struct spdk_ring_ele {
+ void *ele;
+ TAILQ_ENTRY(spdk_ring_ele) link;
+};
+
+struct spdk_ring {
+ TAILQ_HEAD(, spdk_ring_ele) elements;
+};
+
+DEFINE_RETURN_MOCK(spdk_ring_create, struct spdk_ring *);
+struct spdk_ring *
+spdk_ring_create(enum spdk_ring_type type, size_t count, int socket_id)
+{
+ struct spdk_ring *ring;
+
+ HANDLE_RETURN_MOCK(spdk_ring_create);
+
+ ring = calloc(1, sizeof(*ring));
+ if (ring) {
+ TAILQ_INIT(&ring->elements);
+ }
+
+ return ring;
+}
+
+void
+spdk_ring_free(struct spdk_ring *ring)
+{
+ free(ring);
+}
+
+DEFINE_RETURN_MOCK(spdk_ring_enqueue, size_t);
+size_t
+spdk_ring_enqueue(struct spdk_ring *ring, void **objs, size_t count)
+{
+ struct spdk_ring_ele *ele;
+ size_t i;
+
+ HANDLE_RETURN_MOCK(spdk_ring_enqueue);
+
+ for (i = 0; i < count; i++) {
+ ele = calloc(1, sizeof(*ele));
+ if (!ele) {
+ break;
+ }
+
+ ele->ele = objs[i];
+ TAILQ_INSERT_TAIL(&ring->elements, ele, link);
+ }
+
+ return i;
+}
+
+DEFINE_RETURN_MOCK(spdk_ring_dequeue, size_t);
+size_t
+spdk_ring_dequeue(struct spdk_ring *ring, void **objs, size_t count)
+{
+ struct spdk_ring_ele *ele, *tmp;
+ size_t i = 0;
+
+ HANDLE_RETURN_MOCK(spdk_ring_dequeue);
+
+ if (count == 0) {
+ return 0;
+ }
+
+ TAILQ_FOREACH_SAFE(ele, &ring->elements, link, tmp) {
+ TAILQ_REMOVE(&ring->elements, ele, link);
+ objs[i] = ele->ele;
+ free(ele);
+ i++;
+ if (i >= count) {
+ break;
+ }
+ }
+
+ return i;
+
+}
+
+DEFINE_RETURN_MOCK(spdk_get_ticks, uint64_t);
+uint64_t
+spdk_get_ticks(void)
+{
+ HANDLE_RETURN_MOCK(spdk_get_ticks);
+
+ return ut_spdk_get_ticks;
+}
+
+DEFINE_RETURN_MOCK(spdk_get_ticks_hz, uint64_t);
+uint64_t
+spdk_get_ticks_hz(void)
+{
+ HANDLE_RETURN_MOCK(spdk_get_ticks_hz);
+
+ return 1000000;
+}
+
+void
+spdk_delay_us(unsigned int us)
+{
+ /* spdk_get_ticks_hz is 1000000, meaning 1 tick per us. */
+ ut_spdk_get_ticks += us;
+}
+
+DEFINE_RETURN_MOCK(spdk_pci_addr_parse, int);
+int
+spdk_pci_addr_parse(struct spdk_pci_addr *addr, const char *bdf)
+{
+ unsigned domain, bus, dev, func;
+
+ HANDLE_RETURN_MOCK(spdk_pci_addr_parse);
+
+ if (addr == NULL || bdf == NULL) {
+ return -EINVAL;
+ }
+
+ if ((sscanf(bdf, "%x:%x:%x.%x", &domain, &bus, &dev, &func) == 4) ||
+ (sscanf(bdf, "%x.%x.%x.%x", &domain, &bus, &dev, &func) == 4)) {
+ /* Matched a full address - all variables are initialized */
+ } else if (sscanf(bdf, "%x:%x:%x", &domain, &bus, &dev) == 3) {
+ func = 0;
+ } else if ((sscanf(bdf, "%x:%x.%x", &bus, &dev, &func) == 3) ||
+ (sscanf(bdf, "%x.%x.%x", &bus, &dev, &func) == 3)) {
+ domain = 0;
+ } else if ((sscanf(bdf, "%x:%x", &bus, &dev) == 2) ||
+ (sscanf(bdf, "%x.%x", &bus, &dev) == 2)) {
+ domain = 0;
+ func = 0;
+ } else {
+ return -EINVAL;
+ }
+
+ if (bus > 0xFF || dev > 0x1F || func > 7) {
+ return -EINVAL;
+ }
+
+ addr->domain = domain;
+ addr->bus = bus;
+ addr->dev = dev;
+ addr->func = func;
+
+ return 0;
+}
+
+DEFINE_RETURN_MOCK(spdk_pci_addr_fmt, int);
+int
+spdk_pci_addr_fmt(char *bdf, size_t sz, const struct spdk_pci_addr *addr)
+{
+ int rc;
+
+ HANDLE_RETURN_MOCK(spdk_pci_addr_fmt);
+
+ rc = snprintf(bdf, sz, "%04x:%02x:%02x.%x",
+ addr->domain, addr->bus,
+ addr->dev, addr->func);
+
+ if (rc > 0 && (size_t)rc < sz) {
+ return 0;
+ }
+
+ return -1;
+}
+
+DEFINE_RETURN_MOCK(spdk_pci_addr_compare, int);
+int
+spdk_pci_addr_compare(const struct spdk_pci_addr *a1, const struct spdk_pci_addr *a2)
+{
+ HANDLE_RETURN_MOCK(spdk_pci_addr_compare);
+
+ if (a1->domain > a2->domain) {
+ return 1;
+ } else if (a1->domain < a2->domain) {
+ return -1;
+ } else if (a1->bus > a2->bus) {
+ return 1;
+ } else if (a1->bus < a2->bus) {
+ return -1;
+ } else if (a1->dev > a2->dev) {
+ return 1;
+ } else if (a1->dev < a2->dev) {
+ return -1;
+ } else if (a1->func > a2->func) {
+ return 1;
+ } else if (a1->func < a2->func) {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/spdk/test/common/lib/ut_multithread.c b/src/spdk/test/common/lib/ut_multithread.c
new file mode 100644
index 00000000..85fcee2a
--- /dev/null
+++ b/src/spdk/test/common/lib/ut_multithread.c
@@ -0,0 +1,278 @@
+/*-
+ * 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_cunit.h"
+#include "spdk/thread.h"
+#include "spdk_internal/mock.h"
+
+static uint32_t g_ut_num_threads;
+static uint64_t g_current_time_in_us = 0;
+
+int allocate_threads(int num_threads);
+void free_threads(void);
+void poll_threads(void);
+int poll_thread(uintptr_t thread_id);
+void increment_time(uint64_t time_in_us);
+void reset_time(void);
+
+struct ut_msg {
+ spdk_thread_fn fn;
+ void *ctx;
+ TAILQ_ENTRY(ut_msg) link;
+};
+
+struct ut_thread {
+ struct spdk_thread *thread;
+ struct spdk_io_channel *ch;
+ TAILQ_HEAD(, ut_msg) msgs;
+ TAILQ_HEAD(, ut_poller) pollers;
+};
+
+struct ut_thread *g_ut_threads;
+
+struct ut_poller {
+ spdk_poller_fn fn;
+ void *arg;
+ TAILQ_ENTRY(ut_poller) tailq;
+ uint64_t period_us;
+ uint64_t next_expiration_in_us;
+};
+
+static void
+__send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx)
+{
+ struct ut_thread *thread = thread_ctx;
+ struct ut_msg *msg;
+
+ msg = calloc(1, sizeof(*msg));
+ SPDK_CU_ASSERT_FATAL(msg != NULL);
+
+ msg->fn = fn;
+ msg->ctx = ctx;
+ TAILQ_INSERT_TAIL(&thread->msgs, msg, link);
+}
+
+static struct spdk_poller *
+__start_poller(void *thread_ctx, spdk_poller_fn fn, void *arg, uint64_t period_microseconds)
+{
+ struct ut_thread *thread = thread_ctx;
+ struct ut_poller *poller = calloc(1, sizeof(struct ut_poller));
+
+ SPDK_CU_ASSERT_FATAL(poller != NULL);
+
+ poller->fn = fn;
+ poller->arg = arg;
+ poller->period_us = period_microseconds;
+ poller->next_expiration_in_us = g_current_time_in_us + poller->period_us;
+
+ TAILQ_INSERT_TAIL(&thread->pollers, poller, tailq);
+
+ return (struct spdk_poller *)poller;
+}
+
+static void
+__stop_poller(struct spdk_poller *poller, void *thread_ctx)
+{
+ struct ut_thread *thread = thread_ctx;
+
+ TAILQ_REMOVE(&thread->pollers, (struct ut_poller *)poller, tailq);
+
+ free(poller);
+}
+
+#define INVALID_THREAD 0x1000
+
+static uintptr_t g_thread_id = INVALID_THREAD;
+
+static void
+set_thread(uintptr_t thread_id)
+{
+ g_thread_id = thread_id;
+ if (thread_id == INVALID_THREAD) {
+ MOCK_CLEAR(pthread_self);
+ } else {
+ MOCK_SET(pthread_self, (pthread_t)thread_id);
+ }
+}
+
+int
+allocate_threads(int num_threads)
+{
+ struct spdk_thread *thread;
+ uint32_t i;
+
+ g_ut_num_threads = num_threads;
+
+ g_ut_threads = calloc(num_threads, sizeof(*g_ut_threads));
+ SPDK_CU_ASSERT_FATAL(g_ut_threads != NULL);
+
+ for (i = 0; i < g_ut_num_threads; i++) {
+ set_thread(i);
+ spdk_allocate_thread(__send_msg, __start_poller, __stop_poller,
+ &g_ut_threads[i], NULL);
+ thread = spdk_get_thread();
+ SPDK_CU_ASSERT_FATAL(thread != NULL);
+ g_ut_threads[i].thread = thread;
+ TAILQ_INIT(&g_ut_threads[i].msgs);
+ TAILQ_INIT(&g_ut_threads[i].pollers);
+ }
+
+ set_thread(INVALID_THREAD);
+ return 0;
+}
+
+void
+free_threads(void)
+{
+ uint32_t i;
+
+ for (i = 0; i < g_ut_num_threads; i++) {
+ set_thread(i);
+ spdk_free_thread();
+ }
+
+ g_ut_num_threads = 0;
+ free(g_ut_threads);
+ g_ut_threads = NULL;
+}
+
+void
+increment_time(uint64_t time_in_us)
+{
+ g_current_time_in_us += time_in_us;
+ spdk_delay_us(time_in_us);
+}
+
+static void
+reset_pollers(void)
+{
+ uint32_t i = 0;
+ struct ut_thread *thread = NULL;
+ struct ut_poller *poller = NULL;
+ uintptr_t original_thread_id = g_thread_id;
+
+ CU_ASSERT(g_current_time_in_us == 0);
+
+ for (i = 0; i < g_ut_num_threads; i++) {
+ set_thread(i);
+ thread = &g_ut_threads[i];
+
+ TAILQ_FOREACH(poller, &thread->pollers, tailq) {
+ poller->next_expiration_in_us = g_current_time_in_us + poller->period_us;
+ }
+ }
+
+ set_thread(original_thread_id);
+}
+
+void
+reset_time(void)
+{
+ g_current_time_in_us = 0;
+ reset_pollers();
+}
+
+int
+poll_thread(uintptr_t thread_id)
+{
+ int count = 0;
+ struct ut_thread *thread = &g_ut_threads[thread_id];
+ struct ut_msg *msg;
+ struct ut_poller *poller;
+ uintptr_t original_thread_id;
+ TAILQ_HEAD(, ut_poller) tmp_pollers;
+
+ CU_ASSERT(thread_id != (uintptr_t)INVALID_THREAD);
+ CU_ASSERT(thread_id < g_ut_num_threads);
+
+ original_thread_id = g_thread_id;
+ set_thread(thread_id);
+
+ while (!TAILQ_EMPTY(&thread->msgs)) {
+ msg = TAILQ_FIRST(&thread->msgs);
+ TAILQ_REMOVE(&thread->msgs, msg, link);
+
+ msg->fn(msg->ctx);
+ count++;
+ free(msg);
+ }
+
+ TAILQ_INIT(&tmp_pollers);
+
+ while (!TAILQ_EMPTY(&thread->pollers)) {
+ poller = TAILQ_FIRST(&thread->pollers);
+ TAILQ_REMOVE(&thread->pollers, poller, tailq);
+
+ if (g_current_time_in_us >= poller->next_expiration_in_us) {
+ if (poller->fn) {
+ poller->fn(poller->arg);
+ }
+
+ if (poller->period_us == 0) {
+ break;
+ } else {
+ poller->next_expiration_in_us += poller->period_us;
+ }
+ }
+
+ TAILQ_INSERT_TAIL(&tmp_pollers, poller, tailq);
+ }
+
+ TAILQ_SWAP(&tmp_pollers, &thread->pollers, ut_poller, tailq);
+
+ set_thread(original_thread_id);
+
+ return count;
+}
+
+void
+poll_threads(void)
+{
+ bool msg_processed;
+ uint32_t i, count;
+
+ while (true) {
+ msg_processed = false;
+
+ for (i = 0; i < g_ut_num_threads; i++) {
+ count = poll_thread(i);
+ if (count > 0) {
+ msg_processed = true;
+ }
+ }
+
+ if (!msg_processed) {
+ break;
+ }
+ }
+}
diff --git a/src/spdk/test/config_converter/config.ini b/src/spdk/test/config_converter/config.ini
new file mode 100644
index 00000000..2d71f982
--- /dev/null
+++ b/src/spdk/test/config_converter/config.ini
@@ -0,0 +1,151 @@
+#comment1
+[Global]
+ Comment "Global section"#comment2
+ ReactorMask 0xF #comment3
+#comment4
+ #comment5
+[Nvmf]
+ MaxQueuesPerSession 4
+ MaxQueueDepth 128
+ InCapsuleDataSize 4096
+ MaxIOSize 131072
+ AcceptorPollRate 10000
+ IOUnitSize 131072
+
+[Nvme]
+ TransportID "trtype:PCIe traddr:0000:00:04.0" Nvme0
+
+[Bdev]
+ BdevIoPoolSize 65536
+ BdevIoCacheSize 256
+
+[Split]
+ Split Nvme0n1 8
+
+[Nvme]
+ RetryCount 4
+ TimeoutUsec 0
+ ActionOnTimeout None
+ AdminPollRate 100000
+ HotplugEnable Yes
+
+[iSCSI]
+ NodeBase "iqn.2016-06.io.spdk"
+ AuthFile /usr/local/etc/spdk/auth.conf
+ Timeout 30
+ DiscoveryAuthMethod Auto
+ DiscoveryAuthGroup AuthGroup1
+ MaxSessions 16
+ ImmediateData Yes
+ ErrorRecoveryLevel 0
+ MaxR2T 256
+ NopInInterval 10
+ AllowDuplicateIsid Yes
+ MinConnectionsPerCore 4
+ DefaultTime2Wait 2
+ QueueDepth 128
+
+[Malloc]
+ NumberOfLuns 8
+ LunSizeInMB 128
+ BlockSize 4096
+
+[Pmem]
+ Blk /tmp/sample_pmem Pmem0
+
+[AIO]
+ AIO /tmp/sample_aio0 AIO0 2048
+ AIO /tmp/sample_aio1 AIO1 2048
+ AIO /tmp/sample_aio2 AIO2 2048
+ AIO /tmp/sample_aio1 AIO3 2048
+ AIO /tmp/sample_aio2 AIO4 2048
+
+[VhostBlk0]
+ Name vhost.1
+ Dev Malloc6
+ ReadOnly yes
+ Cpumask 0x1
+
+[VhostScsi0]
+ Name naa.vhost.0
+ Target 0 Malloc4
+ Target 1 AIO3
+ Target 2 Nvme0n1p2
+ # Target 3 Nvme1n1p2
+ Cpumask 0x1
+
+[VhostScsi1]
+ Name naa.vhost.1
+ Target 0 AIO4
+ Cpumask 0x1
+
+[VhostBlk1]
+ Name naa.vhost.2
+ Dev Malloc5
+ ReadOnly no
+ Cpumask 0x1
+
+[VhostNvme0]
+ Name naa.vhost.3
+ NumberOfQueues 2
+ Namespace Nvme0n1p0
+ Namespace Nvme0n1p1
+ Cpumask 0x1
+
+[Subsystem1]
+ NQN nqn.2016-06.io.spdk:cnode1
+ Listen RDMA 10.0.2.15:4420
+ AllowAnyHost No
+ Host nqn.2016-06.io.spdk:init
+ SN SPDK00000000000001
+ MaxNamespaces 20
+ Namespace Nvme0n1p5 1
+ Namespace Nvme0n1p6 2
+
+[Subsystem2]
+ NQN nqn.2016-06.io.spdk:cnode2
+ Listen RDMA 10.0.2.15:4421
+ AllowAnyHost No
+ Host nqn.2016-06.io.spdk:init
+ SN SPDK00000000000002
+ Namespace Malloc1
+ Namespace Malloc2
+ Namespace AIO0
+ Namespace AIO1
+
+[InitiatorGroup1]
+ InitiatorName ANY
+ Netmask 127.0.0.1/32
+
+[PortalGroup1]
+ Portal DA1 127.0.0.1:4000
+ Portal DA2 127.0.0.1:4001@0xF
+
+[TargetNode1]
+ TargetName disk1
+ TargetAlias "Data Disk1"
+ Mapping PortalGroup1 InitiatorGroup1
+ AuthMethod Auto
+ AuthGroup AuthGroup1
+ # Enable header and data digest
+ # UseDigest Header Data
+ UseDigest Auto
+ # Use the first malloc target
+ LUN0 Malloc0
+ # Using the first AIO target
+ LUN1 AIO2
+ # Using the second storage target
+ LUN2 AIO3
+ # Using the third storage target
+ LUN3 AIO4
+ QueueDepth 128
+
+[TargetNode2]
+ TargetName disk2
+ TargetAlias "Data Disk2"
+ Mapping PortalGroup1 InitiatorGroup1
+ AuthMethod Auto
+ AuthGroup AuthGroup1
+ UseDigest Auto
+ LUN0 Nvme0n1p3
+ QueueDepth 32
diff --git a/src/spdk/test/config_converter/config_virtio.ini b/src/spdk/test/config_converter/config_virtio.ini
new file mode 100644
index 00000000..b2b7f4c7
--- /dev/null
+++ b/src/spdk/test/config_converter/config_virtio.ini
@@ -0,0 +1,21 @@
+[VirtioUser0]
+ Path naa.vhost.0
+ Queues 8
+
+[VirtioUser1]
+ Path naa.vhost.1
+ Queues 8
+
+#[VirtioUser2]
+# Path naa.vhost.3
+# Queues 8
+
+#[VirtioUser3]
+# Path naa.vhost.2
+# Type Blk
+# Queues 8
+
+[VirtioUser4]
+ Path vhost.1
+ Type Blk
+# Queues 8
diff --git a/src/spdk/test/config_converter/spdk_config.json b/src/spdk/test/config_converter/spdk_config.json
new file mode 100644
index 00000000..4b4ba572
--- /dev/null
+++ b/src/spdk/test/config_converter/spdk_config.json
@@ -0,0 +1,481 @@
+{
+ "subsystems": [
+ {
+ "subsystem": "copy",
+ "config": null
+ },
+ {
+ "subsystem": "interface",
+ "config": null
+ },
+ {
+ "subsystem": "net_framework",
+ "config": null
+ },
+ {
+ "subsystem": "bdev",
+ "config": [
+ {
+ "params": {
+ "bdev_io_pool_size": 65536,
+ "bdev_io_cache_size": 256
+ },
+ "method": "set_bdev_options"
+ },
+ {
+ "params": {
+ "base_bdev": "Nvme0n1",
+ "split_size_mb": 0,
+ "split_count": 8
+ },
+ "method": "construct_split_vbdev"
+ },
+ {
+ "params": {
+ "retry_count": 4,
+ "timeout_us": 0,
+ "nvme_adminq_poll_period_us": 100000,
+ "action_on_timeout": "none"
+ },
+ "method": "set_bdev_nvme_options"
+ },
+ {
+ "params": {
+ "trtype": "PCIe",
+ "name": "Nvme0",
+ "traddr": "0000:00:04.0"
+ },
+ "method": "construct_nvme_bdev"
+ },
+ {
+ "params": {
+ "enable": true,
+ "period_us": 10000000
+ },
+ "method": "set_bdev_nvme_hotplug"
+ },
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768,
+ "name": "Malloc0"
+ },
+ "method": "construct_malloc_bdev"
+ },
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768,
+ "name": "Malloc1"
+ },
+ "method": "construct_malloc_bdev"
+ },
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768,
+ "name": "Malloc2"
+ },
+ "method": "construct_malloc_bdev"
+ },
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768,
+ "name": "Malloc3"
+ },
+ "method": "construct_malloc_bdev"
+ },
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768,
+ "name": "Malloc4"
+ },
+ "method": "construct_malloc_bdev"
+ },
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768,
+ "name": "Malloc5"
+ },
+ "method": "construct_malloc_bdev"
+ },
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768,
+ "name": "Malloc6"
+ },
+ "method": "construct_malloc_bdev"
+ },
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768,
+ "name": "Malloc7"
+ },
+ "method": "construct_malloc_bdev"
+ },
+ {
+ "params": {
+ "block_size": 2048,
+ "name": "AIO0",
+ "filename": "/tmp/sample_aio0"
+ },
+ "method": "construct_aio_bdev"
+ },
+ {
+ "params": {
+ "block_size": 2048,
+ "name": "AIO1",
+ "filename": "/tmp/sample_aio1"
+ },
+ "method": "construct_aio_bdev"
+ },
+ {
+ "params": {
+ "block_size": 2048,
+ "name": "AIO2",
+ "filename": "/tmp/sample_aio2"
+ },
+ "method": "construct_aio_bdev"
+ },
+ {
+ "params": {
+ "block_size": 2048,
+ "name": "AIO3",
+ "filename": "/tmp/sample_aio1"
+ },
+ "method": "construct_aio_bdev"
+ },
+ {
+ "params": {
+ "block_size": 2048,
+ "name": "AIO4",
+ "filename": "/tmp/sample_aio2"
+ },
+ "method": "construct_aio_bdev"
+ },
+ {
+ "params": {
+ "name": "Pmem0",
+ "pmem_file": "/tmp/sample_pmem"
+ },
+ "method": "construct_pmem_bdev"
+ }
+ ]
+ },
+ {
+ "subsystem": "scsi",
+ "config": null
+ },
+ {
+ "subsystem": "nvmf",
+ "config": [
+ {
+ "params": {
+ "acceptor_poll_rate": 10000
+ },
+ "method": "set_nvmf_target_config"
+ },
+ {
+ "params": {
+ "in_capsule_data_size": 4096,
+ "io_unit_size": 131072,
+ "max_qpairs_per_ctrlr": 4,
+ "max_queue_depth": 128,
+ "max_io_size": 131072,
+ "max_subsystems": 1024
+ },
+ "method": "set_nvmf_target_options"
+ },
+ {
+ "params": {
+ "max_namespaces": 20,
+ "listen_addresses": [
+ {
+ "trtype": "RDMA",
+ "adrfam": "IPv4",
+ "trsvcid": "4420",
+ "traddr": "10.0.2.15"
+ }
+ ],
+ "hosts": [
+ "nqn.2016-06.io.spdk:init"
+ ],
+ "namespaces": [
+ {
+ "bdev_name": "Nvme0n1p5",
+ "nsid": 1
+ },
+ {
+ "bdev_name": "Nvme0n1p6",
+ "nsid": 2
+ }
+ ],
+ "allow_any_host": false,
+ "serial_number": "SPDK00000000000001",
+ "nqn": "nqn.2016-06.io.spdk:cnode1"
+ },
+ "method": "construct_nvmf_subsystem"
+ },
+ {
+ "params": {
+ "listen_addresses": [
+ {
+ "trtype": "RDMA",
+ "adrfam": "IPv4",
+ "trsvcid": "4421",
+ "traddr": "10.0.2.15"
+ }
+ ],
+ "hosts": [
+ "nqn.2016-06.io.spdk:init"
+ ],
+ "namespaces": [
+ {
+ "bdev_name": "Malloc1",
+ "nsid": 1
+ },
+ {
+ "bdev_name": "Malloc2",
+ "nsid": 2
+ },
+ {
+ "bdev_name": "AIO0",
+ "nsid": 3
+ },
+ {
+ "bdev_name": "AIO1",
+ "nsid": 4
+ }
+ ],
+ "allow_any_host": false,
+ "serial_number": "SPDK00000000000002",
+ "nqn": "nqn.2016-06.io.spdk:cnode2"
+ },
+ "method": "construct_nvmf_subsystem"
+ }
+ ]
+ },
+ {
+ "subsystem": "nbd",
+ "config": []
+ },
+ {
+ "subsystem": "vhost",
+ "config": [
+ {
+ "params": {
+ "cpumask": "1",
+ "ctrlr": "naa.vhost.0"
+ },
+ "method": "construct_vhost_scsi_controller"
+ },
+ {
+ "params": {
+ "scsi_target_num": 0,
+ "bdev_name": "Malloc4",
+ "ctrlr": "naa.vhost.0"
+ },
+ "method": "add_vhost_scsi_lun"
+ },
+ {
+ "params": {
+ "scsi_target_num": 1,
+ "bdev_name": "AIO3",
+ "ctrlr": "naa.vhost.0"
+ },
+ "method": "add_vhost_scsi_lun"
+ },
+ {
+ "params": {
+ "scsi_target_num": 2,
+ "bdev_name": "Nvme0n1p2",
+ "ctrlr": "naa.vhost.0"
+ },
+ "method": "add_vhost_scsi_lun"
+ },
+ {
+ "params": {
+ "cpumask": "1",
+ "ctrlr": "naa.vhost.1"
+ },
+ "method": "construct_vhost_scsi_controller"
+ },
+ {
+ "params": {
+ "scsi_target_num": 0,
+ "bdev_name": "AIO4",
+ "ctrlr": "naa.vhost.1"
+ },
+ "method": "add_vhost_scsi_lun"
+ },
+ {
+ "params": {
+ "dev_name": "Malloc6",
+ "readonly": true,
+ "ctrlr": "vhost.1",
+ "cpumask": "1"
+ },
+ "method": "construct_vhost_blk_controller"
+ },
+ {
+ "params": {
+ "dev_name": "Malloc5",
+ "readonly": false,
+ "ctrlr": "naa.vhost.2",
+ "cpumask": "1"
+ },
+ "method": "construct_vhost_blk_controller"
+ },
+ {
+ "params": {
+ "cpumask": "1",
+ "io_queues": 2,
+ "ctrlr": "naa.vhost.3"
+ },
+ "method": "construct_vhost_nvme_controller"
+ },
+ {
+ "params": {
+ "bdev_name": "Nvme0n1p0",
+ "ctrlr": "naa.vhost.3"
+ },
+ "method": "add_vhost_nvme_ns"
+ },
+ {
+ "params": {
+ "bdev_name": "Nvme0n1p1",
+ "ctrlr": "naa.vhost.3"
+ },
+ "method": "add_vhost_nvme_ns"
+ }
+ ]
+ },
+ {
+ "subsystem": "iscsi",
+ "config": [
+ {
+ "params": {
+ "allow_duplicated_isid": true,
+ "default_time2retain": 20,
+ "mutual_chap": false,
+ "require_chap": false,
+ "immediate_data": true,
+ "node_base": "iqn.2016-06.io.spdk",
+ "nop_in_interval": 10,
+ "max_connections_per_session": 2,
+ "first_burst_length": 8192,
+ "max_queue_depth": 64,
+ "nop_timeout": 30,
+ "chap_group": 1,
+ "max_sessions": 16,
+ "error_recovery_level": 0,
+ "disable_chap": false,
+ "auth_file": "/usr/local/etc/spdk/auth.conf",
+ "min_connections_per_core": 4,
+ "default_time2wait": 2
+ },
+ "method": "set_iscsi_options"
+ },
+ {
+ "params": {
+ "portals": [
+ {
+ "cpumask": "0x1",
+ "host": "127.0.0.1",
+ "port": "4000"
+ },
+ {
+ "cpumask": "0x1",
+ "host": "127.0.0.1",
+ "port": "4001"
+ }
+ ],
+ "tag": 1
+ },
+ "method": "add_portal_group"
+ },
+ {
+ "params": {
+ "initiators": [
+ "ANY"
+ ],
+ "tag": 1,
+ "netmasks": [
+ "127.0.0.1/32"
+ ]
+ },
+ "method": "add_initiator_group"
+ },
+ {
+ "params": {
+ "luns": [
+ {
+ "lun_id": 0,
+ "bdev_name": "Malloc0"
+ },
+ {
+ "lun_id": 1,
+ "bdev_name": "AIO2"
+ },
+ {
+ "lun_id": 2,
+ "bdev_name": "AIO3"
+ },
+ {
+ "lun_id": 3,
+ "bdev_name": "AIO4"
+ }
+ ],
+ "mutual_chap": false,
+ "name": "iqn.2016-06.io.spdk:disk1",
+ "alias_name": "Data Disk1",
+ "require_chap": false,
+ "chap_group": 1,
+ "pg_ig_maps": [
+ {
+ "ig_tag": 1,
+ "pg_tag": 1
+ }
+ ],
+ "data_digest": false,
+ "disable_chap": false,
+ "header_digest": false,
+ "queue_depth": 64
+ },
+ "method": "construct_target_node"
+ },
+ {
+ "params": {
+ "luns": [
+ {
+ "lun_id": 0,
+ "bdev_name": "Nvme0n1p3"
+ }
+ ],
+ "mutual_chap": false,
+ "name": "iqn.2016-06.io.spdk:disk2",
+ "alias_name": "Data Disk2",
+ "require_chap": false,
+ "chap_group": 1,
+ "pg_ig_maps": [
+ {
+ "ig_tag": 1,
+ "pg_tag": 1
+ }
+ ],
+ "data_digest": false,
+ "disable_chap": false,
+ "header_digest": false,
+ "queue_depth": 32
+ },
+ "method": "construct_target_node"
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/spdk/test/config_converter/spdk_config_virtio.json b/src/spdk/test/config_converter/spdk_config_virtio.json
new file mode 100644
index 00000000..00391f0b
--- /dev/null
+++ b/src/spdk/test/config_converter/spdk_config_virtio.json
@@ -0,0 +1,138 @@
+{
+ "subsystems": [
+ {
+ "subsystem": "copy",
+ "config": null
+ },
+ {
+ "subsystem": "interface",
+ "config": null
+ },
+ {
+ "subsystem": "net_framework",
+ "config": null
+ },
+ {
+ "subsystem": "bdev",
+ "config": [
+ {
+ "params": {
+ "bdev_io_pool_size": 65536,
+ "bdev_io_cache_size": 256
+ },
+ "method": "set_bdev_options"
+ },
+ {
+ "params": {
+ "retry_count": 4,
+ "timeout_us": 0,
+ "nvme_adminq_poll_period_us": 1000000,
+ "action_on_timeout": "none"
+ },
+ "method": "set_bdev_nvme_options"
+ },
+ {
+ "params": {
+ "enable": false,
+ "period_us": 100000
+ },
+ "method": "set_bdev_nvme_hotplug"
+ },
+ {
+ "params": {
+ "name": "VirtioScsi0",
+ "dev_type": "scsi",
+ "vq_size": 512,
+ "trtype": "user",
+ "traddr": "naa.vhost.0",
+ "vq_count": 8
+ },
+ "method": "construct_virtio_dev"
+ },
+ {
+ "params": {
+ "name": "VirtioScsi1",
+ "dev_type": "scsi",
+ "vq_size": 512,
+ "trtype": "user",
+ "traddr": "naa.vhost.1",
+ "vq_count": 8
+ },
+ "method": "construct_virtio_dev"
+ },
+ {
+ "params": {
+ "name": "VirtioBlk4",
+ "dev_type": "blk",
+ "vq_size": 512,
+ "trtype": "user",
+ "traddr": "vhost.1",
+ "vq_count": 1
+ },
+ "method": "construct_virtio_dev"
+ }
+ ]
+ },
+ {
+ "subsystem": "scsi",
+ "config": null
+ },
+ {
+ "subsystem": "nvmf",
+ "config": [
+ {
+ "params": {
+ "acceptor_poll_rate": 10000
+ },
+ "method": "set_nvmf_target_config"
+ },
+ {
+ "params": {
+ "in_capsule_data_size": 4096,
+ "io_unit_size": 131072,
+ "max_qpairs_per_ctrlr": 64,
+ "max_queue_depth": 128,
+ "max_io_size": 131072,
+ "max_subsystems": 1024
+ },
+ "method": "set_nvmf_target_options"
+ }
+ ]
+ },
+ {
+ "subsystem": "nbd",
+ "config": []
+ },
+ {
+ "subsystem": "vhost",
+ "config": []
+ },
+ {
+ "subsystem": "iscsi",
+ "config": [
+ {
+ "params": {
+ "allow_duplicated_isid": false,
+ "default_time2retain": 20,
+ "mutual_chap": false,
+ "require_chap": false,
+ "immediate_data": true,
+ "node_base": "iqn.2016-06.io.spdk",
+ "nop_in_interval": 30,
+ "max_connections_per_session": 2,
+ "first_burst_length": 8192,
+ "max_queue_depth": 64,
+ "nop_timeout": 60,
+ "chap_group": 0,
+ "max_sessions": 128,
+ "error_recovery_level": 0,
+ "disable_chap": false,
+ "min_connections_per_core": 4,
+ "default_time2wait": 2
+ },
+ "method": "set_iscsi_options"
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/spdk/test/config_converter/test_converter.sh b/src/spdk/test/config_converter/test_converter.sh
new file mode 100755
index 00000000..5594df2d
--- /dev/null
+++ b/src/spdk/test/config_converter/test_converter.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+CONVERTER_DIR=$(readlink -f $(dirname $0))
+SPDK_BUILD_DIR=$CONVERTER_DIR/../../
+source $CONVERTER_DIR/../common/autotest_common.sh
+
+function test_cleanup() {
+ rm -f $CONVERTER_DIR/config_converter.json $CONVERTER_DIR/config_virtio_converter.json
+}
+
+function on_error_exit() {
+ set +e
+ test_cleanup
+ print_backtrace
+ exit 1
+}
+
+trap 'on_error_exit' ERR
+
+cat $CONVERTER_DIR/config.ini | python3 $SPDK_BUILD_DIR/scripts/config_converter.py > $CONVERTER_DIR/config_converter.json
+cat $CONVERTER_DIR/config_virtio.ini | python3 $SPDK_BUILD_DIR/scripts/config_converter.py > $CONVERTER_DIR/config_virtio_converter.json
+diff -I "cpumask" -I "max_queue_depth" -I "queue_depth" <(jq -S . $CONVERTER_DIR/config_converter.json) <(jq -S . $CONVERTER_DIR/spdk_config.json)
+diff <(jq -S . $CONVERTER_DIR/config_virtio_converter.json) <(jq -S . $CONVERTER_DIR/spdk_config_virtio.json)
+test_cleanup
diff --git a/src/spdk/test/cpp_headers/.gitignore b/src/spdk/test/cpp_headers/.gitignore
new file mode 100644
index 00000000..ce1da4c5
--- /dev/null
+++ b/src/spdk/test/cpp_headers/.gitignore
@@ -0,0 +1 @@
+*.cpp
diff --git a/src/spdk/test/cpp_headers/Makefile b/src/spdk/test/cpp_headers/Makefile
new file mode 100644
index 00000000..cd159bb5
--- /dev/null
+++ b/src/spdk/test/cpp_headers/Makefile
@@ -0,0 +1,51 @@
+#
+# 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.app.mk
+
+HEADERS := $(wildcard $(SPDK_ROOT_DIR)/include/spdk/*.h)
+CXX_SRCS := $(patsubst %.h,%.cpp,$(notdir $(HEADERS)))
+
+%.cpp: $(SPDK_ROOT_DIR)/include/spdk/%.h
+ $(Q)echo " TEST_HEADER include/spdk/$(notdir $<)"; \
+ echo '#include "spdk/$(notdir $<)"' > $@
+
+all : $(CXX_SRCS) $(OBJS)
+ @:
+
+clean :
+ $(CLEAN_C) $(CXX_SRCS)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk
diff --git a/src/spdk/test/env/Makefile b/src/spdk/test/env/Makefile
new file mode 100644
index 00000000..d90696a1
--- /dev/null
+++ b/src/spdk/test/env/Makefile
@@ -0,0 +1,50 @@
+#
+# 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
+
+ENV_NAME := $(notdir $(CONFIG_ENV))
+
+DIRS-y = vtophys
+
+ifeq ($(ENV_NAME),env_dpdk)
+DIRS-y += memory pci
+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/env/env.sh b/src/spdk/test/env/env.sh
new file mode 100755
index 00000000..7aa560f2
--- /dev/null
+++ b/src/spdk/test/env/env.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+set -e
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/test/common/autotest_common.sh
+
+timing_enter env
+
+timing_enter memory
+$testdir/memory/memory_ut
+timing_exit memory
+
+timing_enter vtophys
+$testdir/vtophys/vtophys
+timing_exit vtophys
+
+timing_enter pci
+$testdir/pci/pci_ut
+timing_exit pci
+
+report_test_completion "env"
+timing_exit env
diff --git a/src/spdk/test/env/memory/.gitignore b/src/spdk/test/env/memory/.gitignore
new file mode 100644
index 00000000..7bef3dc0
--- /dev/null
+++ b/src/spdk/test/env/memory/.gitignore
@@ -0,0 +1 @@
+memory_ut
diff --git a/src/spdk/test/env/memory/Makefile b/src/spdk/test/env/memory/Makefile
new file mode 100644
index 00000000..b6fb88e0
--- /dev/null
+++ b/src/spdk/test/env/memory/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
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+CFLAGS += $(ENV_CFLAGS)
+CFLAGS += -I$(SPDK_ROOT_DIR)/test/lib
+TEST_FILE = memory_ut.c
+
+ADDITIONAL_LIBS += $(ENV_LIBS)
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/env/memory/memory_ut.c b/src/spdk/test/env/memory/memory_ut.c
new file mode 100644
index 00000000..39a89a10
--- /dev/null
+++ b/src/spdk/test/env/memory/memory_ut.c
@@ -0,0 +1,504 @@
+/*-
+ * 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 "env_dpdk/memory.c"
+
+#include "common/lib/test_env.c"
+#include "spdk_cunit.h"
+
+#include "spdk/bit_array.h"
+
+static struct rte_mem_config g_mcfg = {};
+
+static struct rte_config g_cfg = {
+ .mem_config = &g_mcfg,
+};
+
+struct rte_config *
+rte_eal_get_configuration(void)
+{
+ return &g_cfg;
+}
+
+#if RTE_VERSION >= RTE_VERSION_NUM(18, 05, 0, 0)
+DEFINE_STUB(rte_mem_event_callback_register, int, (const char *name, rte_mem_event_callback_t clb,
+ void *arg), 0);
+DEFINE_STUB(rte_memseg_contig_walk, int, (rte_memseg_contig_walk_t func, void *arg), 0);
+DEFINE_STUB(rte_mem_virt2memseg, struct rte_memseg *, (const void *addr,
+ const struct rte_memseg_list *msl), NULL);
+#endif
+
+#define PAGE_ARRAY_SIZE (100)
+static struct spdk_bit_array *g_page_array;
+static void *g_vaddr_to_fail = (void *)UINT64_MAX;
+
+static int
+test_mem_map_notify(void *cb_ctx, struct spdk_mem_map *map,
+ enum spdk_mem_map_notify_action action,
+ void *vaddr, size_t len)
+{
+ uint32_t i, end;
+
+ SPDK_CU_ASSERT_FATAL(((uintptr_t)vaddr & MASK_2MB) == 0);
+ SPDK_CU_ASSERT_FATAL((len & MASK_2MB) == 0);
+
+ /*
+ * This is a test requirement - the bit array we use to verify
+ * pages are valid is only so large.
+ */
+ SPDK_CU_ASSERT_FATAL((uintptr_t)vaddr < (VALUE_2MB * PAGE_ARRAY_SIZE));
+
+ i = (uintptr_t)vaddr >> SHIFT_2MB;
+ end = i + (len >> SHIFT_2MB);
+ for (; i < end; i++) {
+ switch (action) {
+ case SPDK_MEM_MAP_NOTIFY_REGISTER:
+ /* This page should not already be registered */
+ SPDK_CU_ASSERT_FATAL(spdk_bit_array_get(g_page_array, i) == false);
+ SPDK_CU_ASSERT_FATAL(spdk_bit_array_set(g_page_array, i) == 0);
+ break;
+ case SPDK_MEM_MAP_NOTIFY_UNREGISTER:
+ SPDK_CU_ASSERT_FATAL(spdk_bit_array_get(g_page_array, i) == true);
+ spdk_bit_array_clear(g_page_array, i);
+ break;
+ default:
+ SPDK_UNREACHABLE();
+ }
+ }
+
+ return 0;
+}
+
+static int
+test_mem_map_notify_fail(void *cb_ctx, struct spdk_mem_map *map,
+ enum spdk_mem_map_notify_action action, void *vaddr, size_t size)
+{
+ struct spdk_mem_map *reg_map = cb_ctx;
+
+ switch (action) {
+ case SPDK_MEM_MAP_NOTIFY_REGISTER:
+ if (vaddr == g_vaddr_to_fail) {
+ /* Test the error handling. */
+ return -1;
+ }
+ break;
+ case SPDK_MEM_MAP_NOTIFY_UNREGISTER:
+ /* Clear the same region in the other mem_map to be able to
+ * verify that there was no memory left still registered after
+ * the mem_map creation failure.
+ */
+ spdk_mem_map_clear_translation(reg_map, (uint64_t)vaddr, size);
+ break;
+ }
+
+ return 0;
+}
+
+static int
+test_mem_map_notify_checklen(void *cb_ctx, struct spdk_mem_map *map,
+ enum spdk_mem_map_notify_action action, void *vaddr, size_t size)
+{
+ size_t *len_arr = cb_ctx;
+
+ /*
+ * This is a test requirement - the len array we use to verify
+ * pages are valid is only so large.
+ */
+ SPDK_CU_ASSERT_FATAL((uintptr_t)vaddr < (VALUE_2MB * PAGE_ARRAY_SIZE));
+
+ switch (action) {
+ case SPDK_MEM_MAP_NOTIFY_REGISTER:
+ assert(size == len_arr[(uintptr_t)vaddr / VALUE_2MB]);
+ break;
+ case SPDK_MEM_MAP_NOTIFY_UNREGISTER:
+ CU_ASSERT(size == len_arr[(uintptr_t)vaddr / VALUE_2MB]);
+ break;
+ }
+
+ return 0;
+}
+
+static int
+test_check_regions_contiguous(uint64_t addr1, uint64_t addr2)
+{
+ return addr1 == addr2;
+}
+
+const struct spdk_mem_map_ops test_mem_map_ops = {
+ .notify_cb = test_mem_map_notify,
+ .are_contiguous = test_check_regions_contiguous
+};
+
+const struct spdk_mem_map_ops test_mem_map_ops_no_contig = {
+ .notify_cb = test_mem_map_notify,
+ .are_contiguous = NULL
+};
+
+struct spdk_mem_map_ops test_map_ops_notify_fail = {
+ .notify_cb = test_mem_map_notify_fail,
+ .are_contiguous = NULL
+};
+
+struct spdk_mem_map_ops test_map_ops_notify_checklen = {
+ .notify_cb = test_mem_map_notify_checklen,
+ .are_contiguous = NULL
+};
+
+static void
+test_mem_map_alloc_free(void)
+{
+ struct spdk_mem_map *map, *failed_map;
+ uint64_t default_translation = 0xDEADBEEF0BADF00D;
+ int i;
+
+ map = spdk_mem_map_alloc(default_translation, &test_mem_map_ops, NULL);
+ SPDK_CU_ASSERT_FATAL(map != NULL);
+ spdk_mem_map_free(&map);
+ CU_ASSERT(map == NULL);
+
+ map = spdk_mem_map_alloc(default_translation, NULL, NULL);
+ SPDK_CU_ASSERT_FATAL(map != NULL);
+
+ /* Register some memory for the initial memory walk in
+ * spdk_mem_map_alloc(). We'll fail registering the last region
+ * and will check if the mem_map cleaned up all its previously
+ * initialized translations.
+ */
+ for (i = 0; i < 5; i++) {
+ spdk_mem_register((void *)(uintptr_t)(2 * i * VALUE_2MB), VALUE_2MB);
+ }
+
+ /* The last region */
+ g_vaddr_to_fail = (void *)(8 * VALUE_2MB);
+ failed_map = spdk_mem_map_alloc(default_translation, &test_map_ops_notify_fail, map);
+ CU_ASSERT(failed_map == NULL);
+
+ for (i = 0; i < 4; i++) {
+ uint64_t reg, size = VALUE_2MB;
+
+ reg = spdk_mem_map_translate(map, 2 * i * VALUE_2MB, &size);
+ /* check if `failed_map` didn't leave any translations behind */
+ CU_ASSERT(reg == default_translation);
+ }
+
+ for (i = 0; i < 5; i++) {
+ spdk_mem_unregister((void *)(uintptr_t)(2 * i * VALUE_2MB), VALUE_2MB);
+ }
+
+ spdk_mem_map_free(&map);
+ CU_ASSERT(map == NULL);
+}
+
+static void
+test_mem_map_translation(void)
+{
+ struct spdk_mem_map *map;
+ uint64_t default_translation = 0xDEADBEEF0BADF00D;
+ uint64_t addr;
+ uint64_t mapping_length;
+ int rc;
+
+ map = spdk_mem_map_alloc(default_translation, &test_mem_map_ops, NULL);
+ SPDK_CU_ASSERT_FATAL(map != NULL);
+
+ /* Try to get translation for address with no translation */
+ addr = spdk_mem_map_translate(map, 10, NULL);
+ CU_ASSERT(addr == default_translation);
+
+ /* Set translation for region of non-2MB multiple size */
+ rc = spdk_mem_map_set_translation(map, VALUE_2MB, 1234, VALUE_2MB);
+ CU_ASSERT(rc == -EINVAL);
+
+ /* Set translation for vaddr that isn't 2MB aligned */
+ rc = spdk_mem_map_set_translation(map, 1234, VALUE_2MB, VALUE_2MB);
+ CU_ASSERT(rc == -EINVAL);
+
+ /* Set translation for one 2MB page */
+ rc = spdk_mem_map_set_translation(map, VALUE_2MB, VALUE_2MB, VALUE_2MB);
+ CU_ASSERT(rc == 0);
+
+ /* Set translation for region that overlaps the previous translation */
+ rc = spdk_mem_map_set_translation(map, 0, 3 * VALUE_2MB, 0);
+ CU_ASSERT(rc == 0);
+
+ /* Make sure we indicate that the three regions are contiguous */
+ mapping_length = VALUE_2MB * 3;
+ addr = spdk_mem_map_translate(map, 0, &mapping_length);
+ CU_ASSERT(addr == 0);
+ CU_ASSERT(mapping_length == VALUE_2MB * 3)
+
+ /* Clear translation for the middle page of the larger region. */
+ rc = spdk_mem_map_clear_translation(map, VALUE_2MB, VALUE_2MB);
+ CU_ASSERT(rc == 0);
+
+ /* Get translation for first page */
+ addr = spdk_mem_map_translate(map, 0, NULL);
+ CU_ASSERT(addr == 0);
+
+ /* Make sure we indicate that the three regions are no longer contiguous */
+ mapping_length = VALUE_2MB * 3;
+ addr = spdk_mem_map_translate(map, 0, &mapping_length);
+ CU_ASSERT(addr == 0);
+ CU_ASSERT(mapping_length == VALUE_2MB)
+
+ /* Get translation for an unallocated block. Make sure size is 0 */
+ mapping_length = VALUE_2MB * 3;
+ addr = spdk_mem_map_translate(map, VALUE_2MB, &mapping_length);
+ CU_ASSERT(addr == default_translation);
+ CU_ASSERT(mapping_length == VALUE_2MB)
+
+ /* Verify translation for 2nd page is the default */
+ addr = spdk_mem_map_translate(map, VALUE_2MB, NULL);
+ CU_ASSERT(addr == default_translation);
+
+ /* Get translation for third page */
+ addr = spdk_mem_map_translate(map, 2 * VALUE_2MB, NULL);
+ /*
+ * Note that addr should be 0, not 4MB. When we set the
+ * translation above, we said the whole 6MB region
+ * should translate to 0.
+ */
+ CU_ASSERT(addr == 0);
+
+ /* Clear translation for the first page */
+ rc = spdk_mem_map_clear_translation(map, 0, VALUE_2MB);
+ CU_ASSERT(rc == 0);
+
+ /* Get translation for the first page */
+ addr = spdk_mem_map_translate(map, 0, NULL);
+ CU_ASSERT(addr == default_translation);
+
+ /* Clear translation for the third page */
+ rc = spdk_mem_map_clear_translation(map, 2 * VALUE_2MB, VALUE_2MB);
+ CU_ASSERT(rc == 0);
+
+ /* Get translation for the third page */
+ addr = spdk_mem_map_translate(map, 2 * VALUE_2MB, NULL);
+ CU_ASSERT(addr == default_translation);
+
+ /* Set translation for the last valid 2MB region */
+ rc = spdk_mem_map_set_translation(map, 0xffffffe00000ULL, VALUE_2MB, 0x1234);
+ CU_ASSERT(rc == 0);
+
+ /* Verify translation for last valid 2MB region */
+ addr = spdk_mem_map_translate(map, 0xffffffe00000ULL, NULL);
+ CU_ASSERT(addr == 0x1234);
+
+ /* Attempt to set translation for the first invalid address */
+ rc = spdk_mem_map_set_translation(map, 0x1000000000000ULL, VALUE_2MB, 0x5678);
+ CU_ASSERT(rc == -EINVAL);
+
+ /* Attempt to set translation starting at a valid address but exceeding the valid range */
+ rc = spdk_mem_map_set_translation(map, 0xffffffe00000ULL, VALUE_2MB * 2, 0x123123);
+ CU_ASSERT(rc != 0);
+
+ spdk_mem_map_free(&map);
+ CU_ASSERT(map == NULL);
+
+ /* Allocate a map without a contiguous region checker */
+ map = spdk_mem_map_alloc(default_translation, &test_mem_map_ops_no_contig, NULL);
+ SPDK_CU_ASSERT_FATAL(map != NULL);
+
+ /* map three contiguous regions */
+ rc = spdk_mem_map_set_translation(map, 0, 3 * VALUE_2MB, 0);
+ CU_ASSERT(rc == 0);
+
+ /* Since we can't check their contiguity, make sure we only return the size of one page */
+ mapping_length = VALUE_2MB * 3;
+ addr = spdk_mem_map_translate(map, 0, &mapping_length);
+ CU_ASSERT(addr == 0);
+ CU_ASSERT(mapping_length == VALUE_2MB)
+
+ /* Clear the translation */
+ rc = spdk_mem_map_clear_translation(map, 0, VALUE_2MB * 3);
+ CU_ASSERT(rc == 0);
+
+ spdk_mem_map_free(&map);
+ CU_ASSERT(map == NULL);
+}
+
+static void
+test_mem_map_registration(void)
+{
+ int rc;
+ struct spdk_mem_map *map;
+ uint64_t default_translation = 0xDEADBEEF0BADF00D;
+
+ map = spdk_mem_map_alloc(default_translation, &test_mem_map_ops, NULL);
+ SPDK_CU_ASSERT_FATAL(map != NULL);
+
+ /* Unregister memory region that wasn't previously registered */
+ rc = spdk_mem_unregister((void *)VALUE_2MB, VALUE_2MB);
+ CU_ASSERT(rc == -EINVAL);
+
+ /* Register non-2MB multiple size */
+ rc = spdk_mem_register((void *)VALUE_2MB, 1234);
+ CU_ASSERT(rc == -EINVAL);
+
+ /* Register region that isn't 2MB aligned */
+ rc = spdk_mem_register((void *)1234, VALUE_2MB);
+ CU_ASSERT(rc == -EINVAL);
+
+ /* Register one 2MB page */
+ rc = spdk_mem_register((void *)VALUE_2MB, VALUE_2MB);
+ CU_ASSERT(rc == 0);
+
+ /* Register an overlapping address range */
+ rc = spdk_mem_register((void *)0, 3 * VALUE_2MB);
+ CU_ASSERT(rc == -EBUSY);
+
+ /* Unregister a 2MB page */
+ rc = spdk_mem_unregister((void *)VALUE_2MB, VALUE_2MB);
+ CU_ASSERT(rc == 0);
+
+ /* Register non overlapping address range */
+ rc = spdk_mem_register((void *)0, 3 * VALUE_2MB);
+ CU_ASSERT(rc == 0);
+
+ /* Unregister the middle page of the larger region. */
+ rc = spdk_mem_unregister((void *)VALUE_2MB, VALUE_2MB);
+ CU_ASSERT(rc == -ERANGE);
+
+ /* Unregister the first page */
+ rc = spdk_mem_unregister((void *)0, VALUE_2MB);
+ CU_ASSERT(rc == -ERANGE);
+
+ /* Unregister the third page */
+ rc = spdk_mem_unregister((void *)(2 * VALUE_2MB), VALUE_2MB);
+ CU_ASSERT(rc == -ERANGE);
+
+ /* Unregister the entire address range */
+ rc = spdk_mem_unregister((void *)0, 3 * VALUE_2MB);
+ CU_ASSERT(rc == 0);
+
+ spdk_mem_map_free(&map);
+ CU_ASSERT(map == NULL);
+}
+
+static void
+test_mem_map_registration_adjacent(void)
+{
+ struct spdk_mem_map *map, *newmap;
+ uint64_t default_translation = 0xDEADBEEF0BADF00D;
+ uintptr_t vaddr;
+ unsigned i;
+ size_t notify_len[PAGE_ARRAY_SIZE] = {0};
+ size_t chunk_len[] = { 2, 1, 3, 2, 1, 1 };
+
+ map = spdk_mem_map_alloc(default_translation,
+ &test_map_ops_notify_checklen, notify_len);
+ SPDK_CU_ASSERT_FATAL(map != NULL);
+
+ vaddr = 0;
+ for (i = 0; i < SPDK_COUNTOF(chunk_len); i++) {
+ notify_len[vaddr / VALUE_2MB] = chunk_len[i] * VALUE_2MB;
+ spdk_mem_register((void *)vaddr, notify_len[vaddr / VALUE_2MB]);
+ vaddr += notify_len[vaddr / VALUE_2MB];
+ }
+
+ /* Verify the memory is translated in the same chunks it was registered */
+ newmap = spdk_mem_map_alloc(default_translation,
+ &test_map_ops_notify_checklen, notify_len);
+ SPDK_CU_ASSERT_FATAL(newmap != NULL);
+ spdk_mem_map_free(&newmap);
+ CU_ASSERT(newmap == NULL);
+
+ vaddr = 0;
+ for (i = 0; i < SPDK_COUNTOF(chunk_len); i++) {
+ notify_len[vaddr / VALUE_2MB] = chunk_len[i] * VALUE_2MB;
+ spdk_mem_unregister((void *)vaddr, notify_len[vaddr / VALUE_2MB]);
+ vaddr += notify_len[vaddr / VALUE_2MB];
+ }
+
+ /* Register all chunks again just to unregister them again, but this
+ * time with only a single unregister() call.
+ */
+ vaddr = 0;
+ for (i = 0; i < SPDK_COUNTOF(chunk_len); i++) {
+ notify_len[vaddr / VALUE_2MB] = chunk_len[i] * VALUE_2MB;
+ spdk_mem_register((void *)vaddr, notify_len[vaddr / VALUE_2MB]);
+ vaddr += notify_len[vaddr / VALUE_2MB];
+ }
+ spdk_mem_unregister(0, vaddr);
+
+ spdk_mem_map_free(&map);
+ CU_ASSERT(map == NULL);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ /*
+ * These tests can use PAGE_ARRAY_SIZE 2MB pages of memory.
+ * Note that the tests just verify addresses - this memory
+ * is not actually allocated.
+ */
+ g_page_array = spdk_bit_array_create(PAGE_ARRAY_SIZE);
+
+ /* Initialize the memory map */
+ if (spdk_mem_map_init() < 0) {
+ return CUE_NOMEMORY;
+ }
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("memory", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "alloc and free memory map", test_mem_map_alloc_free) == NULL ||
+ CU_add_test(suite, "mem map translation", test_mem_map_translation) == NULL ||
+ CU_add_test(suite, "mem map registration", test_mem_map_registration) == NULL ||
+ CU_add_test(suite, "mem map adjacent registrations", test_mem_map_registration_adjacent) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ spdk_bit_array_free(&g_page_array);
+
+ return num_failures;
+}
diff --git a/src/spdk/test/env/pci/.gitignore b/src/spdk/test/env/pci/.gitignore
new file mode 100644
index 00000000..11d1c65b
--- /dev/null
+++ b/src/spdk/test/env/pci/.gitignore
@@ -0,0 +1 @@
+pci_ut
diff --git a/src/spdk/test/env/pci/Makefile b/src/spdk/test/env/pci/Makefile
new file mode 100644
index 00000000..34165584
--- /dev/null
+++ b/src/spdk/test/env/pci/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
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+CFLAGS += $(ENV_CFLAGS)
+LIBS += $(ENV_LINKER_ARGS)
+TEST_FILE = pci_ut.c
+
+ADDITIONAL_LIBS += $(ENV_LIBS)
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/env/pci/pci_ut.c b/src/spdk/test/env/pci/pci_ut.c
new file mode 100644
index 00000000..bdb3a7cc
--- /dev/null
+++ b/src/spdk/test/env/pci/pci_ut.c
@@ -0,0 +1,94 @@
+/*-
+ * 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 "CUnit/Basic.h"
+
+#include "env_dpdk/pci.c"
+
+static void
+pci_test(void)
+{
+ int rc = 0;
+ pid_t childPid;
+ int status, ret;
+ struct spdk_pci_addr pci_addr;
+
+ pci_addr.domain = 0x0;
+ pci_addr.bus = 0x5;
+ pci_addr.dev = 0x4;
+ pci_addr.func = 1;
+
+ rc = spdk_pci_device_claim(&pci_addr);
+ CU_ASSERT(rc >= 0);
+
+ childPid = fork();
+ CU_ASSERT(childPid >= 0);
+ if (childPid == 0) {
+ ret = spdk_pci_device_claim(&pci_addr);
+ CU_ASSERT(ret == -1);
+ exit(0);
+ } else {
+ waitpid(childPid, &status, 0);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("pci", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "pci_ut1", pci_test) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/env/vtophys/.gitignore b/src/spdk/test/env/vtophys/.gitignore
new file mode 100644
index 00000000..a03b46cc
--- /dev/null
+++ b/src/spdk/test/env/vtophys/.gitignore
@@ -0,0 +1 @@
+vtophys
diff --git a/src/spdk/test/env/vtophys/Makefile b/src/spdk/test/env/vtophys/Makefile
new file mode 100644
index 00000000..7dc4d005
--- /dev/null
+++ b/src/spdk/test/env/vtophys/Makefile
@@ -0,0 +1,42 @@
+#
+# 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.app.mk
+
+LIBS += $(ENV_LINKER_ARGS)
+TEST_FILE = vtophys.c
+
+ADDITIONAL_LIBS += $(ENV_LIBS)
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/env/vtophys/vtophys.c b/src/spdk/test/env/vtophys/vtophys.c
new file mode 100644
index 00000000..d672b1ae
--- /dev/null
+++ b/src/spdk/test/env/vtophys/vtophys.c
@@ -0,0 +1,135 @@
+/*-
+ * 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"
+
+static int
+vtophys_negative_test(void)
+{
+ void *p = NULL;
+ int i;
+ unsigned int size = 1;
+ int rc = 0;
+
+ for (i = 0; i < 31; i++) {
+ p = malloc(size);
+ if (p == NULL) {
+ continue;
+ }
+
+ if (spdk_vtophys(p) != SPDK_VTOPHYS_ERROR) {
+ rc = -1;
+ printf("Err: VA=%p is mapped to a huge_page,\n", p);
+ free(p);
+ break;
+ }
+
+ free(p);
+ size = size << 1;
+ }
+
+ /* Test addresses that are not in the valid x86-64 usermode range */
+
+ if (spdk_vtophys((void *)0x0000800000000000ULL) != SPDK_VTOPHYS_ERROR) {
+ rc = -1;
+ printf("Err: kernel-mode address incorrectly allowed\n");
+ }
+
+ if (!rc) {
+ printf("vtophys_negative_test passed\n");
+ } else {
+ printf("vtophys_negative_test failed\n");
+ }
+
+ return rc;
+}
+
+static int
+vtophys_positive_test(void)
+{
+ void *p = NULL;
+ int i;
+ unsigned int size = 1;
+ int rc = 0;
+
+ for (i = 0; i < 31; i++) {
+ p = spdk_dma_zmalloc(size, 512, NULL);
+ if (p == NULL) {
+ continue;
+ }
+
+ if (spdk_vtophys(p) == SPDK_VTOPHYS_ERROR) {
+ rc = -1;
+ printf("Err: VA=%p is not mapped to a huge_page,\n", p);
+ spdk_dma_free(p);
+ break;
+ }
+
+ spdk_dma_free(p);
+ size = size << 1;
+ }
+
+ if (!rc) {
+ printf("vtophys_positive_test passed\n");
+ } else {
+ printf("vtophys_positive_test failed\n");
+ }
+
+ return rc;
+}
+
+int
+main(int argc, char **argv)
+{
+ int rc;
+ struct spdk_env_opts opts;
+
+ spdk_env_opts_init(&opts);
+ opts.name = "vtophys";
+ opts.core_mask = "0x1";
+ opts.mem_size = 256;
+ if (spdk_env_init(&opts) < 0) {
+ printf("Err: Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ rc = vtophys_negative_test();
+ if (rc < 0) {
+ return rc;
+ }
+
+ rc = vtophys_positive_test();
+ return rc;
+}
diff --git a/src/spdk/test/event/Makefile b/src/spdk/test/event/Makefile
new file mode 100644
index 00000000..b3e9cf1b
--- /dev/null
+++ b/src/spdk/test/event/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 = event_perf reactor reactor_perf
+
+.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/event/event.sh b/src/spdk/test/event/event.sh
new file mode 100755
index 00000000..e1d080b1
--- /dev/null
+++ b/src/spdk/test/event/event.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/test/common/autotest_common.sh
+
+timing_enter event
+$testdir/event_perf/event_perf -m 0xF -t 1
+$testdir/reactor/reactor -t 1
+$testdir/reactor_perf/reactor_perf -t 1
+report_test_completion "event"
+timing_exit event
diff --git a/src/spdk/test/event/event_perf/.gitignore b/src/spdk/test/event/event_perf/.gitignore
new file mode 100644
index 00000000..2bdb558d
--- /dev/null
+++ b/src/spdk/test/event/event_perf/.gitignore
@@ -0,0 +1 @@
+event_perf
diff --git a/src/spdk/test/event/event_perf/Makefile b/src/spdk/test/event/event_perf/Makefile
new file mode 100644
index 00000000..a3aef2ea
--- /dev/null
+++ b/src/spdk/test/event/event_perf/Makefile
@@ -0,0 +1,54 @@
+#
+# 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.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+
+APP = event_perf
+C_SRCS := event_perf.c
+
+SPDK_LIB_LIST = event trace conf thread util log rpc jsonrpc json
+
+LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS)
+
+all : $(APP)
+ @:
+
+$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(ENV_LIBS)
+ $(LINK_C)
+
+clean :
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk
diff --git a/src/spdk/test/event/event_perf/event_perf.c b/src/spdk/test/event/event_perf/event_perf.c
new file mode 100644
index 00000000..917ea950
--- /dev/null
+++ b/src/spdk/test/event/event_perf/event_perf.c
@@ -0,0 +1,180 @@
+/*-
+ * 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"
+#include "spdk_internal/event.h"
+#include "spdk/log.h"
+
+static uint64_t g_tsc_rate;
+static uint64_t g_tsc_us_rate;
+static uint64_t g_tsc_end;
+
+static int g_time_in_sec;
+
+static uint64_t *call_count;
+
+static bool g_app_stopped = false;
+
+static void
+submit_new_event(void *arg1, void *arg2)
+{
+ struct spdk_event *event;
+ static __thread uint32_t next_lcore = UINT32_MAX;
+
+ if (spdk_get_ticks() > g_tsc_end) {
+ if (__sync_bool_compare_and_swap(&g_app_stopped, false, true)) {
+ spdk_app_stop(0);
+ }
+ return;
+ }
+
+ if (next_lcore == UINT32_MAX) {
+ next_lcore = spdk_env_get_next_core(spdk_env_get_current_core());
+ if (next_lcore == UINT32_MAX) {
+ next_lcore = spdk_env_get_first_core();
+ }
+ }
+
+ call_count[next_lcore]++;
+ event = spdk_event_allocate(next_lcore, submit_new_event, NULL, NULL);
+ spdk_event_call(event);
+}
+
+static void
+event_work_fn(void *arg1, void *arg2)
+{
+
+ submit_new_event(NULL, NULL);
+ submit_new_event(NULL, NULL);
+ submit_new_event(NULL, NULL);
+ submit_new_event(NULL, NULL);
+}
+
+static void
+event_perf_start(void *arg1, void *arg2)
+{
+ uint32_t i;
+
+ call_count = calloc(spdk_env_get_last_core() + 1, sizeof(*call_count));
+ if (call_count == NULL) {
+ fprintf(stderr, "call_count allocation failed\n");
+ spdk_app_stop(1);
+ return;
+ }
+
+ g_tsc_rate = spdk_get_ticks_hz();
+ g_tsc_us_rate = g_tsc_rate / (1000 * 1000);
+ g_tsc_end = spdk_get_ticks() + g_time_in_sec * g_tsc_rate;
+
+ printf("Running I/O for %d seconds...", g_time_in_sec);
+ fflush(stdout);
+
+ SPDK_ENV_FOREACH_CORE(i) {
+ spdk_event_call(spdk_event_allocate(i, event_work_fn,
+ NULL, NULL));
+ }
+
+}
+
+static void
+usage(char *program_name)
+{
+ printf("%s options\n", program_name);
+ printf("\t[-m core mask for distributing I/O submission/completion work\n");
+ printf("\t\t(default: 0x1 - use core 0 only)]\n");
+ printf("\t[-t time in seconds]\n");
+}
+
+static void
+performance_dump(int io_time)
+{
+ uint32_t i;
+
+ if (call_count == NULL) {
+ return;
+ }
+
+ printf("\n");
+ SPDK_ENV_FOREACH_CORE(i) {
+ printf("lcore %2d: %8ju\n", i, call_count[i] / g_time_in_sec);
+ }
+
+ fflush(stdout);
+ free(call_count);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct spdk_app_opts opts = {};
+ int op;
+ int rc = 0;
+
+ opts.name = "event_perf";
+ opts.mem_size = 256;
+
+ g_time_in_sec = 0;
+
+ while ((op = getopt(argc, argv, "m:t:")) != -1) {
+ switch (op) {
+ case 'm':
+ opts.reactor_mask = optarg;
+ break;
+ case 't':
+ g_time_in_sec = atoi(optarg);
+ break;
+ default:
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+ if (!g_time_in_sec) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ printf("Running I/O for %d seconds...", g_time_in_sec);
+ fflush(stdout);
+
+ rc = spdk_app_start(&opts, event_perf_start, NULL, NULL);
+
+ spdk_app_fini();
+ performance_dump(g_time_in_sec);
+
+ printf("done.\n");
+ return rc;
+}
diff --git a/src/spdk/test/event/reactor/.gitignore b/src/spdk/test/event/reactor/.gitignore
new file mode 100644
index 00000000..194b15d7
--- /dev/null
+++ b/src/spdk/test/event/reactor/.gitignore
@@ -0,0 +1 @@
+reactor
diff --git a/src/spdk/test/event/reactor/Makefile b/src/spdk/test/event/reactor/Makefile
new file mode 100644
index 00000000..c5a6168a
--- /dev/null
+++ b/src/spdk/test/event/reactor/Makefile
@@ -0,0 +1,54 @@
+#
+# 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.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+
+APP = reactor
+C_SRCS := reactor.c
+
+SPDK_LIB_LIST = event trace conf thread util log rpc jsonrpc json
+
+LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS)
+
+all : $(APP)
+ @:
+
+$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(ENV_LIBS)
+ $(LINK_C)
+
+clean :
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk
diff --git a/src/spdk/test/event/reactor/reactor.c b/src/spdk/test/event/reactor/reactor.c
new file mode 100644
index 00000000..d79f94ba
--- /dev/null
+++ b/src/spdk/test/event/reactor/reactor.c
@@ -0,0 +1,144 @@
+/*-
+ * 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/thread.h"
+
+static int g_time_in_sec;
+static struct spdk_poller *test_end_poller;
+static struct spdk_poller *poller_100ms;
+static struct spdk_poller *poller_250ms;
+static struct spdk_poller *poller_500ms;
+static struct spdk_poller *poller_oneshot;
+static struct spdk_poller *poller_unregister;
+
+static int
+test_end(void *arg)
+{
+ printf("test_end\n");
+
+ spdk_poller_unregister(&test_end_poller);
+ spdk_poller_unregister(&poller_100ms);
+ spdk_poller_unregister(&poller_250ms);
+ spdk_poller_unregister(&poller_500ms);
+
+ spdk_app_stop(0);
+ return -1;
+}
+
+static int
+tick(void *arg)
+{
+ uintptr_t period = (uintptr_t)arg;
+
+ printf("tick %" PRIu64 "\n", (uint64_t)period);
+
+ return -1;
+}
+
+static int
+oneshot(void *arg)
+{
+ printf("oneshot\n");
+ spdk_poller_unregister(&poller_oneshot);
+
+ return -1;
+}
+
+static int
+nop(void *arg)
+{
+ return -1;
+}
+
+static void
+test_start(void *arg1, void *arg2)
+{
+ printf("test_start\n");
+
+ /* Register a poller that will stop the test after the time has elapsed. */
+ test_end_poller = spdk_poller_register(test_end, NULL, g_time_in_sec * 1000000ULL);
+
+ poller_100ms = spdk_poller_register(tick, (void *)100, 100000);
+ poller_250ms = spdk_poller_register(tick, (void *)250, 250000);
+ poller_500ms = spdk_poller_register(tick, (void *)500, 500000);
+ poller_oneshot = spdk_poller_register(oneshot, NULL, 0);
+
+ poller_unregister = spdk_poller_register(nop, NULL, 0);
+ spdk_poller_unregister(&poller_unregister);
+}
+
+static void
+usage(const char *program_name)
+{
+ printf("%s options\n", program_name);
+ printf("\t[-t time in seconds]\n");
+}
+
+int
+main(int argc, char **argv)
+{
+ struct spdk_app_opts opts;
+ int op;
+ int rc = 0;
+
+ spdk_app_opts_init(&opts);
+ opts.name = "reactor";
+ opts.max_delay_us = 1000;
+
+ g_time_in_sec = 0;
+
+ while ((op = getopt(argc, argv, "t:")) != -1) {
+ switch (op) {
+ case 't':
+ g_time_in_sec = atoi(optarg);
+ break;
+ default:
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+ if (!g_time_in_sec) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ rc = spdk_app_start(&opts, test_start, NULL, NULL);
+
+ spdk_app_fini();
+
+ return rc;
+}
diff --git a/src/spdk/test/event/reactor_perf/.gitignore b/src/spdk/test/event/reactor_perf/.gitignore
new file mode 100644
index 00000000..32160228
--- /dev/null
+++ b/src/spdk/test/event/reactor_perf/.gitignore
@@ -0,0 +1 @@
+reactor_perf
diff --git a/src/spdk/test/event/reactor_perf/Makefile b/src/spdk/test/event/reactor_perf/Makefile
new file mode 100644
index 00000000..820a6042
--- /dev/null
+++ b/src/spdk/test/event/reactor_perf/Makefile
@@ -0,0 +1,54 @@
+#
+# 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.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+
+APP = reactor_perf
+C_SRCS := reactor_perf.c
+
+SPDK_LIB_LIST = event trace conf thread util log rpc jsonrpc json
+
+LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS)
+
+all : $(APP)
+ @:
+
+$(APP) : $(OBJS) $(SPDK_LIB_FILES) $(ENV_LIBS)
+ $(LINK_C)
+
+clean :
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk
diff --git a/src/spdk/test/event/reactor_perf/reactor_perf.c b/src/spdk/test/event/reactor_perf/reactor_perf.c
new file mode 100644
index 00000000..357f9038
--- /dev/null
+++ b/src/spdk/test/event/reactor_perf/reactor_perf.c
@@ -0,0 +1,144 @@
+/*-
+ * 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"
+#include "spdk/thread.h"
+
+static int g_time_in_sec;
+static int g_queue_depth;
+static struct spdk_poller *test_end_poller;
+static uint64_t g_call_count = 0;
+
+static int
+__test_end(void *arg)
+{
+ printf("test_end\n");
+ spdk_app_stop(0);
+ return -1;
+}
+
+static void
+__submit_next(void *arg1, void *arg2)
+{
+ struct spdk_event *event;
+
+ g_call_count++;
+
+ event = spdk_event_allocate(spdk_env_get_current_core(),
+ __submit_next, NULL, NULL);
+ spdk_event_call(event);
+}
+
+static void
+test_start(void *arg1, void *arg2)
+{
+ int i;
+
+ printf("test_start\n");
+
+ /* Register a poller that will stop the test after the time has elapsed. */
+ test_end_poller = spdk_poller_register(__test_end, NULL,
+ g_time_in_sec * 1000000ULL);
+
+ for (i = 0; i < g_queue_depth; i++) {
+ __submit_next(NULL, NULL);
+ }
+}
+
+static void
+test_cleanup(void)
+{
+ printf("test_abort\n");
+
+ spdk_poller_unregister(&test_end_poller);
+ spdk_app_stop(0);
+}
+
+static void
+usage(const char *program_name)
+{
+ printf("%s options\n", program_name);
+ printf("\t[-d Allowed delay when passing messages between cores in microseconds]\n");
+ printf("\t[-q Queue depth (default: 1)]\n");
+ printf("\t[-t time in seconds]\n");
+}
+
+int
+main(int argc, char **argv)
+{
+ struct spdk_app_opts opts;
+ int op;
+ int rc;
+
+ spdk_app_opts_init(&opts);
+ opts.name = "reactor_perf";
+ opts.max_delay_us = 1000;
+
+ g_time_in_sec = 0;
+ g_queue_depth = 1;
+
+ while ((op = getopt(argc, argv, "d:q:t:")) != -1) {
+ switch (op) {
+ case 'd':
+ opts.max_delay_us = atoi(optarg);
+ break;
+ case 'q':
+ g_queue_depth = atoi(optarg);
+ break;
+ case 't':
+ g_time_in_sec = atoi(optarg);
+ break;
+ default:
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+ if (!g_time_in_sec) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ opts.shutdown_cb = test_cleanup;
+
+ rc = spdk_app_start(&opts, test_start, NULL, NULL);
+
+ spdk_app_fini();
+
+ printf("Performance: %8ju events per second\n", g_call_count / g_time_in_sec);
+
+ return rc;
+}
diff --git a/src/spdk/test/ioat/ioat.sh b/src/spdk/test/ioat/ioat.sh
new file mode 100755
index 00000000..b15f635e
--- /dev/null
+++ b/src/spdk/test/ioat/ioat.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+set -xe
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/test/common/autotest_common.sh
+
+timing_enter ioat
+
+timing_enter perf
+$rootdir/examples/ioat/perf/perf -t 1
+timing_exit perf
+
+timing_enter verify
+$rootdir/examples/ioat/verify/verify -t 1
+timing_exit verify
+
+report_test_completion "ioat"
+timing_exit ioat
diff --git a/src/spdk/test/iscsi_tgt/bdev_io_wait/bdev_io_wait.sh b/src/spdk/test/iscsi_tgt/bdev_io_wait/bdev_io_wait.sh
new file mode 100755
index 00000000..94137507
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/bdev_io_wait/bdev_io_wait.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+timing_enter bdev_io_wait
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+timing_enter start_iscsi_tgt
+
+# Start the iSCSI target without using stub
+# Reason: Two SPDK processes will be started
+$ISCSI_APP -m 0x2 -p 1 -s 512 --wait-for-rpc &
+pid=$!
+echo "iSCSI target launched. pid: $pid"
+trap "killprocess $pid;exit 1" SIGINT SIGTERM EXIT
+waitforlisten $pid
+$rpc_py set_iscsi_options -o 30 -a 4
+# Minimal number of bdev io pool (5) and cache (1)
+$rpc_py set_bdev_options -p 5 -c 1
+$rpc_py start_subsystem_init
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+$rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE
+# "Malloc0:0" ==> use Malloc0 blockdev for LUN0
+# "1:2" ==> map PortalGroup1 to InitiatorGroup2
+# "64" ==> iSCSI queue depth 64
+# "-d" ==> disable CHAP authentication
+$rpc_py construct_target_node disk1 disk1_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 256 -d
+sleep 1
+trap "killprocess $pid; rm -f $testdir/bdev.conf; exit 1" SIGINT SIGTERM EXIT
+
+# Prepare config file for iSCSI initiator
+echo "[iSCSI_Initiator]" > $testdir/bdev.conf
+echo " URL iscsi://$TARGET_IP/iqn.2016-06.io.spdk:disk1/0 iSCSI0" >> $testdir/bdev.conf
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w write -t 1
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w read -t 1
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w flush -t 1
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w unmap -t 1
+rm -f $testdir/bdev.conf
+
+trap - SIGINT SIGTERM EXIT
+
+killprocess $pid
+
+report_test_completion "bdev_io_wait"
+timing_exit bdev_io_wait
diff --git a/src/spdk/test/iscsi_tgt/calsoft/auth.conf b/src/spdk/test/iscsi_tgt/calsoft/auth.conf
new file mode 100644
index 00000000..303bac31
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/calsoft/auth.conf
@@ -0,0 +1,3 @@
+[AuthGroup1]
+ Comment "Auth Group1"
+ Auth "root" "tester"
diff --git a/src/spdk/test/iscsi_tgt/calsoft/calsoft.py b/src/spdk/test/iscsi_tgt/calsoft/calsoft.py
new file mode 100755
index 00000000..2970328e
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/calsoft/calsoft.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+
+import os
+import time
+import sys
+import subprocess
+import threading
+import json
+
+CALSOFT_BIN_PATH = "/usr/local/calsoft/iscsi-pcts-v1.5/bin"
+
+'''
+11/26/2015 disable tc_login_11_2 and tc_login_11_4
+RFC 7143 6.3
+Neither the initiator nor the target should attempt to declare or
+negotiate a parameter more than once during login, except for
+responses to specific keys that explicitly allow repeated key
+declarations (e.g., TargetAddress)
+
+The spec didn't make it clear what other keys could be re-declare
+Disscussed this with UNH and get the conclusion that TargetName/
+TargetAddress/MaxRecvDataSegmentLength could be re-declare.
+'''
+'''
+12/1/2015 add tc_login_2_2 to known_failed_cases
+RFC 7143 6.1
+A standard-label MUST begin with a capital letter and must not exceed
+63 characters.
+key name: A standard-label
+'''
+known_failed_cases = ['tc_ffp_15_2', 'tc_ffp_29_2', 'tc_ffp_29_3', 'tc_ffp_29_4',
+ 'tc_err_1_1', 'tc_err_1_2', 'tc_err_2_8',
+ 'tc_err_3_1', 'tc_err_3_2', 'tc_err_3_3',
+ 'tc_err_3_4', 'tc_err_5_1', 'tc_login_3_1',
+ 'tc_login_11_2', 'tc_login_11_4', 'tc_login_2_2']
+
+
+def run_case(case, result_list, log_dir_path):
+ try:
+ case_log = subprocess.check_output("{}/{}".format(CALSOFT_BIN_PATH, case), stderr=subprocess.STDOUT, shell=True)
+ except subprocess.CalledProcessError as e:
+ result_list.append({"Name": case, "Result": "FAIL"})
+ case_log = e.output
+ else:
+ result_list.append({"Name": case, "Result": "PASS"})
+ with open(log_dir_path + case + '.txt', 'w') as f:
+ f.write(case_log)
+
+
+def main():
+ if not os.path.exists(CALSOFT_BIN_PATH):
+ print("The Calsoft test suite is not available on this machine.")
+ sys.exit(1)
+
+ output_dir = sys.argv[1]
+ if len(sys.argv) > 2:
+ output_file = sys.argv[2]
+ else:
+ output_file = "%s/calsoft.json" % (output_dir)
+
+ log_dir = "%s/calsoft/" % output_dir
+
+ all_cases = [x for x in os.listdir(CALSOFT_BIN_PATH) if x.startswith('tc')]
+ all_cases.sort()
+
+ case_result_list = []
+
+ result = {"Calsoft iSCSI tests": case_result_list}
+
+ if not os.path.exists(log_dir):
+ os.mkdir(log_dir)
+ for case in known_failed_cases:
+ print("Skipping %s. It is known to fail." % (case))
+ case_result_list.append({"Name": case, "Result": "SKIP"})
+
+ thread_objs = []
+ left_cases = list(set(all_cases) - set(known_failed_cases))
+ index = 0
+ max_thread_count = 32
+
+ while index < len(left_cases):
+ cur_thread_count = 0
+ for thread_obj in thread_objs:
+ if thread_obj.is_alive():
+ cur_thread_count += 1
+ while cur_thread_count < max_thread_count and index < len(left_cases):
+ thread_obj = threading.Thread(target=run_case, args=(left_cases[index], case_result_list, log_dir, ))
+ thread_obj.start()
+ time.sleep(0.02)
+ thread_objs.append(thread_obj)
+ index += 1
+ cur_thread_count += 1
+ end_time = time.time() + 30
+ while time.time() < end_time:
+ for thread_obj in thread_objs:
+ if thread_obj.is_alive():
+ break
+ else:
+ break
+ else:
+ print("Thread timeout")
+ exit(1)
+ with open(output_file, 'w') as f:
+ json.dump(obj=result, fp=f, indent=2)
+
+ failed = 0
+ for x in case_result_list:
+ if x["Result"] == "FAIL":
+ print("Test case %s failed." % (x["Name"]))
+ failed = 1
+ exit(failed)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/spdk/test/iscsi_tgt/calsoft/calsoft.sh b/src/spdk/test/iscsi_tgt/calsoft/calsoft.sh
new file mode 100755
index 00000000..1a5c3932
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/calsoft/calsoft.sh
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+delete_tmp_conf_files() {
+ rm -f /usr/local/etc/its.conf
+ rm -f /usr/local/etc/auth.conf
+}
+
+if [ ! -d /usr/local/calsoft ]; then
+ echo "skipping calsoft tests"
+ exit 0
+fi
+
+timing_enter calsoft
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+calsoft_py="$testdir/calsoft.py"
+
+# Copy the calsoft config file to /usr/local/etc
+mkdir -p /usr/local/etc
+cp $testdir/its.conf /usr/local/etc/
+cp $testdir/auth.conf /usr/local/etc/
+
+# Append target ip to calsoft config
+echo "IP=$TARGET_IP" >> /usr/local/etc/its.conf
+
+timing_enter start_iscsi_tgt
+
+$ISCSI_APP -m 0x1 --wait-for-rpc &
+pid=$!
+echo "Process pid: $pid"
+
+trap "killprocess $pid; delete_tmp_conf_files; exit 1 " SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py load_subsystem_config < $testdir/iscsi.json
+$rpc_py start_subsystem_init
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+$rpc_py construct_malloc_bdev -b MyBdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE
+# "MyBdev:0" ==> use MyBdev blockdev for LUN0
+# "1:2" ==> map PortalGroup1 to InitiatorGroup2
+# "64" ==> iSCSI queue depth 64
+# "0 0 0 1" ==> enable CHAP authentication using auth group 1
+$rpc_py construct_target_node Target3 Target3_alias 'MyBdev:0' $PORTAL_TAG:$INITIATOR_TAG 64 -g 1
+sleep 1
+
+if [ "$1" ]; then
+ $calsoft_py "$output_dir" "$1"
+ failed=$?
+else
+ $calsoft_py "$output_dir"
+ failed=$?
+fi
+
+trap - SIGINT SIGTERM EXIT
+
+killprocess $pid
+delete_tmp_conf_files
+timing_exit calsoft
+exit $failed
diff --git a/src/spdk/test/iscsi_tgt/calsoft/iscsi.json b/src/spdk/test/iscsi_tgt/calsoft/iscsi.json
new file mode 100644
index 00000000..34e44ca0
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/calsoft/iscsi.json
@@ -0,0 +1,17 @@
+{
+ "subsystem": "iscsi",
+ "config": [
+ {
+ "params": {
+ "allow_duplicated_isid": true,
+ "nop_timeout": 30,
+ "nop_in_interval": 10,
+ "discovery_auth_group": 1,
+ "max_sessions": 256,
+ "error_recovery_level": 2,
+ "auth_file": "/usr/local/etc/auth.conf"
+ },
+ "method": "set_iscsi_options"
+ }
+ ]
+}
diff --git a/src/spdk/test/iscsi_tgt/calsoft/its.conf b/src/spdk/test/iscsi_tgt/calsoft/its.conf
new file mode 100644
index 00000000..6469dab6
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/calsoft/its.conf
@@ -0,0 +1,7 @@
+InitiatorName=iqn.1994-05.com.redhat:b3283535dc3b
+TargetName=iqn.2016-06.io.spdk:Target3
+DefaultTime2Retain=20
+DefaultTime2Wait=2
+AuthMethod=CHAP,None
+UserName=root
+PassWord=tester
diff --git a/src/spdk/test/iscsi_tgt/common.sh b/src/spdk/test/iscsi_tgt/common.sh
new file mode 100644
index 00000000..1928449b
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/common.sh
@@ -0,0 +1,50 @@
+# Network configuration
+TARGET_INTERFACE="spdk_tgt_int"
+INITIATOR_INTERFACE="spdk_init_int"
+TARGET_NAMESPACE="spdk_iscsi_ns"
+TARGET_NS_CMD="ip netns exec $TARGET_NAMESPACE"
+
+# iSCSI target configuration
+TARGET_IP=10.0.0.1
+INITIATOR_IP=10.0.0.2
+ISCSI_PORT=3260
+NETMASK=$INITIATOR_IP/32
+INITIATOR_TAG=2
+INITIATOR_NAME=ANY
+PORTAL_TAG=1
+ISCSI_APP="$TARGET_NS_CMD ./app/iscsi_tgt/iscsi_tgt -i 0"
+ISCSI_TEST_CORE_MASK=0xFF
+
+function create_veth_interfaces() {
+ # $1 = test type (posix/vpp)
+ ip netns del $TARGET_NAMESPACE || true
+ ip link delete $INITIATOR_INTERFACE || true
+
+ trap "cleanup_veth_interfaces $1; exit 1" SIGINT SIGTERM EXIT
+
+ # Create veth (Virtual ethernet) interface pair
+ ip link add $INITIATOR_INTERFACE type veth peer name $TARGET_INTERFACE
+ ip addr add $INITIATOR_IP/24 dev $INITIATOR_INTERFACE
+ ip link set $INITIATOR_INTERFACE up
+
+ # Create and add interface for target to network namespace
+ ip netns add $TARGET_NAMESPACE
+ ip link set $TARGET_INTERFACE netns $TARGET_NAMESPACE
+
+ $TARGET_NS_CMD ip link set lo up
+ $TARGET_NS_CMD ip addr add $TARGET_IP/24 dev $TARGET_INTERFACE
+ $TARGET_NS_CMD ip link set $TARGET_INTERFACE up
+
+ # Verify connectivity
+ ping -c 1 $TARGET_IP
+ ip netns exec $TARGET_NAMESPACE ping -c 1 $INITIATOR_IP
+}
+
+function cleanup_veth_interfaces() {
+ # $1 = test type (posix/vpp)
+
+ # Cleanup veth interfaces and network namespace
+ # Note: removing one veth, removes the pair
+ ip link delete $INITIATOR_INTERFACE
+ ip netns del $TARGET_NAMESPACE
+}
diff --git a/src/spdk/test/iscsi_tgt/digests/digests.sh b/src/spdk/test/iscsi_tgt/digests/digests.sh
new file mode 100755
index 00000000..675cf1c1
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/digests/digests.sh
@@ -0,0 +1,104 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+function node_login_fio_logout() {
+ for arg in "$@"; do
+ iscsiadm -m node -p $TARGET_IP:$ISCSI_PORT -o update -n node.conn[0].iscsi.$arg
+ done
+ iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+ sleep 1
+ $fio_py 512 1 write 2
+ $fio_py 512 1 read 2
+ iscsiadm -m node --logout -p $TARGET_IP:$ISCSI_PORT
+ sleep 1
+}
+
+function iscsi_header_digest_test() {
+ # Enable HeaderDigest to CRC32C
+ timing_enter HeaderDigest_enabled
+ node_login_fio_logout "HeaderDigest -v CRC32C"
+ timing_exit HeaderDigest_enabled
+
+ # Let iscsi target to decide its preference on
+ # HeaderDigest based on its capability.
+ timing_enter preferred
+ node_login_fio_logout "HeaderDigest -v CRC32C,None"
+ timing_exit preferred
+}
+
+function iscsi_header_data_digest_test() {
+ # Only enable HeaderDigest to CRC32C
+ timing_enter HeaderDigest_enabled
+ node_login_fio_logout "HeaderDigest -v CRC32C" "DataDigest -v None"
+ timing_exit HeaderDigest_enabled
+
+ # Only enable DataDigest to CRC32C
+ timing_enter DataDigest_enabled
+ node_login_fio_logout "HeaderDigest -v None" "DataDigest -v CRC32C"
+ timing_exit DataDigest_enabled
+
+ # Let iscsi target to decide its preference on both
+ # HeaderDigest and DataDigest based on its capability.
+ timing_enter both_preferred
+ node_login_fio_logout "HeaderDigest -v CRC32C,None" "DataDigest -v CRC32C,None"
+ timing_exit both_preferred
+
+ # Enable HeaderDigest and DataDigest both.
+ timing_enter both_enabled
+ node_login_fio_logout "HeaderDigest -v CRC32C" "DataDigest -v CRC32C"
+ timing_exit both_enabled
+}
+
+timing_enter digests
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+fio_py="$rootdir/scripts/fio.py"
+
+timing_enter start_iscsi_tgt
+
+$ISCSI_APP -m $ISCSI_TEST_CORE_MASK --wait-for-rpc &
+pid=$!
+echo "Process pid: $pid"
+
+trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py set_iscsi_options -o 30 -a 16
+$rpc_py start_subsystem_init
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+$rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE
+# "Malloc0:0" ==> use Malloc0 blockdev for LUN0
+# "1:2" ==> map PortalGroup1 to InitiatorGroup2
+# "64" ==> iSCSI queue depth 64
+# "-d" ==> disable CHAP authentication
+$rpc_py construct_target_node Target3 Target3_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d
+sleep 1
+
+iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+
+# iscsiadm installed by some Fedora releases loses DataDigest parameter.
+# Check and avoid setting DataDigest.
+DataDigestAbility=$(iscsiadm -m node -p $TARGET_IP:$ISCSI_PORT | grep DataDigest || true)
+if [ "$DataDigestAbility"x = x ]; then
+ iscsi_header_digest_test
+else
+ iscsi_header_data_digest_test
+fi
+
+trap - SIGINT SIGTERM EXIT
+
+iscsicleanup
+killprocess $pid
+timing_exit digests
diff --git a/src/spdk/test/iscsi_tgt/ext4test/ext4test.sh b/src/spdk/test/iscsi_tgt/ext4test/ext4test.sh
new file mode 100755
index 00000000..b022cfb7
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/ext4test/ext4test.sh
@@ -0,0 +1,128 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+if [ ! -z $1 ]; then
+ DPDK_DIR=$(readlink -f $1)
+fi
+
+timing_enter ext4test
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+timing_enter start_iscsi_tgt
+
+$ISCSI_APP --wait-for-rpc &
+pid=$!
+echo "Process pid: $pid"
+
+trap "$rpc_py destruct_split_vbdev Name0n1 || true; killprocess $pid; rm -f $testdir/iscsi.conf; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py set_iscsi_options -o 30 -a 4 -b "iqn.2013-06.com.intel.ch.spdk"
+$rpc_py start_subsystem_init
+$rootdir/scripts/gen_nvme.sh --json | $rpc_py load_subsystem_config
+$rpc_py construct_malloc_bdev 512 4096 --name Malloc0
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+$rpc_py construct_error_bdev 'Malloc0'
+# "1:2" ==> map PortalGroup1 to InitiatorGroup2
+# "64" ==> iSCSI queue depth 64
+# "-d" ==> disable CHAP authentication
+$rpc_py construct_target_node Target0 Target0_alias EE_Malloc0:0 1:2 64 -d
+sleep 1
+
+iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+
+trap 'for new_dir in `dir -d /mnt/*dir`; do umount $new_dir; rm -rf $new_dir; done; \
+ iscsicleanup; killprocess $pid; rm -f $testdir/iscsi.conf; exit 1' SIGINT SIGTERM EXIT
+
+sleep 1
+
+echo "Test error injection"
+$rpc_py bdev_inject_error EE_Malloc0 'all' 'failure' -n 1000
+
+dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}')
+
+set +e
+mkfs.ext4 -F /dev/$dev
+if [ $? -eq 0 ]; then
+ echo "mkfs successful - expected failure"
+ iscsicleanup
+ killprocess $pid
+ exit 1
+else
+ echo "mkfs failed as expected"
+fi
+set -e
+
+$rpc_py bdev_inject_error EE_Malloc0 'clear' 'failure'
+echo "Error injection test done"
+
+iscsicleanup
+
+if [ -z "$NO_NVME" ]; then
+ $rpc_py construct_split_vbdev Nvme0n1 2 -s 10000
+ $rpc_py construct_target_node Target1 Target1_alias Nvme0n1p0:0 1:2 64 -d
+fi
+
+iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+
+devs=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}')
+
+for dev in $devs; do
+ mkfs.ext4 -F /dev/$dev
+ mkdir -p /mnt/${dev}dir
+ mount -o sync /dev/$dev /mnt/${dev}dir
+
+ rsync -qav --exclude=".git" --exclude="*.o" $rootdir/ /mnt/${dev}dir/spdk
+
+ make -C /mnt/${dev}dir/spdk clean
+ (cd /mnt/${dev}dir/spdk && ./configure $config_params)
+ make -C /mnt/${dev}dir/spdk -j16
+
+ # Print out space consumed on target device to help decide
+ # if/when we need to increase the size of the malloc LUN
+ df -h /dev/$dev
+
+ rm -rf /mnt/${dev}dir/spdk
+done
+
+for dev in $devs; do
+ umount /mnt/${dev}dir
+ rm -rf /mnt/${dev}dir
+
+ stats=($(cat /sys/block/$dev/stat))
+ echo ""
+ echo "$dev stats"
+ printf "READ IO cnt: % 8u merges: % 8u sectors: % 8u ticks: % 8u\n" \
+ ${stats[0]} ${stats[1]} ${stats[2]} ${stats[3]}
+ printf "WRITE IO cnt: % 8u merges: % 8u sectors: % 8u ticks: % 8u\n" \
+ ${stats[4]} ${stats[5]} ${stats[6]} ${stats[7]}
+ printf "in flight: % 8u io ticks: % 8u time in queue: % 8u\n" \
+ ${stats[8]} ${stats[9]} ${stats[10]}
+ echo ""
+done
+
+trap - SIGINT SIGTERM EXIT
+
+iscsicleanup
+$rpc_py destruct_split_vbdev Nvme0n1
+$rpc_py delete_error_bdev EE_Malloc0
+
+if [ -z "$NO_NVME" ]; then
+ $rpc_py delete_nvme_controller Nvme0
+fi
+
+killprocess $pid
+report_test_completion "nightly_iscsi_ext4test"
+timing_exit ext4test
diff --git a/src/spdk/test/iscsi_tgt/filesystem/filesystem.sh b/src/spdk/test/iscsi_tgt/filesystem/filesystem.sh
new file mode 100755
index 00000000..0c530b3b
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/filesystem/filesystem.sh
@@ -0,0 +1,136 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+source $rootdir/scripts/common.sh
+
+timing_enter filesystem
+
+rpc_py="$rootdir/scripts/rpc.py"
+# Remove lvol bdevs and stores.
+function remove_backends() {
+ echo "INFO: Removing lvol bdev"
+ $rpc_py destroy_lvol_bdev "lvs_0/lbd_0"
+
+ echo "INFO: Removing lvol stores"
+ $rpc_py destroy_lvol_store -l lvs_0
+
+ echo "INFO: Removing NVMe"
+ $rpc_py delete_nvme_controller Nvme0
+
+ return 0
+}
+
+timing_enter start_iscsi_tgt
+
+$ISCSI_APP -m $ISCSI_TEST_CORE_MASK --wait-for-rpc &
+pid=$!
+echo "Process pid: $pid"
+
+trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py set_iscsi_options -o 30 -a 16
+$rpc_py start_subsystem_init
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+bdf=$(iter_pci_class_code 01 08 02 | head -1)
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+$rpc_py construct_nvme_bdev -b "Nvme0" -t "pcie" -a $bdf
+
+ls_guid=$($rpc_py construct_lvol_store Nvme0n1 lvs_0)
+free_mb=$(get_lvs_free_mb "$ls_guid")
+# Using maximum 2048MiB to reduce the test time
+if [ $free_mb -gt 2048 ]; then
+ $rpc_py construct_lvol_bdev -u $ls_guid lbd_0 2048
+else
+ $rpc_py construct_lvol_bdev -u $ls_guid lbd_0 $free_mb
+fi
+# "lvs_0/lbd_0:0" ==> use lvs_0/lbd_0 blockdev for LUN0
+# "1:2" ==> map PortalGroup1 to InitiatorGroup2
+# "256" ==> iSCSI queue depth 256
+# "-d" ==> disable CHAP authentication
+$rpc_py construct_target_node Target1 Target1_alias 'lvs_0/lbd_0:0' $PORTAL_TAG:$INITIATOR_TAG 256 -d
+sleep 1
+
+iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+
+trap "remove_backends; umount /mnt/device; rm -rf /mnt/device; iscsicleanup; killprocess $pid; exit 1" SIGINT SIGTERM EXIT
+
+sleep 1
+
+mkdir -p /mnt/device
+
+dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}')
+
+parted -s /dev/$dev mklabel msdos
+parted -s /dev/$dev mkpart primary '0%' '100%'
+sleep 1
+
+for fstype in "ext4" "btrfs" "xfs"; do
+
+ if [ "$fstype" == "ext4" ]; then
+ mkfs.${fstype} -F /dev/${dev}1
+ else
+ mkfs.${fstype} -f /dev/${dev}1
+ fi
+ mount /dev/${dev}1 /mnt/device
+ if [ $RUN_NIGHTLY -eq 1 ]; then
+ fio -filename=/mnt/device/test -direct=1 -iodepth 64 -thread=1 -invalidate=1 -rw=randwrite -ioengine=libaio -bs=4k \
+ -size=1024M -name=job0
+ umount /mnt/device
+
+ iscsiadm -m node --logout
+ sleep 1
+ iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+ sleep 1
+ dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}')
+ mount -o rw /dev/${dev}1 /mnt/device
+ if [ -f "/mnt/device/test" ]; then
+ echo "File existed."
+ fio -filename=/mnt/device/test -direct=1 -iodepth 64 -thread=1 -invalidate=1 -rw=randread \
+ -ioengine=libaio -bs=4k -runtime=20 -time_based=1 -name=job0
+ else
+ echo "File doesn't exist."
+ exit 1
+ fi
+
+ rm -rf /mnt/device/test
+ umount /mnt/device
+ else
+ touch /mnt/device/aaa
+ umount /mnt/device
+
+ iscsiadm -m node --logout
+ sleep 1
+ iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+ sleep 1
+ dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}')
+ mount -o rw /dev/${dev}1 /mnt/device
+
+ if [ -f "/mnt/device/aaa" ]; then
+ echo "File existed."
+ else
+ echo "File doesn't exist."
+ exit 1
+ fi
+
+ rm -rf /mnt/device/aaa
+ umount /mnt/device
+ fi
+done
+
+rm -rf /mnt/device
+
+trap - SIGINT SIGTERM EXIT
+
+iscsicleanup
+remove_backends
+killprocess $pid
+timing_exit filesystem
diff --git a/src/spdk/test/iscsi_tgt/fio/fio.sh b/src/spdk/test/iscsi_tgt/fio/fio.sh
new file mode 100755
index 00000000..5fdeaed2
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/fio/fio.sh
@@ -0,0 +1,142 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+delete_tmp_files() {
+ rm -f $testdir/iscsi.conf
+ rm -f ./local-job0-0-verify.state
+}
+
+function running_config() {
+ # generate a config file from the running iscsi_tgt
+ # running_config.sh will leave the file at /tmp/iscsi.conf
+ $testdir/running_config.sh $pid
+ sleep 1
+
+ # now start iscsi_tgt again using the generated config file
+ # keep the same iscsiadm configuration to confirm that the
+ # config file matched the running configuration
+ killprocess $pid
+ trap "iscsicleanup; delete_tmp_files; exit 1" SIGINT SIGTERM EXIT
+
+ timing_enter start_iscsi_tgt2
+
+ $ISCSI_APP -c /tmp/iscsi.conf &
+ pid=$!
+ echo "Process pid: $pid"
+ trap "iscsicleanup; killprocess $pid; delete_tmp_files; exit 1" SIGINT SIGTERM EXIT
+ waitforlisten $pid
+ echo "iscsi_tgt is listening. Running tests..."
+
+ timing_exit start_iscsi_tgt2
+
+ sleep 1
+ $fio_py 4096 1 randrw 5
+}
+
+if [ -z "$TARGET_IP" ]; then
+ echo "TARGET_IP not defined in environment"
+ exit 1
+fi
+
+if [ -z "$INITIATOR_IP" ]; then
+ echo "INITIATOR_IP not defined in environment"
+ exit 1
+fi
+
+timing_enter fio
+
+cp $testdir/iscsi.conf.in $testdir/iscsi.conf
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=4096
+
+rpc_py="$rootdir/scripts/rpc.py"
+fio_py="$rootdir/scripts/fio.py"
+
+timing_enter start_iscsi_tgt
+
+$ISCSI_APP -c $testdir/iscsi.conf &
+pid=$!
+echo "Process pid: $pid"
+
+trap "killprocess $pid; rm -f $testdir/iscsi.conf; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+# Create a RAID-0 bdev from two malloc bdevs
+malloc_bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) "
+malloc_bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+$rpc_py construct_raid_bdev -n raid0 -s 64 -r 0 -b "$malloc_bdevs"
+# "raid0:0" ==> use raid0 blockdev for LUN0
+# "1:2" ==> map PortalGroup1 to InitiatorGroup2
+# "64" ==> iSCSI queue depth 64
+# "-d" ==> disable CHAP authentication
+$rpc_py construct_target_node Target3 Target3_alias 'raid0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d
+sleep 1
+
+iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+
+trap "iscsicleanup; killprocess $pid; delete_tmp_files; exit 1" SIGINT SIGTERM EXIT
+
+sleep 1
+$fio_py 4096 1 randrw 1 verify
+$fio_py 131072 32 randrw 1 verify
+$fio_py 524288 128 randrw 1 verify
+
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ $fio_py 4096 1 write 300 verify
+
+ # Run the running_config test which will generate a config file from the
+ # running iSCSI target, then kill and restart the iSCSI target using the
+ # generated config file
+ # Temporarily disabled
+ # running_config
+fi
+
+# Start hotplug test case.
+$fio_py 1048576 128 rw 10 &
+fio_pid=$!
+
+sleep 3
+set +e
+# Delete raid0, Malloc0, Malloc1 blockdevs
+$rpc_py destroy_raid_bdev 'raid0'
+$rpc_py delete_malloc_bdev 'Malloc0'
+$rpc_py delete_malloc_bdev 'Malloc1'
+
+wait $fio_pid
+fio_status=$?
+
+if [ $fio_status -eq 0 ]; then
+ echo "iscsi hotplug test: fio successful - expected failure"
+ set -e
+ exit 1
+else
+ echo "iscsi hotplug test: fio failed as expected"
+fi
+
+set -e
+
+iscsicleanup
+$rpc_py delete_target_node 'iqn.2016-06.io.spdk:Target3'
+
+delete_tmp_files
+
+trap - SIGINT SIGTERM EXIT
+
+killprocess $pid
+#echo 1 > /sys/bus/pci/rescan
+#sleep 2
+$rootdir/scripts/setup.sh
+
+timing_exit fio
diff --git a/src/spdk/test/iscsi_tgt/fio/iscsi.conf.in b/src/spdk/test/iscsi_tgt/fio/iscsi.conf.in
new file mode 100644
index 00000000..be06af58
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/fio/iscsi.conf.in
@@ -0,0 +1,16 @@
+[Global]
+
+[iSCSI]
+ NodeBase "iqn.2016-06.io.spdk"
+ AuthFile /usr/local/etc/spdk/auth.conf
+ Timeout 30
+ DiscoveryAuthMethod Auto
+ MaxSessions 16
+ ImmediateData Yes
+ ErrorRecoveryLevel 0
+
+[Nvme]
+ RetryCount 4
+ ActionOnTimeout None
+ AdminPollRate 100000
+ HotplugEnable Yes
diff --git a/src/spdk/test/iscsi_tgt/fio/running_config.sh b/src/spdk/test/iscsi_tgt/fio/running_config.sh
new file mode 100755
index 00000000..ea59eb5a
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/fio/running_config.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+set -xe
+
+pid="$1"
+
+if [[ -z "$pid" ]]; then
+ echo "usage: $0 pid"
+ exit 1
+fi
+
+# delete any existing temporary iscsi.conf files
+rm -f /tmp/iscsi.conf*
+
+kill -USR1 "$pid"
+
+if [ ! -f $(ls /tmp/iscsi.conf.*) ]; then
+ echo "iscsi_tgt did not generate config file"
+ exit 1
+fi
+
+mv $(ls /tmp/iscsi.conf.*) /tmp/iscsi.conf
diff --git a/src/spdk/test/iscsi_tgt/initiator/initiator.sh b/src/spdk/test/iscsi_tgt/initiator/initiator.sh
new file mode 100755
index 00000000..8f3104a4
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/initiator/initiator.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+timing_enter initiator
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+timing_enter start_iscsi_tgt
+
+# Start the iSCSI target without using stub
+# Reason: Two SPDK processes will be started
+$ISCSI_APP -m 0x2 -p 1 -s 512 --wait-for-rpc &
+pid=$!
+echo "iSCSI target launched. pid: $pid"
+trap "killprocess $pid;exit 1" SIGINT SIGTERM EXIT
+waitforlisten $pid
+$rpc_py set_iscsi_options -o 30 -a 4
+$rpc_py start_subsystem_init
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+$rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE
+# "Malloc0:0" ==> use Malloc0 blockdev for LUN0
+# "1:2" ==> map PortalGroup1 to InitiatorGroup2
+# "64" ==> iSCSI queue depth 64
+# "-d" ==> disable CHAP authentication
+$rpc_py construct_target_node disk1 disk1_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 256 -d
+sleep 1
+trap "killprocess $pid; rm -f $testdir/bdev.conf; exit 1" SIGINT SIGTERM EXIT
+
+# Prepare config file for iSCSI initiator
+echo "[iSCSI_Initiator]" > $testdir/bdev.conf
+echo " URL iscsi://$TARGET_IP/iqn.2016-06.io.spdk:disk1/0 iSCSI0" >> $testdir/bdev.conf
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w verify -t 5 -s 512
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ $rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w unmap -t 5 -s 512
+ $rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w flush -t 5 -s 512
+ $rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w reset -t 10 -s 512
+fi
+rm -f $testdir/bdev.conf
+
+trap - SIGINT SIGTERM EXIT
+
+killprocess $pid
+
+report_test_completion "iscsi_initiator"
+timing_exit initiator
diff --git a/src/spdk/test/iscsi_tgt/ip_migration/ip_migration.sh b/src/spdk/test/iscsi_tgt/ip_migration/ip_migration.sh
new file mode 100755
index 00000000..25332ff8
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/ip_migration/ip_migration.sh
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+rpc_py="$rootdir/scripts/rpc.py"
+fio_py="$rootdir/scripts/fio.py"
+
+# Namespaces are NOT used here on purpose. This test requires changes to detect
+# ifc_index for interface that was put into namespace. Needed for add_ip_address.
+ISCSI_APP="$rootdir/app/iscsi_tgt/iscsi_tgt"
+NETMASK=127.0.0.0/24
+MIGRATION_ADDRESS=127.0.0.2
+
+function kill_all_iscsi_target() {
+ for ((i = 0; i < 2; i++)); do
+ rpc_addr="/var/tmp/spdk${i}.sock"
+ $rpc_py -s $rpc_addr kill_instance SIGTERM
+ done
+}
+
+function rpc_config() {
+ # $1 = RPC server address
+ # $2 = Netmask
+ $rpc_py -s $1 add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $2
+ $rpc_py -s $1 construct_malloc_bdev 64 512
+}
+
+function rpc_add_target_node() {
+ $rpc_py -s $1 add_ip_address 1 $MIGRATION_ADDRESS
+ $rpc_py -s $1 add_portal_group $PORTAL_TAG $MIGRATION_ADDRESS:$ISCSI_PORT
+ $rpc_py -s $1 construct_target_node target1 target1_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d
+}
+
+timing_enter ip_migration
+
+echo "Running ip migration tests"
+for ((i = 0; i < 2; i++)); do
+ timing_enter start_iscsi_tgt_$i
+
+ rpc_addr="/var/tmp/spdk${i}.sock"
+
+ # TODO: run the different iSCSI instances on non-overlapping CPU masks
+ $ISCSI_APP -r $rpc_addr -s 1000 -i $i -m $ISCSI_TEST_CORE_MASK --wait-for-rpc &
+ pid=$!
+ echo "Process pid: $pid"
+
+ trap "kill_all_iscsi_target; exit 1" SIGINT SIGTERM EXIT
+
+ waitforlisten $pid $rpc_addr
+ $rpc_py -s $rpc_addr set_iscsi_options -o 30 -a 64
+ $rpc_py -s $rpc_addr start_subsystem_init
+ echo "iscsi_tgt is listening. Running tests..."
+
+ timing_exit start_iscsi_tgt_$i
+
+ rpc_config $rpc_addr $NETMASK
+ trap "kill_all_iscsi_target; exit 1" \
+ SIGINT SIGTERM EXIT
+done
+
+rpc_first_addr="/var/tmp/spdk0.sock"
+rpc_add_target_node $rpc_first_addr
+
+sleep 1
+iscsiadm -m discovery -t sendtargets -p $MIGRATION_ADDRESS:$ISCSI_PORT
+sleep 1
+iscsiadm -m node --login -p $MIGRATION_ADDRESS:$ISCSI_PORT
+
+# fio tests for multi-process
+sleep 1
+$fio_py 4096 32 randrw 10 &
+fiopid=$!
+sleep 5
+
+$rpc_py -s $rpc_first_addr kill_instance SIGTERM
+
+rpc_second_addr="/var/tmp/spdk1.sock"
+rpc_add_target_node $rpc_second_addr
+
+wait $fiopid
+
+trap - SIGINT SIGTERM EXIT
+
+iscsicleanup
+
+$rpc_py -s $rpc_second_addr kill_instance SIGTERM
+report_test_completion "nightly_iscsi_ip_migration"
+timing_exit ip_migration
diff --git a/src/spdk/test/iscsi_tgt/iscsi_tgt.sh b/src/spdk/test/iscsi_tgt/iscsi_tgt.sh
new file mode 100755
index 00000000..fbf9f239
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/iscsi_tgt.sh
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/test/common/autotest_common.sh
+
+if [ ! $(uname -s) = Linux ]; then
+ exit 0
+fi
+
+source $rootdir/test/iscsi_tgt/common.sh
+
+timing_enter iscsi_tgt
+
+# $1 = test type (posix/vpp)
+if [ "$1" == "posix" ] || [ "$1" == "vpp" ]; then
+ TEST_TYPE=$1
+else
+ echo "No iSCSI test type specified"
+ exit 1
+fi
+
+# Network configuration
+create_veth_interfaces $TEST_TYPE
+
+# ISCSI_TEST_CORE_MASK is the biggest core mask specified by
+# any of the iscsi_tgt tests. Using this mask for the stub
+# ensures that if this mask spans CPU sockets, that we will
+# allocate memory from both sockets. The stub will *not*
+# run anything on the extra cores (and will sleep on master
+# core 0) so there is no impact to the iscsi_tgt tests by
+# specifying the bigger core mask.
+start_stub "-s 2048 -i 0 -m $ISCSI_TEST_CORE_MASK"
+trap "kill_stub; cleanup_veth_interfaces $TEST_TYPE; exit 1" SIGINT SIGTERM EXIT
+
+run_test suite ./test/iscsi_tgt/calsoft/calsoft.sh
+run_test suite ./test/iscsi_tgt/filesystem/filesystem.sh
+run_test suite ./test/iscsi_tgt/reset/reset.sh
+run_test suite ./test/iscsi_tgt/rpc_config/rpc_config.sh $TEST_TYPE
+run_test suite ./test/iscsi_tgt/lvol/iscsi_lvol.sh
+run_test suite ./test/iscsi_tgt/fio/fio.sh
+run_test suite ./test/iscsi_tgt/qos/qos.sh
+
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ if [ $SPDK_TEST_PMDK -eq 1 ]; then
+ run_test suite ./test/iscsi_tgt/pmem/iscsi_pmem.sh 4096 10
+ fi
+ run_test suite ./test/iscsi_tgt/ip_migration/ip_migration.sh
+ run_test suite ./test/iscsi_tgt/ext4test/ext4test.sh
+ run_test suite ./test/iscsi_tgt/digests/digests.sh
+fi
+if [ $SPDK_TEST_RBD -eq 1 ]; then
+ run_test suite ./test/iscsi_tgt/rbd/rbd.sh
+fi
+
+trap "cleanup_veth_interfaces $TEST_TYPE; exit 1" SIGINT SIGTERM EXIT
+kill_stub
+
+if [ $SPDK_TEST_NVMF -eq 1 ]; then
+ # TODO: enable remote NVMe controllers with multi-process so that
+ # we can use the stub for this test
+ # Test configure remote NVMe device from rpc and conf file
+ run_test suite ./test/iscsi_tgt/nvme_remote/fio_remote_nvme.sh
+fi
+
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ run_test suite ./test/iscsi_tgt/multiconnection/multiconnection.sh
+fi
+
+if [ $SPDK_TEST_ISCSI_INITIATOR -eq 1 ]; then
+ run_test suite ./test/iscsi_tgt/initiator/initiator.sh
+ run_test suite ./test/iscsi_tgt/bdev_io_wait/bdev_io_wait.sh
+fi
+
+cleanup_veth_interfaces $TEST_TYPE
+trap - SIGINT SIGTERM EXIT
+timing_exit iscsi_tgt
diff --git a/src/spdk/test/iscsi_tgt/iscsijson/json_config.sh b/src/spdk/test/iscsi_tgt/iscsijson/json_config.sh
new file mode 100755
index 00000000..cec662b7
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/iscsijson/json_config.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+set -xe
+ISCSI_JSON_DIR=$(readlink -f $(dirname $0))
+. $ISCSI_JSON_DIR/../../json_config/common.sh
+. $JSON_DIR/../iscsi_tgt/common.sh
+base_iscsi_config=$JSON_DIR/base_iscsi_config.json
+last_iscsi_config=$JSON_DIR/last_iscsi_config.json
+rpc_py="$spdk_rpc_py"
+clear_config_py="$spdk_clear_config_py"
+trap 'on_error_exit "${FUNCNAME}" "${LINENO}"; rm -f $base_iscsi_config $last_iscsi_config' ERR
+
+timing_enter iscsi_json_config
+run_spdk_tgt
+$rpc_py start_subsystem_init
+
+timing_enter iscsi_json_config_create_setup
+$rpc_py add_portal_group $PORTAL_TAG 127.0.0.1:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+$rpc_py construct_malloc_bdev 64 4096 --name Malloc0
+$rpc_py construct_target_node Target3 Target3_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d
+$rpc_py save_config > $base_iscsi_config
+timing_exit iscsi_json_config_create_setup
+
+timing_enter iscsi_json_config_test
+test_json_config
+timing_exit iscsi_json_config_test
+
+timing_enter iscsi_json_config_restart_spdk
+$clear_config_py clear_config
+kill_targets
+run_spdk_tgt
+$rpc_py load_config < $base_iscsi_config
+$rpc_py save_config > $last_iscsi_config
+timing_exit iscsi_json_config_restart_spdk
+
+json_diff $base_iscsi_config $last_iscsi_config
+
+$clear_config_py clear_config
+kill_targets
+rm -f $base_iscsi_config $last_iscsi_config
+
+timing_exit iscsi_json_config
+report_test_completion iscsi_json_config
diff --git a/src/spdk/test/iscsi_tgt/lvol/iscsi_lvol.sh b/src/spdk/test/iscsi_tgt/lvol/iscsi_lvol.sh
new file mode 100755
index 00000000..c3df3af7
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/lvol/iscsi_lvol.sh
@@ -0,0 +1,82 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+timing_enter iscsi_lvol
+
+MALLOC_BDEV_SIZE=128
+MALLOC_BLOCK_SIZE=512
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ NUM_LVS=10
+ NUM_LVOL=10
+else
+ NUM_LVS=2
+ NUM_LVOL=2
+fi
+
+rpc_py="$rootdir/scripts/rpc.py"
+fio_py="$rootdir/scripts/fio.py"
+
+timing_enter start_iscsi_tgt
+
+$ISCSI_APP -m $ISCSI_TEST_CORE_MASK --wait-for-rpc &
+pid=$!
+echo "Process pid: $pid"
+
+trap "iscsicleanup; killprocess $pid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py set_iscsi_options -o 30 -a 16
+$rpc_py start_subsystem_init
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+timing_enter setup
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+# Create the first LVS from a Raid-0 bdev, which is created from two malloc bdevs
+# Create remaining LVSs from a malloc bdev, respectively
+for i in $(seq 1 $NUM_LVS); do
+ INITIATOR_TAG=$((i + 2))
+ $rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+ if [ $i -eq 1 ]; then
+ # construct RAID bdev and put its name in $bdev
+ malloc_bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) "
+ malloc_bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+ $rpc_py construct_raid_bdev -n raid0 -s 64 -r 0 -b "$malloc_bdevs"
+ bdev="raid0"
+ else
+ # construct malloc bdev and put its name in $bdev
+ bdev=$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)
+ fi
+ ls_guid=$($rpc_py construct_lvol_store $bdev lvs_$i -c 1048576)
+ LUNs=""
+ for j in $(seq 1 $NUM_LVOL); do
+ lb_name=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_$j 10)
+ LUNs+="$lb_name:$((j - 1)) "
+ done
+ $rpc_py construct_target_node Target$i Target${i}_alias "$LUNs" "1:$INITIATOR_TAG" 256 -d
+done
+timing_exit setup
+
+sleep 1
+
+timing_enter discovery
+iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+timing_exit discovery
+
+timing_enter fio
+$fio_py 131072 8 randwrite 10 verify
+timing_exit fio
+
+rm -f ./local-job0-0-verify.state
+trap - SIGINT SIGTERM EXIT
+
+rm -f ./local-job*
+iscsicleanup
+killprocess $pid
+timing_exit iscsi_lvol
diff --git a/src/spdk/test/iscsi_tgt/multiconnection/multiconnection.sh b/src/spdk/test/iscsi_tgt/multiconnection/multiconnection.sh
new file mode 100755
index 00000000..b793d751
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/multiconnection/multiconnection.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+rpc_py="$rootdir/scripts/rpc.py"
+fio_py="$rootdir/scripts/fio.py"
+
+CONNECTION_NUMBER=30
+
+# Remove lvol bdevs and stores.
+function remove_backends() {
+ echo "INFO: Removing lvol bdevs"
+ for i in $(seq 1 $CONNECTION_NUMBER); do
+ lun="lvs0/lbd_$i"
+ $rpc_py destroy_lvol_bdev $lun
+ echo -e "\tINFO: lvol bdev $lun removed"
+ done
+ sleep 1
+
+ echo "INFO: Removing lvol stores"
+ $rpc_py destroy_lvol_store -l lvs0
+ echo "INFO: lvol store lvs0 removed"
+
+ echo "INFO: Removing NVMe"
+ $rpc_py delete_nvme_controller Nvme0
+
+ return 0
+}
+
+set -e
+timing_enter multiconnection
+
+timing_enter start_iscsi_tgt
+# Start the iSCSI target without using stub.
+$ISCSI_APP --wait-for-rpc &
+iscsipid=$!
+echo "iSCSI target launched. pid: $iscsipid"
+trap "remove_backends; iscsicleanup; killprocess $iscsipid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $iscsipid
+$rpc_py set_iscsi_options -o 30 -a 128
+$rpc_py start_subsystem_init
+$rootdir/scripts/gen_nvme.sh --json | $rpc_py load_subsystem_config
+timing_exit start_iscsi_tgt
+
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+
+echo "Creating an iSCSI target node."
+ls_guid=$($rpc_py construct_lvol_store "Nvme0n1" "lvs0" -c 1048576)
+
+# Assign even size for each lvol_bdev.
+get_lvs_free_mb $ls_guid
+lvol_bdev_size=$(($free_mb / $CONNECTION_NUMBER))
+for i in $(seq 1 $CONNECTION_NUMBER); do
+ $rpc_py construct_lvol_bdev -u $ls_guid lbd_$i $lvol_bdev_size
+done
+
+for i in $(seq 1 $CONNECTION_NUMBER); do
+ lun="lvs0/lbd_$i:0"
+ $rpc_py construct_target_node Target$i Target${i}_alias "$lun" $PORTAL_TAG:$INITIATOR_TAG 256 -d
+done
+sleep 1
+
+echo "Logging into iSCSI target."
+iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+sleep 1
+
+echo "Running FIO"
+$fio_py 131072 64 randrw 5
+$fio_py 262144 16 randwrite 10
+sync
+
+trap - SIGINT SIGTERM EXIT
+
+rm -f ./local-job*
+iscsicleanup
+remove_backends
+killprocess $iscsipid
+timing_exit multiconnection
diff --git a/src/spdk/test/iscsi_tgt/nvme_remote/fio_remote_nvme.sh b/src/spdk/test/iscsi_tgt/nvme_remote/fio_remote_nvme.sh
new file mode 100755
index 00000000..2f00b7a5
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/nvme_remote/fio_remote_nvme.sh
@@ -0,0 +1,112 @@
+#!/usr/bin/env bash
+
+set -e
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+rpc_py="$rootdir/scripts/rpc.py"
+fio_py="$rootdir/scripts/fio.py"
+
+NVMF_PORT=4420
+
+# Namespaces are NOT used here on purpose. Rxe_cfg utilility used for NVMf tests do not support namespaces.
+TARGET_IP=127.0.0.1
+INITIATOR_IP=127.0.0.1
+NETMASK=$INITIATOR_IP/32
+
+function run_nvme_remote() {
+ echo "now use $1 method to run iscsi tgt."
+
+ # Start the iSCSI target without using stub
+ iscsi_rpc_addr="/var/tmp/spdk-iscsi.sock"
+ ISCSI_APP="$rootdir/app/iscsi_tgt/iscsi_tgt"
+ $ISCSI_APP -r "$iscsi_rpc_addr" -m 0x1 -p 0 -s 512 --wait-for-rpc &
+ iscsipid=$!
+ echo "iSCSI target launched. pid: $iscsipid"
+ trap "killprocess $iscsipid; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT
+ waitforlisten $iscsipid "$iscsi_rpc_addr"
+ $rpc_py -s "$iscsi_rpc_addr" set_iscsi_options -o 30 -a 16
+ $rpc_py -s "$iscsi_rpc_addr" start_subsystem_init
+ if [ "$1" = "remote" ]; then
+ $rpc_py -s $iscsi_rpc_addr construct_nvme_bdev -b "Nvme0" -t "rdma" -f "ipv4" -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT -n nqn.2016-06.io.spdk:cnode1
+ fi
+
+ echo "iSCSI target has started."
+
+ timing_exit start_iscsi_tgt
+
+ echo "Creating an iSCSI target node."
+ $rpc_py -s "$iscsi_rpc_addr" add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+ $rpc_py -s "$iscsi_rpc_addr" add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+ if [ "$1" = "local" ]; then
+ $rpc_py -s "$iscsi_rpc_addr" construct_nvme_bdev -b "Nvme0" -t "rdma" -f "ipv4" -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT -n nqn.2016-06.io.spdk:cnode1
+ fi
+ $rpc_py -s "$iscsi_rpc_addr" construct_target_node Target1 Target1_alias 'Nvme0n1:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d
+ sleep 1
+
+ echo "Logging in to iSCSI target."
+ iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+ iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+}
+
+timing_enter nvme_remote
+
+# Start the NVMf target
+NVMF_APP="$rootdir/app/nvmf_tgt/nvmf_tgt"
+$NVMF_APP -m 0x2 -p 1 -s 512 --wait-for-rpc &
+nvmfpid=$!
+echo "NVMf target launched. pid: $nvmfpid"
+trap "killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT
+waitforlisten $nvmfpid
+$rpc_py start_subsystem_init
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+echo "NVMf target has started."
+bdevs=$($rpc_py construct_malloc_bdev 64 512)
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev
+done
+echo "NVMf subsystem created."
+
+timing_enter start_iscsi_tgt
+
+run_nvme_remote "local"
+
+trap "iscsicleanup; killprocess $iscsipid; killprocess $nvmfpid; \
+ rm -f ./local-job0-0-verify.state; exit 1" SIGINT SIGTERM EXIT
+sleep 1
+
+echo "Running FIO"
+$fio_py 4096 1 randrw 1 verify
+
+rm -f ./local-job0-0-verify.state
+iscsicleanup
+killprocess $iscsipid
+
+run_nvme_remote "remote"
+
+echo "Running FIO"
+$fio_py 4096 1 randrw 1 verify
+
+rm -f ./local-job0-0-verify.state
+trap - SIGINT SIGTERM EXIT
+
+iscsicleanup
+killprocess $iscsipid
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+killprocess $nvmfpid
+
+report_test_completion "iscsi_nvme_remote"
+timing_exit nvme_remote
diff --git a/src/spdk/test/iscsi_tgt/pmem/iscsi_pmem.sh b/src/spdk/test/iscsi_tgt/pmem/iscsi_pmem.sh
new file mode 100755
index 00000000..063bb695
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/pmem/iscsi_pmem.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+BLOCKSIZE=$1
+RUNTIME=$2
+PMEM_BDEVS=""
+PMEM_SIZE=128
+PMEM_BLOCK_SIZE=512
+TGT_NR=10
+PMEM_PER_TGT=1
+rpc_py="$rootdir/scripts/rpc.py"
+fio_py="$rootdir/scripts/fio.py"
+
+timing_enter iscsi_pmem
+
+timing_enter start_iscsi_target
+$ISCSI_APP -m $ISCSI_TEST_CORE_MASK --wait-for-rpc &
+pid=$!
+echo "Process pid: $pid"
+
+trap "iscsicleanup; killprocess $pid; rm -f /tmp/pool_file*; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py set_iscsi_options -o 30 -a 16
+$rpc_py start_subsystem_init
+echo "iscsi_tgt is listening. Running tests..."
+timing_exit start_iscsi_target
+
+timing_enter setup
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+for i in $(seq 1 $TGT_NR); do
+ INITIATOR_TAG=$((i + 1))
+ $rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+
+ luns=""
+ for j in $(seq 1 $PMEM_PER_TGT); do
+ $rpc_py create_pmem_pool /tmp/pool_file${i}_${j} $PMEM_SIZE $PMEM_BLOCK_SIZE
+ bdevs_name="$($rpc_py construct_pmem_bdev -n pmem${i}_${j} /tmp/pool_file${i}_${j})"
+ PMEM_BDEVS+="$bdevs_name "
+ luns+="$bdevs_name:$((j - 1)) "
+ done
+ $rpc_py construct_target_node Target$i Target${i}_alias "$luns" "1:$INITIATOR_TAG " 256 -d
+done
+timing_exit setup
+sleep 1
+
+timing_enter discovery
+iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+timing_exit discovery
+
+timing_enter fio_test
+$fio_py $BLOCKSIZE 64 randwrite $RUNTIME verify
+timing_exit fio_test
+
+iscsicleanup
+
+for pmem in $PMEM_BDEVS; do
+ $rpc_py delete_pmem_bdev $pmem
+done
+
+for i in $(seq 1 $TGT_NR); do
+ for c in $(seq 1 $PMEM_PER_TGT); do
+ $rpc_py delete_pmem_pool /tmp/pool_file${i}_${c}
+ done
+done
+
+trap - SIGINT SIGTERM EXIT
+
+rm -f ./local-job*
+rm -f /tmp/pool_file*
+killprocess $pid
+report_test_completion "nightly_iscsi_pmem"
+timing_exit iscsi_pmem
diff --git a/src/spdk/test/iscsi_tgt/qos/qos.sh b/src/spdk/test/iscsi_tgt/qos/qos.sh
new file mode 100755
index 00000000..da12f8f8
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/qos/qos.sh
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+function check_qos_works_well() {
+ local enable_limit=$1
+ local iops_limit=$2
+ local retval=0
+
+ start_io_count=$($rpc_py get_bdevs_iostat -b $3 | jq -r '.[1].num_read_ops')
+ $fio_py 512 64 randread 5
+ end_io_count=$($rpc_py get_bdevs_iostat -b $3 | jq -r '.[1].num_read_ops')
+
+ read_iops=$(((end_io_count-start_io_count)/5))
+
+ if [ $enable_limit = true ]; then
+ retval=$(echo "$iops_limit*0.9 < $read_iops && $read_iops < $iops_limit*1.01" | bc)
+ if [ $retval -eq 0 ]; then
+ echo "Failed to limit the io read rate of malloc bdev by qos"
+ exit 1
+ fi
+ else
+ retval=$(echo "$read_iops > $iops_limit" | bc)
+ if [ $retval -eq 0 ]; then
+ echo "$read_iops less than $iops_limit - expected greater than"
+ exit 1
+ fi
+ fi
+}
+
+if [ -z "$TARGET_IP" ]; then
+ echo "TARGET_IP not defined in environment"
+ exit 1
+fi
+
+if [ -z "$INITIATOR_IP" ]; then
+ echo "INITIATOR_IP not defined in environment"
+ exit 1
+fi
+
+timing_enter qos
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+IOPS_LIMIT=20000
+rpc_py="$rootdir/scripts/rpc.py"
+fio_py="$rootdir/scripts/fio.py"
+
+timing_enter start_iscsi_tgt
+
+$ISCSI_APP &
+pid=$!
+echo "Process pid: $pid"
+trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT
+waitforlisten $pid
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+$rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE
+# "Malloc0:0" ==> use Malloc0 blockdev for LUN0
+# "1:2" ==> map PortalGroup1 to InitiatorGroup2
+# "64" ==> iSCSI queue depth 64
+# "-d" ==> disable CHAP authentication
+$rpc_py construct_target_node Target1 Target1_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d
+sleep 1
+
+iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+
+trap "iscsicleanup; killprocess $pid; exit 1" SIGINT SIGTERM EXIT
+
+# Limit the I/O rate by RPC, then confirm the observed rate matches.
+$rpc_py set_bdev_qos_limit Malloc0 --rw_ios_per_sec $IOPS_LIMIT
+check_qos_works_well true $IOPS_LIMIT Malloc0
+
+# Now disable the rate limiting, and confirm the observed rate is not limited anymore.
+$rpc_py set_bdev_qos_limit Malloc0 --rw_ios_per_sec 0
+check_qos_works_well false $IOPS_LIMIT Malloc0
+
+# Limit the I/O rate again.
+$rpc_py set_bdev_qos_limit Malloc0 --rw_ios_per_sec $IOPS_LIMIT
+check_qos_works_well true $IOPS_LIMIT Malloc0
+echo "I/O rate limiting tests successful"
+
+iscsicleanup
+$rpc_py delete_target_node 'iqn.2016-06.io.spdk:Target1'
+
+rm -f ./local-job0-0-verify.state
+trap - SIGINT SIGTERM EXIT
+killprocess $pid
+
+timing_exit qos
diff --git a/src/spdk/test/iscsi_tgt/rbd/rbd.sh b/src/spdk/test/iscsi_tgt/rbd/rbd.sh
new file mode 100755
index 00000000..27d86159
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/rbd/rbd.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+if ! hash ceph; then
+ echo "Ceph not detected on this system; skipping RBD tests"
+ exit 0
+fi
+
+timing_enter rbd_setup
+rbd_setup $TARGET_IP $TARGET_NAMESPACE
+trap "rbd_cleanup; exit 1" SIGINT SIGTERM EXIT
+timing_exit rbd_setup
+
+timing_enter rbd
+
+rpc_py="$rootdir/scripts/rpc.py"
+fio_py="$rootdir/scripts/fio.py"
+
+timing_enter start_iscsi_tgt
+
+$ISCSI_APP -m $ISCSI_TEST_CORE_MASK --wait-for-rpc &
+pid=$!
+
+trap "killprocess $pid; rbd_cleanup; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py set_iscsi_options -o 30 -a 16
+$rpc_py start_subsystem_init
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+rbd_bdev="$($rpc_py construct_rbd_bdev $RBD_POOL $RBD_NAME 4096)"
+$rpc_py get_bdevs
+# "Ceph0:0" ==> use Ceph0 blockdev for LUN0
+# "1:2" ==> map PortalGroup1 to InitiatorGroup2
+# "64" ==> iSCSI queue depth 64
+# "-d" ==> disable CHAP authentication
+$rpc_py construct_target_node Target3 Target3_alias 'Ceph0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d
+sleep 1
+
+iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+
+trap "iscsicleanup; killprocess $pid; rbd_cleanup; exit 1" SIGINT SIGTERM EXIT
+
+sleep 1
+$fio_py 4096 1 randrw 1 verify
+$fio_py 131072 32 randrw 1 verify
+
+rm -f ./local-job0-0-verify.state
+
+trap - SIGINT SIGTERM EXIT
+
+iscsicleanup
+$rpc_py delete_rbd_bdev $rbd_bdev
+killprocess $pid
+rbd_cleanup
+
+report_test_completion "iscsi_rbd"
+timing_exit rbd
diff --git a/src/spdk/test/iscsi_tgt/reset/reset.sh b/src/spdk/test/iscsi_tgt/reset/reset.sh
new file mode 100755
index 00000000..0e986ac5
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/reset/reset.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+
+set -xe
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+timing_enter reset
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+fio_py="$rootdir/scripts/fio.py"
+
+if ! hash sg_reset; then
+ exit 1
+fi
+
+timing_enter start_iscsi_tgt
+
+$ISCSI_APP --wait-for-rpc &
+pid=$!
+echo "Process pid: $pid"
+
+trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py set_iscsi_options -o 30 -a 16
+$rpc_py start_subsystem_init
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+$rpc_py add_portal_group $PORTAL_TAG $TARGET_IP:$ISCSI_PORT
+$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
+$rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE
+# "Malloc0:0" ==> use Malloc0 blockdev for LUN0
+# "1:2" ==> map PortalGroup1 to InitiatorGroup2
+# "64" ==> iSCSI queue depth 64
+# "-d" ==> disable CHAP authentication
+$rpc_py construct_target_node Target3 Target3_alias 'Malloc0:0' $PORTAL_TAG:$INITIATOR_TAG 64 -d
+sleep 1
+
+iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$ISCSI_PORT
+iscsiadm -m node --login -p $TARGET_IP:$ISCSI_PORT
+sleep 1
+dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}')
+
+sleep 1
+$fio_py 512 1 read 60 &
+fiopid=$!
+echo "FIO pid: $fiopid"
+
+trap "iscsicleanup; killprocess $pid; killprocess $fiopid; exit 1" SIGINT SIGTERM EXIT
+
+# Do 3 resets while making sure iscsi_tgt and fio are still running
+for i in 1 2 3; do
+ sleep 1
+ kill -s 0 $pid
+ kill -s 0 $fiopid
+ sg_reset -d /dev/$dev
+ sleep 1
+ kill -s 0 $pid
+ kill -s 0 $fiopid
+done
+
+kill $fiopid
+wait $fiopid || true
+
+trap - SIGINT SIGTERM EXIT
+
+iscsicleanup
+killprocess $pid
+timing_exit reset
diff --git a/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.py b/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.py
new file mode 100755
index 00000000..03647c47
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.py
@@ -0,0 +1,502 @@
+#!/usr/bin/env python3
+
+
+import os
+import os.path
+import re
+import sys
+import time
+import json
+import random
+from subprocess import check_call, call, check_output, Popen, PIPE, CalledProcessError
+
+if (len(sys.argv) == 8):
+ target_ip = sys.argv[2]
+ initiator_ip = sys.argv[3]
+ port = sys.argv[4]
+ netmask = sys.argv[5]
+ namespace = sys.argv[6]
+ test_type = sys.argv[7]
+
+ns_cmd = 'ip netns exec ' + namespace
+other_ip = '127.0.0.6'
+initiator_name = 'ANY'
+portal_tag = '1'
+initiator_tag = '1'
+
+rpc_param = {
+ 'target_ip': target_ip,
+ 'initiator_ip': initiator_ip,
+ 'port': port,
+ 'initiator_name': initiator_name,
+ 'netmask': netmask,
+ 'lun_total': 3,
+ 'malloc_bdev_size': 64,
+ 'malloc_block_size': 512,
+ 'queue_depth': 64,
+ 'target_name': 'Target3',
+ 'alias_name': 'Target3_alias',
+ 'disable_chap': True,
+ 'mutual_chap': False,
+ 'require_chap': False,
+ 'chap_group': 0,
+ 'header_digest': False,
+ 'data_digest': False,
+ 'trace_flag': 'rpc',
+ 'cpumask': 0x1
+}
+
+
+class RpcException(Exception):
+
+ def __init__(self, retval, *args):
+ super(RpcException, self).__init__(*args)
+ self.retval = retval
+
+
+class spdk_rpc(object):
+
+ def __init__(self, rpc_py):
+ self.rpc_py = rpc_py
+
+ def __getattr__(self, name):
+ def call(*args):
+ cmd = "{} {}".format(self.rpc_py, name)
+ for arg in args:
+ cmd += " {}".format(arg)
+ return check_output(cmd, shell=True)
+ return call
+
+
+def verify(expr, retcode, msg):
+ if not expr:
+ raise RpcException(retcode, msg)
+
+
+def verify_trace_flag_rpc_methods(rpc_py, rpc_param):
+ rpc = spdk_rpc(rpc_py)
+ output = rpc.get_trace_flags()
+ jsonvalue = json.loads(output)
+ verify(not jsonvalue[rpc_param['trace_flag']], 1,
+ "get_trace_flags returned {}, expected false".format(jsonvalue))
+ rpc.set_trace_flag(rpc_param['trace_flag'])
+ output = rpc.get_trace_flags()
+ jsonvalue = json.loads(output)
+ verify(jsonvalue[rpc_param['trace_flag']], 1,
+ "get_trace_flags returned {}, expected true".format(jsonvalue))
+ rpc.clear_trace_flag(rpc_param['trace_flag'])
+ output = rpc.get_trace_flags()
+ jsonvalue = json.loads(output)
+ verify(not jsonvalue[rpc_param['trace_flag']], 1,
+ "get_trace_flags returned {}, expected false".format(jsonvalue))
+
+ print("verify_trace_flag_rpc_methods passed")
+
+
+def verify_iscsi_connection_rpc_methods(rpc_py):
+ rpc = spdk_rpc(rpc_py)
+ output = rpc.get_iscsi_connections()
+ jsonvalue = json.loads(output)
+ verify(not jsonvalue, 1,
+ "get_iscsi_connections returned {}, expected empty".format(jsonvalue))
+
+ rpc.construct_malloc_bdev(rpc_param['malloc_bdev_size'], rpc_param['malloc_block_size'])
+ rpc.add_portal_group(portal_tag, "{}:{}".format(rpc_param['target_ip'], str(rpc_param['port'])))
+ rpc.add_initiator_group(initiator_tag, rpc_param['initiator_name'], rpc_param['netmask'])
+
+ lun_mapping = "Malloc" + str(rpc_param['lun_total']) + ":0"
+ net_mapping = portal_tag + ":" + initiator_tag
+ rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'], '-d')
+ check_output('iscsiadm -m discovery -t st -p {}'.format(rpc_param['target_ip']), shell=True)
+ check_output('iscsiadm -m node --login', shell=True)
+ name = json.loads(rpc.get_target_nodes())[0]['name']
+ output = rpc.get_iscsi_connections()
+ jsonvalues = json.loads(output)
+ verify(jsonvalues[0]['target_node_name'] == rpc_param['target_name'], 1,
+ "target node name vaule is {}, expected {}".format(jsonvalues[0]['target_node_name'], rpc_param['target_name']))
+ verify(jsonvalues[0]['id'] == 0, 1,
+ "device id value is {}, expected 0".format(jsonvalues[0]['id']))
+ verify(jsonvalues[0]['initiator_addr'] == rpc_param['initiator_ip'], 1,
+ "initiator address values is {}, expected {}".format(jsonvalues[0]['initiator_addr'], rpc_param['initiator_ip']))
+ verify(jsonvalues[0]['target_addr'] == rpc_param['target_ip'], 1,
+ "target address values is {}, expected {}".format(jsonvalues[0]['target_addr'], rpc_param['target_ip']))
+
+ check_output('iscsiadm -m node --logout', shell=True)
+ check_output('iscsiadm -m node -o delete', shell=True)
+ rpc.delete_initiator_group(initiator_tag)
+ rpc.delete_portal_group(portal_tag)
+ rpc.delete_target_node(name)
+ output = rpc.get_iscsi_connections()
+ jsonvalues = json.loads(output)
+ verify(not jsonvalues, 1,
+ "get_iscsi_connections returned {}, expected empty".format(jsonvalues))
+
+ print("verify_iscsi_connection_rpc_methods passed")
+
+
+def verify_scsi_devices_rpc_methods(rpc_py):
+ rpc = spdk_rpc(rpc_py)
+ output = rpc.get_scsi_devices()
+ jsonvalue = json.loads(output)
+ verify(not jsonvalue, 1,
+ "get_scsi_devices returned {}, expected empty".format(jsonvalue))
+
+ rpc.construct_malloc_bdev(rpc_param['malloc_bdev_size'], rpc_param['malloc_block_size'])
+ rpc.add_portal_group(portal_tag, "{}:{}".format(rpc_param['target_ip'], str(rpc_param['port'])))
+ rpc.add_initiator_group(initiator_tag, rpc_param['initiator_name'], rpc_param['netmask'])
+
+ lun_mapping = "Malloc" + str(rpc_param['lun_total']) + ":0"
+ net_mapping = portal_tag + ":" + initiator_tag
+ rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'], '-d')
+ check_output('iscsiadm -m discovery -t st -p {}'.format(rpc_param['target_ip']), shell=True)
+ check_output('iscsiadm -m node --login', shell=True)
+ name = json.loads(rpc.get_target_nodes())[0]['name']
+ output = rpc.get_iscsi_global_params()
+ jsonvalues = json.loads(output)
+ nodebase = jsonvalues['node_base']
+ output = rpc.get_scsi_devices()
+ jsonvalues = json.loads(output)
+ verify(jsonvalues[0]['device_name'] == nodebase + ":" + rpc_param['target_name'], 1,
+ "device name vaule is {}, expected {}".format(jsonvalues[0]['device_name'], rpc_param['target_name']))
+ verify(jsonvalues[0]['id'] == 0, 1,
+ "device id value is {}, expected 0".format(jsonvalues[0]['id']))
+
+ check_output('iscsiadm -m node --logout', shell=True)
+ check_output('iscsiadm -m node -o delete', shell=True)
+ rpc.delete_initiator_group(initiator_tag)
+ rpc.delete_portal_group(portal_tag)
+ rpc.delete_target_node(name)
+ output = rpc.get_scsi_devices()
+ jsonvalues = json.loads(output)
+ verify(not jsonvalues, 1,
+ "get_scsi_devices returned {}, expected empty".format(jsonvalues))
+
+ print("verify_scsi_devices_rpc_methods passed")
+
+
+def create_malloc_bdevs_rpc_methods(rpc_py, rpc_param):
+ rpc = spdk_rpc(rpc_py)
+
+ for i in range(1, rpc_param['lun_total'] + 1):
+ rpc.construct_malloc_bdev(rpc_param['malloc_bdev_size'], rpc_param['malloc_block_size'])
+
+ print("create_malloc_bdevs_rpc_methods passed")
+
+
+def verify_portal_groups_rpc_methods(rpc_py, rpc_param):
+ rpc = spdk_rpc(rpc_py)
+ output = rpc.get_portal_groups()
+ jsonvalues = json.loads(output)
+ verify(not jsonvalues, 1,
+ "get_portal_groups returned {} groups, expected empty".format(jsonvalues))
+
+ lo_ip = (target_ip, other_ip)
+ nics = json.loads(rpc.get_interfaces())
+ for x in nics:
+ if x["ifc_index"] == 'lo':
+ rpc.add_ip_address(x["ifc_index"], lo_ip[1])
+ for idx, value in enumerate(lo_ip):
+ # The portal group tag must start at 1
+ tag = idx + 1
+ rpc.add_portal_group(tag, "{}:{}@{}".format(value, rpc_param['port'], rpc_param['cpumask']))
+ output = rpc.get_portal_groups()
+ jsonvalues = json.loads(output)
+ verify(len(jsonvalues) == tag, 1,
+ "get_portal_groups returned {} groups, expected {}".format(len(jsonvalues), tag))
+
+ tag_list = []
+ for idx, value in enumerate(jsonvalues):
+ verify(value['portals'][0]['host'] == lo_ip[idx], 1,
+ "host value is {}, expected {}".format(value['portals'][0]['host'], rpc_param['target_ip']))
+ verify(value['portals'][0]['port'] == str(rpc_param['port']), 1,
+ "port value is {}, expected {}".format(value['portals'][0]['port'], str(rpc_param['port'])))
+ verify(value['portals'][0]['cpumask'] == format(rpc_param['cpumask'], '#x'), 1,
+ "cpumask value is {}, expected {}".format(value['portals'][0]['cpumask'], format(rpc_param['cpumask'], '#x')))
+ tag_list.append(value['tag'])
+ verify(value['tag'] == idx + 1, 1,
+ "tag value is {}, expected {}".format(value['tag'], idx + 1))
+
+ for idx, value in enumerate(tag_list):
+ rpc.delete_portal_group(value)
+ output = rpc.get_portal_groups()
+ jsonvalues = json.loads(output)
+ verify(len(jsonvalues) == (len(tag_list) - (idx + 1)), 1,
+ "get_portal_group returned {} groups, expected {}".format(len(jsonvalues), (len(tag_list) - (idx + 1))))
+ if not jsonvalues:
+ break
+
+ for jidx, jvalue in enumerate(jsonvalues):
+ verify(jvalue['portals'][0]['host'] == lo_ip[idx + jidx + 1], 1,
+ "host value is {}, expected {}".format(jvalue['portals'][0]['host'], lo_ip[idx + jidx + 1]))
+ verify(jvalue['portals'][0]['port'] == str(rpc_param['port']), 1,
+ "port value is {}, expected {}".format(jvalue['portals'][0]['port'], str(rpc_param['port'])))
+ verify(jvalue['portals'][0]['cpumask'] == format(rpc_param['cpumask'], '#x'), 1,
+ "cpumask value is {}, expected {}".format(jvalue['portals'][0]['cpumask'], format(rpc_param['cpumask'], '#x')))
+ verify(jvalue['tag'] != value or jvalue['tag'] == tag_list[idx + jidx + 1], 1,
+ "tag value is {}, expected {} and not {}".format(jvalue['tag'], tag_list[idx + jidx + 1], value))
+
+ for x in nics:
+ if x["ifc_index"] == 'lo':
+ rpc.delete_ip_address(x["ifc_index"], lo_ip[1])
+
+ print("verify_portal_groups_rpc_methods passed")
+
+
+def verify_initiator_groups_rpc_methods(rpc_py, rpc_param):
+ rpc = spdk_rpc(rpc_py)
+ output = rpc.get_initiator_groups()
+ jsonvalues = json.loads(output)
+ verify(not jsonvalues, 1,
+ "get_initiator_groups returned {}, expected empty".format(jsonvalues))
+ for idx, value in enumerate(rpc_param['netmask']):
+ # The initiator group tag must start at 1
+ tag = idx + 1
+ rpc.add_initiator_group(tag, rpc_param['initiator_name'], value)
+ output = rpc.get_initiator_groups()
+ jsonvalues = json.loads(output)
+ verify(len(jsonvalues) == tag, 1,
+ "get_initiator_groups returned {} groups, expected {}".format(len(jsonvalues), tag))
+
+ tag_list = []
+ for idx, value in enumerate(jsonvalues):
+ verify(value['initiators'][0] == rpc_param['initiator_name'], 1,
+ "initiator value is {}, expected {}".format(value['initiators'][0], rpc_param['initiator_name']))
+ tag_list.append(value['tag'])
+ verify(value['tag'] == idx + 1, 1,
+ "tag value is {}, expected {}".format(value['tag'], idx + 1))
+ verify(value['netmasks'][0] == rpc_param['netmask'][idx], 1,
+ "netmasks value is {}, expected {}".format(value['netmasks'][0], rpc_param['netmask'][idx]))
+
+ for idx, value in enumerate(rpc_param['netmask']):
+ tag = idx + 1
+ rpc.delete_initiators_from_initiator_group(tag, '-n', rpc_param['initiator_name'], '-m', value)
+
+ output = rpc.get_initiator_groups()
+ jsonvalues = json.loads(output)
+ verify(len(jsonvalues) == tag, 1,
+ "get_initiator_groups returned {} groups, expected {}".format(len(jsonvalues), tag))
+
+ for idx, value in enumerate(jsonvalues):
+ verify(value['tag'] == idx + 1, 1,
+ "tag value is {}, expected {}".format(value['tag'], idx + 1))
+ initiators = value.get('initiators')
+ verify(len(initiators) == 0, 1,
+ "length of initiator list is {}, expected 0".format(len(initiators)))
+ netmasks = value.get('netmasks')
+ verify(len(netmasks) == 0, 1,
+ "length of netmask list is {}, expected 0".format(len(netmasks)))
+
+ for idx, value in enumerate(rpc_param['netmask']):
+ tag = idx + 1
+ rpc.add_initiators_to_initiator_group(tag, '-n', rpc_param['initiator_name'], '-m', value)
+ output = rpc.get_initiator_groups()
+ jsonvalues = json.loads(output)
+ verify(len(jsonvalues) == tag, 1,
+ "get_initiator_groups returned {} groups, expected {}".format(len(jsonvalues), tag))
+
+ tag_list = []
+ for idx, value in enumerate(jsonvalues):
+ verify(value['initiators'][0] == rpc_param['initiator_name'], 1,
+ "initiator value is {}, expected {}".format(value['initiators'][0], rpc_param['initiator_name']))
+ tag_list.append(value['tag'])
+ verify(value['tag'] == idx + 1, 1,
+ "tag value is {}, expected {}".format(value['tag'], idx + 1))
+ verify(value['netmasks'][0] == rpc_param['netmask'][idx], 1,
+ "netmasks value is {}, expected {}".format(value['netmasks'][0], rpc_param['netmask'][idx]))
+
+ for idx, value in enumerate(tag_list):
+ rpc.delete_initiator_group(value)
+ output = rpc.get_initiator_groups()
+ jsonvalues = json.loads(output)
+ verify(len(jsonvalues) == (len(tag_list) - (idx + 1)), 1,
+ "get_initiator_groups returned {} groups, expected {}".format(len(jsonvalues), (len(tag_list) - (idx + 1))))
+ if not jsonvalues:
+ break
+ for jidx, jvalue in enumerate(jsonvalues):
+ verify(jvalue['initiators'][0] == rpc_param['initiator_name'], 1,
+ "initiator value is {}, expected {}".format(jvalue['initiators'][0], rpc_param['initiator_name']))
+ verify(jvalue['tag'] != value or jvalue['tag'] == tag_list[idx + jidx + 1], 1,
+ "tag value is {}, expected {} and not {}".format(jvalue['tag'], tag_list[idx + jidx + 1], value))
+ verify(jvalue['netmasks'][0] == rpc_param['netmask'][idx + jidx + 1], 1,
+ "netmasks value is {}, expected {}".format(jvalue['netmasks'][0], rpc_param['netmask'][idx + jidx + 1]))
+
+ print("verify_initiator_groups_rpc_method passed.")
+
+
+def verify_target_nodes_rpc_methods(rpc_py, rpc_param):
+ rpc = spdk_rpc(rpc_py)
+ output = rpc.get_iscsi_global_params()
+ jsonvalues = json.loads(output)
+ nodebase = jsonvalues['node_base']
+ output = rpc.get_target_nodes()
+ jsonvalues = json.loads(output)
+ verify(not jsonvalues, 1,
+ "get_target_nodes returned {}, expected empty".format(jsonvalues))
+
+ rpc.construct_malloc_bdev(rpc_param['malloc_bdev_size'], rpc_param['malloc_block_size'])
+ rpc.add_portal_group(portal_tag, "{}:{}".format(rpc_param['target_ip'], str(rpc_param['port'])))
+ rpc.add_initiator_group(initiator_tag, rpc_param['initiator_name'], rpc_param['netmask'])
+
+ lun_mapping = "Malloc" + str(rpc_param['lun_total']) + ":0"
+ net_mapping = portal_tag + ":" + initiator_tag
+ rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'], '-d')
+ output = rpc.get_target_nodes()
+ jsonvalues = json.loads(output)
+ verify(len(jsonvalues) == 1, 1,
+ "get_target_nodes returned {} nodes, expected 1".format(len(jsonvalues)))
+ bdev_name = jsonvalues[0]['luns'][0]['bdev_name']
+ verify(bdev_name == "Malloc" + str(rpc_param['lun_total']), 1,
+ "bdev_name value is {}, expected Malloc{}".format(jsonvalues[0]['luns'][0]['bdev_name'], str(rpc_param['lun_total'])))
+ name = jsonvalues[0]['name']
+ verify(name == nodebase + ":" + rpc_param['target_name'], 1,
+ "target name value is {}, expected {}".format(name, nodebase + ":" + rpc_param['target_name']))
+ verify(jsonvalues[0]['alias_name'] == rpc_param['alias_name'], 1,
+ "target alias_name value is {}, expected {}".format(jsonvalues[0]['alias_name'], rpc_param['alias_name']))
+ verify(jsonvalues[0]['luns'][0]['lun_id'] == 0, 1,
+ "lun id value is {}, expected 0".format(jsonvalues[0]['luns'][0]['lun_id']))
+ verify(jsonvalues[0]['pg_ig_maps'][0]['ig_tag'] == int(initiator_tag), 1,
+ "initiator group tag value is {}, expected {}".format(jsonvalues[0]['pg_ig_maps'][0]['ig_tag'], initiator_tag))
+ verify(jsonvalues[0]['queue_depth'] == rpc_param['queue_depth'], 1,
+ "queue depth value is {}, expected {}".format(jsonvalues[0]['queue_depth'], rpc_param['queue_depth']))
+ verify(jsonvalues[0]['pg_ig_maps'][0]['pg_tag'] == int(portal_tag), 1,
+ "portal group tag value is {}, expected {}".format(jsonvalues[0]['pg_ig_maps'][0]['pg_tag'], portal_tag))
+ verify(jsonvalues[0]['disable_chap'] == rpc_param['disable_chap'], 1,
+ "disable chap value is {}, expected {}".format(jsonvalues[0]['disable_chap'], rpc_param['disable_chap']))
+ verify(jsonvalues[0]['mutual_chap'] == rpc_param['mutual_chap'], 1,
+ "chap mutual value is {}, expected {}".format(jsonvalues[0]['mutual_chap'], rpc_param['mutual_chap']))
+ verify(jsonvalues[0]['require_chap'] == rpc_param['require_chap'], 1,
+ "chap required value is {}, expected {}".format(jsonvalues[0]['require_chap'], rpc_param['require_chap']))
+ verify(jsonvalues[0]['chap_group'] == rpc_param['chap_group'], 1,
+ "chap auth group value is {}, expected {}".format(jsonvalues[0]['chap_group'], rpc_param['chap_group']))
+ verify(jsonvalues[0]['header_digest'] == rpc_param['header_digest'], 1,
+ "header digest value is {}, expected {}".format(jsonvalues[0]['header_digest'], rpc_param['header_digest']))
+ verify(jsonvalues[0]['data_digest'] == rpc_param['data_digest'], 1,
+ "data digest value is {}, expected {}".format(jsonvalues[0]['data_digest'], rpc_param['data_digest']))
+ lun_id = '1'
+ rpc.target_node_add_lun(name, bdev_name, "-i", lun_id)
+ output = rpc.get_target_nodes()
+ jsonvalues = json.loads(output)
+ verify(jsonvalues[0]['luns'][1]['bdev_name'] == "Malloc" + str(rpc_param['lun_total']), 1,
+ "bdev_name value is {}, expected Malloc{}".format(jsonvalues[0]['luns'][0]['bdev_name'], str(rpc_param['lun_total'])))
+ verify(jsonvalues[0]['luns'][1]['lun_id'] == 1, 1,
+ "lun id value is {}, expected 1".format(jsonvalues[0]['luns'][1]['lun_id']))
+
+ rpc.delete_target_node(name)
+ output = rpc.get_target_nodes()
+ jsonvalues = json.loads(output)
+ verify(not jsonvalues, 1,
+ "get_target_nodes returned {}, expected empty".format(jsonvalues))
+
+ rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'], '-d')
+
+ rpc.delete_portal_group(portal_tag)
+ rpc.delete_initiator_group(initiator_tag)
+ rpc.delete_target_node(name)
+ output = rpc.get_target_nodes()
+ jsonvalues = json.loads(output)
+ if not jsonvalues:
+ print("This issue will be fixed later.")
+
+ print("verify_target_nodes_rpc_methods passed.")
+
+
+def verify_get_interfaces(rpc_py):
+ rpc = spdk_rpc(rpc_py)
+ nics = json.loads(rpc.get_interfaces())
+ nics_names = set(x["name"] for x in nics)
+ # parse ip link show to verify the get_interfaces result
+ ip_show = ns_cmd + " ip link show"
+ ifcfg_nics = set(re.findall("\S+:\s(\S+?)(?:@\S+){0,1}:\s<.*", check_output(ip_show.split()).decode()))
+ verify(nics_names == ifcfg_nics, 1, "get_interfaces returned {}".format(nics))
+ print("verify_get_interfaces passed.")
+
+
+def help_get_interface_ip_list(rpc_py, nic_name):
+ rpc = spdk_rpc(rpc_py)
+ nics = json.loads(rpc.get_interfaces())
+ nic = list([x for x in nics if x["name"] == nic_name])
+ verify(len(nic) != 0, 1,
+ "Nic name: {} is not found in {}".format(nic_name, [x["name"] for x in nics]))
+ return nic[0]["ip_addr"]
+
+
+def verify_add_delete_ip_address(rpc_py):
+ rpc = spdk_rpc(rpc_py)
+ nics = json.loads(rpc.get_interfaces())
+ # add ip on up to first 2 nics
+ for x in nics[:2]:
+ faked_ip = "123.123.{}.{}".format(random.randint(1, 254), random.randint(1, 254))
+ ping_cmd = ns_cmd + " ping -c 1 -W 1 " + faked_ip
+ rpc.add_ip_address(x["ifc_index"], faked_ip)
+ verify(faked_ip in help_get_interface_ip_list(rpc_py, x["name"]), 1,
+ "add ip {} to nic {} failed.".format(faked_ip, x["name"]))
+ try:
+ check_call(ping_cmd.split())
+ except BaseException:
+ verify(False, 1,
+ "ping ip {} for {} was failed(adding was successful)".format
+ (faked_ip, x["name"]))
+ rpc.delete_ip_address(x["ifc_index"], faked_ip)
+ verify(faked_ip not in help_get_interface_ip_list(rpc_py, x["name"]), 1,
+ "delete ip {} from nic {} failed.(adding and ping were successful)".format
+ (faked_ip, x["name"]))
+ # ping should be failed and throw an CalledProcessError exception
+ try:
+ check_call(ping_cmd.split())
+ except CalledProcessError as _:
+ pass
+ except Exception as e:
+ verify(False, 1,
+ "Unexpected exception was caught {}(adding/ping/delete were successful)".format
+ (str(e)))
+ else:
+ verify(False, 1,
+ "ip {} for {} could be pinged after delete ip(adding/ping/delete were successful)".format
+ (faked_ip, x["name"]))
+ print("verify_add_delete_ip_address passed.")
+
+
+def verify_add_nvme_bdev_rpc_methods(rpc_py):
+ rpc = spdk_rpc(rpc_py)
+ test_pass = 0
+ output = check_output(["lspci", "-mm", "-nn"])
+ addrs = re.findall('^([0-9]{2}:[0-9]{2}.[0-9]) "Non-Volatile memory controller \[0108\]".*-p02', output.decode(), re.MULTILINE)
+ for addr in addrs:
+ ctrlr_address = "-b Nvme{} -t pcie -a 0000:{}".format(addrs.index(addr), addr)
+ rpc.construct_nvme_bdev(ctrlr_address)
+ print("add nvme device passed first time")
+ test_pass = 0
+ try:
+ rpc.construct_nvme_bdev(ctrlr_address)
+ except Exception as e:
+ print("add nvme device passed second time")
+ test_pass = 1
+ pass
+ else:
+ pass
+ verify(test_pass == 1, 1, "add nvme device passed second time")
+ print("verify_add_nvme_bdev_rpc_methods passed.")
+
+
+if __name__ == "__main__":
+
+ rpc_py = sys.argv[1]
+
+ try:
+ verify_trace_flag_rpc_methods(rpc_py, rpc_param)
+ verify_get_interfaces(rpc_py)
+ verify_add_delete_ip_address(rpc_py)
+ create_malloc_bdevs_rpc_methods(rpc_py, rpc_param)
+ verify_portal_groups_rpc_methods(rpc_py, rpc_param)
+ verify_initiator_groups_rpc_methods(rpc_py, rpc_param)
+ verify_target_nodes_rpc_methods(rpc_py, rpc_param)
+ verify_scsi_devices_rpc_methods(rpc_py)
+ verify_iscsi_connection_rpc_methods(rpc_py)
+ verify_add_nvme_bdev_rpc_methods(rpc_py)
+ except RpcException as e:
+ print("{}. Exiting with status {}".format(e.message, e.retval))
+ raise e
+ except Exception as e:
+ raise e
+
+ sys.exit(0)
diff --git a/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.sh b/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.sh
new file mode 100755
index 00000000..ac5c4647
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/rpc_config/rpc_config.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/iscsi_tgt/common.sh
+
+timing_enter rpc_config
+
+# $1 = test type (posix/vpp)
+if [ "$1" == "posix" ] || [ "$1" == "vpp" ]; then
+ TEST_TYPE=$1
+else
+ echo "No iSCSI test type specified"
+ exit 1
+fi
+
+MALLOC_BDEV_SIZE=64
+
+rpc_py=$rootdir/scripts/rpc.py
+rpc_config_py="$testdir/rpc_config.py"
+
+timing_enter start_iscsi_tgt
+
+$ISCSI_APP --wait-for-rpc &
+pid=$!
+echo "Process pid: $pid"
+
+trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py set_iscsi_options -o 30 -a 16
+$rpc_py start_subsystem_init
+echo "iscsi_tgt is listening. Running tests..."
+
+timing_exit start_iscsi_tgt
+
+$rpc_config_py $rpc_py $TARGET_IP $INITIATOR_IP $ISCSI_PORT $NETMASK $TARGET_NAMESPACE $TEST_TYPE
+
+$rpc_py get_bdevs
+
+trap - SIGINT SIGTERM EXIT
+
+iscsicleanup
+killprocess $pid
+timing_exit rpc_config
diff --git a/src/spdk/test/iscsi_tgt/test_plan.md b/src/spdk/test/iscsi_tgt/test_plan.md
new file mode 100644
index 00000000..4afad162
--- /dev/null
+++ b/src/spdk/test/iscsi_tgt/test_plan.md
@@ -0,0 +1,41 @@
+# SPDK iscsi_tgt test plan
+
+## Objective
+The purpose of these tests is to verify correct behavior of SPDK iSCSI target
+feature.
+These tests are run either per-commit or as nightly tests.
+
+## Configuration
+All tests share the same basic configuration file for SPDK iscsi_tgt to run.
+Static configuration from config file consists of setting number of per session
+queues and enabling RPC for further configuration via RPC calls.
+RPC calls used for dynamic configuration consist:
+- creating Malloc backend devices
+- creating Null Block backend devices
+- creating Pmem backend devices
+- constructing iSCSI subsystems
+- deleting iSCSI subsystems
+
+### Tests
+
+#### Test 1: iSCSI namespace on a Pmem device
+This test configures a SPDK iSCSI subsystem backed by pmem
+devices and uses FIO to generate I/Os that target those subsystems.
+Test steps:
+- Step 1: Start SPDK iscsi_tgt application.
+- Step 2: Create 10 pmem pools.
+- Step 3: Create pmem bdevs on pmem pools.
+- Step 4: Create iSCSI subsystems with 10 pmem bdevs namespaces.
+- Step 5: Connect to iSCSI susbsystems with kernel initiator.
+- Step 6: Run FIO with workload parameters: blocksize=4096, iodepth=64,
+ workload=randwrite; varify flag is enabled so that
+ FIO reads and verifies the data written to the pmem device.
+ The run time is 10 seconds for a quick test an 10 minutes
+ for longer nightly test.
+- Step 7: Run FIO with workload parameters: blocksize=128kB, iodepth=4,
+ workload=randwrite; varify flag is enabled so that
+ FIO reads and verifies the data written to the pmem device.
+ The run time is 10 seconds for a quick test an 10 minutes
+ for longer nightly test.
+- Step 8: Disconnect kernel initiator from iSCSI subsystems.
+- Step 9: Delete iSCSI subsystems from configuration.
diff --git a/src/spdk/test/json_config/clear_config.py b/src/spdk/test/json_config/clear_config.py
new file mode 100755
index 00000000..e6d8dd71
--- /dev/null
+++ b/src/spdk/test/json_config/clear_config.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+import argparse
+sys.path.append(os.path.join(os.path.dirname(__file__), "../../scripts"))
+import rpc # noqa
+from rpc.client import print_dict, JSONRPCException # noqa
+
+
+def get_bdev_name_key(bdev):
+ bdev_name_key = 'name'
+ if 'method' in bdev and bdev['method'] == 'construct_split_vbdev':
+ bdev_name_key = "base_bdev"
+ return bdev_name_key
+
+
+def get_bdev_name(bdev):
+ bdev_name = None
+ if 'params' in bdev:
+ if 'name' in bdev['params']:
+ bdev_name = bdev['params']['name']
+ elif 'base_name' in bdev['params']:
+ bdev_name = bdev['params']['base_name']
+ elif 'base_bdev' in bdev['params']:
+ bdev_name = bdev['params']['base_bdev']
+ if 'method' in bdev and bdev['method'] == 'construct_error_bdev':
+ bdev_name = "EE_%s" % bdev_name
+ return bdev_name
+
+
+def delete_subbdevs(args, bdev, rpc_bdevs):
+ ret_value = False
+ bdev_name = get_bdev_name(bdev)
+ if bdev_name and 'method' in bdev:
+ construct_method = bdev['method']
+ if construct_method == 'construct_nvme_bdev':
+ for rpc_bdev in rpc_bdevs:
+ if bdev_name in rpc_bdev['name'] and rpc_bdev['product_name'] == "NVMe disk":
+ args.client.call('delete_nvme_controller', {'name': "%s" % rpc_bdev['name'].split('n')[0]})
+ ret_value = True
+
+ return ret_value
+
+
+def get_bdev_destroy_method(bdev):
+ destroy_method_map = {'construct_nvme_bdev': "delete_nvme_controller",
+ 'construct_malloc_bdev': "delete_malloc_bdev",
+ 'construct_null_bdev': "delete_null_bdev",
+ 'construct_rbd_bdev': "delete_rbd_bdev",
+ 'construct_pmem_bdev': "delete_pmem_bdev",
+ 'construct_aio_bdev': "delete_aio_bdev",
+ 'construct_error_bdev': "delete_error_bdev",
+ 'construct_split_vbdev': "destruct_split_vbdev",
+ 'construct_virtio_dev': "remove_virtio_bdev",
+ 'construct_crypto_bdev': "delete_crypto_bdev"
+ }
+ destroy_method = None
+ if 'method' in bdev:
+ construct_method = bdev['method']
+ if construct_method in list(destroy_method_map.keys()):
+ destroy_method = destroy_method_map[construct_method]
+
+ return destroy_method
+
+
+def clear_bdev_subsystem(args, bdev_config):
+ rpc_bdevs = args.client.call("get_bdevs")
+ for bdev in bdev_config:
+ if delete_subbdevs(args, bdev, rpc_bdevs):
+ continue
+ bdev_name_key = get_bdev_name_key(bdev)
+ bdev_name = get_bdev_name(bdev)
+ destroy_method = get_bdev_destroy_method(bdev)
+ if destroy_method:
+ args.client.call(destroy_method, {bdev_name_key: bdev_name})
+
+ ''' Disable and reset hotplug '''
+ rpc.bdev.set_bdev_nvme_hotplug(args.client, False)
+
+
+def get_nvmf_destroy_method(nvmf):
+ destroy_method_map = {'nvmf_subsystem_create': "delete_nvmf_subsystem"}
+ try:
+ return destroy_method_map[nvmf['method']]
+ except KeyError:
+ return None
+
+
+def clear_nvmf_subsystem(args, nvmf_config):
+ for nvmf in nvmf_config:
+ destroy_method = get_nvmf_destroy_method(nvmf)
+ if destroy_method:
+ args.client.call(destroy_method, {'nqn': nvmf['params']['nqn']})
+
+
+def get_iscsi_destroy_method(iscsi):
+ destroy_method_map = {'add_portal_group': "delete_portal_group",
+ 'add_initiator_group': "delete_initiator_group",
+ 'construct_target_node': "delete_target_node",
+ 'set_iscsi_options': None
+ }
+ return destroy_method_map[iscsi['method']]
+
+
+def get_iscsi_name(iscsi):
+ if 'name' in iscsi['params']:
+ return iscsi['params']['name']
+ else:
+ return iscsi['params']['tag']
+
+
+def get_iscsi_name_key(iscsi):
+ if iscsi['method'] == 'construct_target_node':
+ return "name"
+ else:
+ return 'tag'
+
+
+def clear_iscsi_subsystem(args, iscsi_config):
+ for iscsi in iscsi_config:
+ destroy_method = get_iscsi_destroy_method(iscsi)
+ if destroy_method:
+ args.client.call(destroy_method, {get_iscsi_name_key(iscsi): get_iscsi_name(iscsi)})
+
+
+def get_nbd_destroy_method(nbd):
+ destroy_method_map = {'start_nbd_disk': "stop_nbd_disk"
+ }
+ return destroy_method_map[nbd['method']]
+
+
+def clear_nbd_subsystem(args, nbd_config):
+ for nbd in nbd_config:
+ destroy_method = get_nbd_destroy_method(nbd)
+ if destroy_method:
+ args.client.call(destroy_method, {'nbd_device': nbd['params']['nbd_device']})
+
+
+def clear_net_framework_subsystem(args, net_framework_config):
+ pass
+
+
+def clear_copy_subsystem(args, copy_config):
+ pass
+
+
+def clear_interface_subsystem(args, interface_config):
+ pass
+
+
+def clear_vhost_subsystem(args, vhost_config):
+ for vhost in reversed(vhost_config):
+ if 'method' in vhost:
+ method = vhost['method']
+ if method in ['add_vhost_scsi_lun']:
+ args.client.call("remove_vhost_scsi_target",
+ {"ctrlr": vhost['params']['ctrlr'],
+ "scsi_target_num": vhost['params']['scsi_target_num']})
+ elif method in ['construct_vhost_scsi_controller', 'construct_vhost_blk_controller',
+ 'construct_vhost_nvme_controller']:
+ args.client.call("remove_vhost_controller", {'ctrlr': vhost['params']['ctrlr']})
+
+
+def call_test_cmd(func):
+ def rpc_test_cmd(*args, **kwargs):
+ try:
+ func(*args, **kwargs)
+ except JSONRPCException as ex:
+ print((ex.message))
+ exit(1)
+ return rpc_test_cmd
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description='Clear config command')
+ parser.add_argument('-s', dest='server_addr', default='/var/tmp/spdk.sock')
+ parser.add_argument('-p', dest='port', default=5260, type=int)
+ parser.add_argument('-t', dest='timeout', default=60.0, type=float)
+ parser.add_argument('-v', dest='verbose', action='store_true')
+ subparsers = parser.add_subparsers(help='RPC methods')
+
+ @call_test_cmd
+ def clear_config(args):
+ for subsystem_item in reversed(args.client.call('get_subsystems')):
+ args.subsystem = subsystem_item['subsystem']
+ clear_subsystem(args)
+
+ p = subparsers.add_parser('clear_config', help="""Clear configuration of all SPDK subsystems and targets using JSON RPC""")
+ p.set_defaults(func=clear_config)
+
+ @call_test_cmd
+ def clear_subsystem(args):
+ config = args.client.call('get_subsystem_config', {"name": args.subsystem})
+ if config is None:
+ return
+ if args.verbose:
+ print("Calling clear_%s_subsystem" % args.subsystem)
+ globals()["clear_%s_subsystem" % args.subsystem](args, config)
+
+ p = subparsers.add_parser('clear_subsystem', help="""Clear configuration of SPDK subsystem using JSON RPC""")
+ p.add_argument('--subsystem', help="""Subsystem name""")
+ p.set_defaults(func=clear_subsystem)
+
+ args = parser.parse_args()
+
+ try:
+ args.client = rpc.client.JSONRPCClient(args.server_addr, args.port, args.verbose, args.timeout)
+ except JSONRPCException as ex:
+ print((ex.message))
+ exit(1)
+ args.func(args)
diff --git a/src/spdk/test/json_config/common.sh b/src/spdk/test/json_config/common.sh
new file mode 100644
index 00000000..a0a56951
--- /dev/null
+++ b/src/spdk/test/json_config/common.sh
@@ -0,0 +1,249 @@
+JSON_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]}))
+SPDK_BUILD_DIR=$JSON_DIR/../../
+source $JSON_DIR/../common/autotest_common.sh
+source $JSON_DIR/../nvmf/common.sh
+
+spdk_rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s /var/tmp/spdk.sock"
+spdk_clear_config_py="$JSON_DIR/clear_config.py -s /var/tmp/spdk.sock"
+initiator_rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s /var/tmp/virtio.sock"
+initiator_clear_config_py="$JSON_DIR/clear_config.py -s /var/tmp/virtio.sock"
+base_json_config=$JSON_DIR/base_config.json
+last_json_config=$JSON_DIR/last_config.json
+full_config=$JSON_DIR/full_config.json
+base_bdevs=$JSON_DIR/bdevs_base.txt
+last_bdevs=$JSON_DIR/bdevs_last.txt
+null_json_config=$JSON_DIR/null_json_config.json
+
+function run_spdk_tgt() {
+ echo "Running spdk target"
+ $SPDK_BUILD_DIR/app/spdk_tgt/spdk_tgt -m 0x1 -p 0 -s 4096 --wait-for-rpc &
+ spdk_tgt_pid=$!
+
+ echo "Waiting for app to run..."
+ waitforlisten $spdk_tgt_pid
+ echo "spdk_tgt started - pid=$spdk_tgt_pid but waits for subsystem initialization"
+
+ echo ""
+}
+
+function load_nvme() {
+ echo '{"subsystems": [' > nvme_config.json
+ $SPDK_BUILD_DIR/scripts/gen_nvme.sh --json >> nvme_config.json
+ echo ']}' >> nvme_config.json
+ $rpc_py load_config < nvme_config.json
+ rm nvme_config.json
+}
+
+function run_initiator() {
+ $SPDK_BUILD_DIR/app/spdk_tgt/spdk_tgt -m 0x2 -p 0 -g -u -s 1024 -r /var/tmp/virtio.sock --wait-for-rpc &
+ virtio_pid=$!
+ waitforlisten $virtio_pid /var/tmp/virtio.sock
+}
+
+function upload_vhost() {
+ $rpc_py construct_split_vbdev Nvme0n1 8
+ $rpc_py construct_vhost_scsi_controller sample1
+ $rpc_py add_vhost_scsi_lun sample1 0 Nvme0n1p3
+ $rpc_py add_vhost_scsi_lun sample1 1 Nvme0n1p4
+ $rpc_py set_vhost_controller_coalescing sample1 1 100
+ $rpc_py construct_vhost_blk_controller sample2 Nvme0n1p5
+ $rpc_py construct_vhost_nvme_controller sample3 16
+ $rpc_py add_vhost_nvme_ns sample3 Nvme0n1p6
+}
+
+function kill_targets() {
+ if [ ! -z $virtio_pid ]; then
+ killprocess $virtio_pid
+ fi
+ if [ ! -z $spdk_tgt_pid ]; then
+ killprocess $spdk_tgt_pid
+ fi
+}
+
+# Compare two JSON files.
+#
+# NOTE: Order of objects in JSON can change by just doing loads -> dumps so all JSON objects (not arrays) are sorted by
+# config_filter.py script. Sorted output is used to compare JSON output.
+#
+function json_diff()
+{
+ local tmp_file_1=$(mktemp ${1}.XXX)
+ local tmp_file_2=$(mktemp ${2}.XXX)
+ local ret=0
+
+ cat $1 | $JSON_DIR/config_filter.py -method "sort" > $tmp_file_1
+ cat $2 | $JSON_DIR/config_filter.py -method "sort" > $tmp_file_2
+
+ if ! diff -u $tmp_file_1 $tmp_file_2; then
+ ret=1
+ fi
+
+ rm $tmp_file_1 $tmp_file_2
+ return $ret
+}
+
+# This function test if json config was properly saved and loaded.
+# 1. Get a list of bdevs and save it to the file "base_bdevs".
+# 2. Save only configuration of the running spdk_tgt to the file "base_json_config"
+# (global parameters are not saved).
+# 3. Clear configuration of the running spdk_tgt.
+# 4. Save only configuration of the running spdk_tgt to the file "null_json_config"
+# (global parameters are not saved).
+# 5. Check if configuration of the running spdk_tgt is cleared by checking
+# if the file "null_json_config" doesn't have any configuration.
+# 6. Load the file "base_json_config" to the running spdk_tgt.
+# 7. Get a list of bdevs and save it to the file "last_bdevs".
+# 8. Save only configuration of the running spdk_tgt to the file "last_json_config".
+# 9. Check if the file "base_json_config" matches the file "last_json_config".
+# 10. Check if the file "base_bdevs" matches the file "last_bdevs".
+# 11. Remove all files.
+function test_json_config() {
+ $rpc_py get_bdevs | jq '.|sort_by(.name)' > $base_bdevs
+ $rpc_py save_config > $full_config
+ $JSON_DIR/config_filter.py -method "delete_global_parameters" < $full_config > $base_json_config
+ $clear_config_py clear_config
+ $rpc_py save_config | $JSON_DIR/config_filter.py -method "delete_global_parameters" > $null_json_config
+ if [ "[]" != "$(jq '.subsystems | map(select(.config != null)) | map(select(.config != []))' $null_json_config)" ]; then
+ echo "Config has not been cleared"
+ return 1
+ fi
+ $rpc_py load_config < $base_json_config
+ $rpc_py get_bdevs | jq '.|sort_by(.name)' > $last_bdevs
+ $rpc_py save_config | $JSON_DIR/config_filter.py -method "delete_global_parameters" > $last_json_config
+
+ json_diff $base_json_config $last_json_config
+ json_diff $base_bdevs $last_bdevs
+ remove_config_files_after_test_json_config
+}
+
+function remove_config_files_after_test_json_config() {
+ rm -f $last_bdevs $base_bdevs
+ rm -f $last_json_config $base_json_config
+ rm -f $full_config $null_json_config
+}
+
+function create_pmem_bdev_subsytem_config() {
+ $rpc_py create_pmem_pool /tmp/pool_file1 128 512
+ $rpc_py construct_pmem_bdev -n pmem1 /tmp/pool_file1
+}
+
+function clear_pmem_bdev_subsystem_config() {
+ $clear_config_py clear_config
+ $rpc_py delete_pmem_pool /tmp/pool_file1
+}
+
+function create_rbd_bdev_subsystem_config() {
+ rbd_setup 127.0.0.1
+ $rpc_py construct_rbd_bdev $RBD_POOL $RBD_NAME 4096
+}
+
+function clear_rbd_bdev_subsystem_config() {
+ $clear_config_py clear_config
+ rbd_cleanup
+}
+
+function create_bdev_subsystem_config() {
+ $rpc_py construct_split_vbdev Nvme0n1 2
+ $rpc_py construct_null_bdev Null0 32 512
+ $rpc_py construct_malloc_bdev 128 512 --name Malloc0
+ $rpc_py construct_malloc_bdev 64 4096 --name Malloc1
+ $rpc_py construct_malloc_bdev 8 1024 --name Malloc2
+ if [ $SPDK_TEST_CRYPTO -eq 1 ]; then
+ $rpc_py construct_malloc_bdev 8 1024 --name Malloc3
+ if [ $(lspci -d:37c8 | wc -l) -eq 0 ]; then
+ $rpc_py construct_crypto_bdev -b Malloc3 -c CryMalloc3 -d crypto_aesni_mb -k 0123456789123456
+ else
+ $rpc_py construct_crypto_bdev -b Malloc3 -c CryMalloc3 -d crypto_qat -k 0123456789123456
+ fi
+ fi
+ $rpc_py construct_error_bdev Malloc2
+ if [ $(uname -s) = Linux ]; then
+ dd if=/dev/zero of=/tmp/sample_aio bs=2048 count=5000
+ $rpc_py construct_aio_bdev /tmp/sample_aio aio_disk 1024
+ fi
+ $rpc_py construct_lvol_store -c 1048576 Nvme0n1p0 lvs_test
+ $rpc_py construct_lvol_bdev -l lvs_test lvol0 32
+ $rpc_py construct_lvol_bdev -l lvs_test -t lvol1 32
+ $rpc_py snapshot_lvol_bdev lvs_test/lvol0 snapshot0
+ $rpc_py clone_lvol_bdev lvs_test/snapshot0 clone0
+}
+
+function create_nvmf_subsystem_config() {
+ rdma_device_init
+ RDMA_IP_LIST=$(get_available_rdma_ips)
+ NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+ if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "Error: no NIC for nvmf test"
+ return 1
+ fi
+
+ bdevs="$($rpc_py construct_malloc_bdev 64 512) "
+ bdevs+="$($rpc_py construct_malloc_bdev 64 512)"
+ $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+ for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev
+ done
+ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_FIRST_TARGET_IP -s "$NVMF_PORT"
+}
+
+function clear_nvmf_subsystem_config() {
+ $clear_config_py clear_config
+}
+
+function clear_bdev_subsystem_config() {
+ $rpc_py destroy_lvol_bdev lvs_test/clone0
+ $rpc_py destroy_lvol_bdev lvs_test/lvol0
+ $rpc_py destroy_lvol_bdev lvs_test/snapshot0
+ $rpc_py destroy_lvol_store -l lvs_test
+ $clear_config_py clear_config
+ if [ $(uname -s) = Linux ]; then
+ rm -f /tmp/sample_aio
+ fi
+}
+
+# In this test, target is spdk_tgt or virtio_initiator.
+# 1. Save current spdk config to full_config
+# and save only global parameters to the file "base_json_config".
+# 2. Exit the running spdk target.
+# 3. Start the spdk target and wait for loading config.
+# 4. Load global parameters and configuration to the spdk target from the file full_config.
+# 5. Save json config to the file "full_config".
+# 6. Save only global parameters to the file "last_json_config".
+# 7. Check if the file "base_json_config" matches the file "last_json_config".
+# 8. Delete all files.
+function test_global_params() {
+ target=$1
+ $rpc_py save_config > $full_config
+ $JSON_DIR/config_filter.py -method "delete_configs" < $full_config > $base_json_config
+ if [ $target == "spdk_tgt" ]; then
+ killprocess $spdk_tgt_pid
+ run_spdk_tgt
+ elif [ $target == "virtio_initiator" ]; then
+ killprocess $virtio_pid
+ run_initiator
+ else
+ echo "Target is not specified for test_global_params"
+ return 1
+ fi
+ $rpc_py load_config < $full_config
+ $rpc_py save_config > $full_config
+ $JSON_DIR/config_filter.py -method "delete_configs" < $full_config > $last_json_config
+
+ json_diff $base_json_config $last_json_config
+ rm $base_json_config $last_json_config
+ rm $full_config
+}
+
+function on_error_exit() {
+ set +e
+ echo "Error on $1 - $2"
+ remove_config_files_after_test_json_config
+ rpc_py="$spdk_rpc_py"
+ clear_config_py="$spdk_clear_config_py"
+ clear_bdev_subsystem_config
+
+ kill_targets
+
+ print_backtrace
+ exit 1
+}
diff --git a/src/spdk/test/json_config/config_filter.py b/src/spdk/test/json_config/config_filter.py
new file mode 100755
index 00000000..59e96f94
--- /dev/null
+++ b/src/spdk/test/json_config/config_filter.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+
+import sys
+import json
+import argparse
+from collections import OrderedDict
+
+
+def sort_json_object(o):
+ if isinstance(o, dict):
+ sorted_o = OrderedDict()
+ """ Order of keys in JSON object is irrelevant but we need to pick one
+ to be able to compare JSONS. """
+ for key in sorted(o.keys()):
+ sorted_o[key] = sort_json_object(o[key])
+ return sorted_o
+ if isinstance(o, list):
+ """ Keep list in the same orded but sort each item """
+ return [sort_json_object(item) for item in o]
+ else:
+ return o
+
+
+def filter_methods(do_remove_global_rpcs):
+ global_rpcs = [
+ 'set_iscsi_options',
+ 'set_nvmf_target_config',
+ 'set_nvmf_target_options',
+ 'nvmf_create_transport',
+ 'set_bdev_options',
+ 'set_bdev_nvme_options',
+ 'set_bdev_nvme_hotplug',
+ ]
+
+ data = json.loads(sys.stdin.read())
+ out = {'subsystems': []}
+ for s in data['subsystems']:
+ if s['config']:
+ s_config = []
+ for config in s['config']:
+ m_name = config['method']
+ is_global_rpc = m_name in global_rpcs
+ if do_remove_global_rpcs != is_global_rpc:
+ s_config.append(config)
+ else:
+ s_config = None
+ out['subsystems'].append({
+ 'subsystem': s['subsystem'],
+ 'config': s_config,
+ })
+
+ print(json.dumps(out, indent=2))
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-method', dest='method')
+
+ args = parser.parse_args()
+ if args.method == "delete_global_parameters":
+ filter_methods(True)
+ elif args.method == "delete_configs":
+ filter_methods(False)
+ elif args.method == "sort":
+ """ Wrap input into JSON object so any input is possible here
+ like output from get_bdevs RPC method"""
+ o = json.loads('{ "the_object": ' + sys.stdin.read() + ' }')
+ print(json.dumps(sort_json_object(o)['the_object'], indent=2))
+ else:
+ raise ValueError("Invalid method '{}'".format(args.method))
diff --git a/src/spdk/test/lvol/lvol.sh b/src/spdk/test/lvol/lvol.sh
new file mode 100755
index 00000000..a5883765
--- /dev/null
+++ b/src/spdk/test/lvol/lvol.sh
@@ -0,0 +1,135 @@
+#!/usr/bin/env bash
+set -e
+BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../ && pwd)"
+
+total_size=256
+block_size=512
+test_cases=all
+x=""
+
+rpc_py="$TEST_DIR/scripts/rpc.py "
+
+function usage() {
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for doing automated lvol tests"
+ echo "Usage: $(basename $1) [OPTIONS]"
+ echo
+ echo "-h, --help print help and exit"
+ echo " --total-size Size of malloc bdev in MB (int > 0)"
+ echo " --block-size Block size for this bdev"
+ echo "-x set -x for script debug"
+ echo " --test-cases= List test cases which will be run:
+ 1: 'construct_lvs_positive',
+ 50: 'construct_logical_volume_positive',
+ 51: 'construct_multi_logical_volumes_positive',
+ 52: 'construct_lvol_bdev_using_name_positive',
+ 53: 'construct_lvol_bdev_duplicate_names_positive',
+ 100: 'construct_logical_volume_nonexistent_lvs_uuid',
+ 101: 'construct_lvol_bdev_on_full_lvol_store',
+ 102: 'construct_lvol_bdev_name_twice',
+ 150: 'resize_lvol_bdev_positive',
+ 200: 'resize_logical_volume_nonexistent_logical_volume',
+ 201: 'resize_logical_volume_with_size_out_of_range',
+ 250: 'destroy_lvol_store_positive',
+ 251: 'destroy_lvol_store_use_name_positive',
+ 252: 'destroy_lvol_store_with_lvol_bdev_positive',
+ 253: 'destroy_multi_logical_volumes_positive',
+ 254: 'destroy_after_resize_lvol_bdev_positive',
+ 255: 'delete_lvol_store_persistent_positive',
+ 300: 'destroy_lvol_store_nonexistent_lvs_uuid',
+ 301: 'delete_lvol_store_underlying_bdev',
+ 350: 'nested_destroy_logical_volume_negative',
+ 400: 'nested_construct_logical_volume_positive',
+ 450: 'construct_lvs_nonexistent_bdev',
+ 451: 'construct_lvs_on_bdev_twice',
+ 452: 'construct_lvs_name_twice',
+ 500: 'nested_construct_lvol_bdev_on_full_lvol_store',
+ 550: 'delete_bdev_positive',
+ 551: 'delete_lvol_bdev',
+ 552: 'destroy_lvol_store_with_clones',
+ 553: 'unregister_lvol_bdev',
+ 600: 'construct_lvol_store_with_cluster_size_max',
+ 601: 'construct_lvol_store_with_cluster_size_min',
+ 650: 'thin_provisioning_check_space',
+ 651: 'thin_provisioning_read_empty_bdev',
+ 652: 'thin_provisionind_data_integrity_test',
+ 653: 'thin_provisioning_resize',
+ 654: 'thin_overprovisioning',
+ 655: 'thin_provisioning_filling_disks_less_than_lvs_size',
+ 700: 'tasting_positive',
+ 701: 'tasting_lvol_store_positive',
+ 750: 'snapshot_readonly',
+ 751: 'snapshot_compare_with_lvol_bdev',
+ 752: 'snapshot_during_io_traffic',
+ 753: 'snapshot_of_snapshot',
+ 754: 'clone_bdev_only',
+ 755: 'clone_writing_clone',
+ 756: 'clone_and_snapshot_consistency',
+ 757: 'clone_inflate',
+ 758: 'clone_decouple_parent',
+ 759: 'clone_decouple_parent_rw',
+ 800: 'rename_positive',
+ 801: 'rename_lvs_nonexistent',
+ 802: 'rename_lvs_EEXIST',
+ 803: 'rename_lvol_bdev_nonexistent',
+ 804: 'rename_lvol_bdev_EEXIST',
+ 10000: 'SIGTERM'
+ or
+ all: This parameter runs all tests
+ Ex: \"1,2,19,20\", default: all"
+ echo
+ echo
+}
+
+while getopts 'xh-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 && exit 0;;
+ total-size=*) total_size="${OPTARG#*=}" ;;
+ block-size=*) block_size="${OPTARG#*=}" ;;
+ test-cases=*) test_cases="${OPTARG#*=}" ;;
+ *) usage $0 "Invalid argument '$OPTARG'" && exit 1 ;;
+ esac
+ ;;
+ h) usage $0 && exit 0 ;;
+ x) set -x
+ x="-x" ;;
+ *) usage $0 "Invalid argument '$OPTARG'" && exit 1 ;;
+ esac
+done
+shift $(( OPTIND - 1 ))
+
+source $TEST_DIR/test/common/autotest_common.sh
+
+### Function starts vhost app
+function vhost_start()
+{
+ modprobe nbd
+ $TEST_DIR/app/vhost/vhost &
+ vhost_pid=$!
+ echo $vhost_pid > $BASE_DIR/vhost.pid
+ waitforlisten $vhost_pid
+}
+
+### Function stops vhost app
+function vhost_kill()
+{
+ ### Kill with SIGKILL param
+ if pkill -F $BASE_DIR/vhost.pid; then
+ sleep 1
+ fi
+ rm $BASE_DIR/vhost.pid || true
+ rmmod nbd || true
+}
+
+trap "vhost_kill; rm -f $BASE_DIR/aio_bdev_0 $BASE_DIR/aio_bdev_1; exit 1" SIGINT SIGTERM EXIT
+
+truncate -s 400M $BASE_DIR/aio_bdev_0 $BASE_DIR/aio_bdev_1
+vhost_start
+$BASE_DIR/lvol_test.py $rpc_py $total_size $block_size $BASE_DIR $TEST_DIR/app/vhost "${test_cases[@]}"
+
+vhost_kill
+rm -rf $BASE_DIR/aio_bdev_0 $BASE_DIR/aio_bdev_1
+trap - SIGINT SIGTERM EXIT
diff --git a/src/spdk/test/lvol/lvol_test.py b/src/spdk/test/lvol/lvol_test.py
new file mode 100755
index 00000000..50255f1f
--- /dev/null
+++ b/src/spdk/test/lvol/lvol_test.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+import sys
+from test_cases import *
+
+
+if __name__ == "__main__":
+ rpc_py = None
+ total_size = None
+ block_size = None
+ num_test = None
+ fail_count = 0
+ tc_failed = []
+ tc_list = []
+
+ if len(sys.argv) == 7 and len(sys.argv[6].split(',')) <= test_counter():
+ rpc_py = sys.argv[1]
+ total_size = int(sys.argv[2])
+ block_size = int(sys.argv[3])
+ base_dir_path = sys.argv[4]
+ app_path = sys.argv[5]
+ tc_list = sys.argv[6].split(',')
+ else:
+ print("Invalid argument")
+ try:
+ tc = TestCases(rpc_py, total_size, block_size, base_dir_path, app_path)
+ if "all" in tc_list:
+ tc_list = sorted([i.split("test_case")[1] for i in dir(TestCases) if "test_case" in i], key=int)
+
+ for num_test in tc_list:
+ fail_count = 0
+ exec("fail_count += tc.test_case{num_test}"
+ "()".format(num_test=num_test))
+ if fail_count:
+ tc_failed.append(num_test)
+
+ if not tc_failed:
+ print("RESULT: All test cases - PASS")
+ elif tc_failed:
+ print("RESULT: Some test cases FAIL")
+ print(tc_failed)
+ sys.exit(1)
+ except BaseException:
+ print("Test: {num_test} - FAIL".format(num_test=num_test))
+ sys.exit(1)
diff --git a/src/spdk/test/lvol/rpc_commands_lib.py b/src/spdk/test/lvol/rpc_commands_lib.py
new file mode 100644
index 00000000..0857c73d
--- /dev/null
+++ b/src/spdk/test/lvol/rpc_commands_lib.py
@@ -0,0 +1,241 @@
+import json
+import sys
+from uuid import UUID
+from subprocess import check_output, CalledProcessError
+
+
+class Spdk_Rpc(object):
+ def __init__(self, rpc_py):
+ self.rpc_py = rpc_py
+
+ def __getattr__(self, name):
+ def call(*args):
+ cmd = "{} {} {}".format(sys.executable, self.rpc_py, name)
+ for arg in args:
+ cmd += " {}".format(arg)
+ try:
+ output = check_output(cmd, shell=True)
+ return output.decode('ascii').rstrip('\n'), 0
+ except CalledProcessError as e:
+ print("ERROR: RPC Command {cmd} "
+ "execution failed:". format(cmd=cmd))
+ print("Failed command output:")
+ print(e.output)
+ return e.output.decode('ascii'), e.returncode
+ return call
+
+
+class Commands_Rpc(object):
+ def __init__(self, rpc_py):
+ self.rpc = Spdk_Rpc(rpc_py)
+
+ def check_get_bdevs_methods(self, uuid_bdev, bdev_size_mb, bdev_alias=""):
+ print("INFO: Check RPC COMMAND get_bdevs")
+ output = self.rpc.get_bdevs()[0]
+ json_value = json.loads(output)
+ for i in range(len(json_value)):
+ uuid_json = json_value[i]['name']
+ aliases = json_value[i]['aliases']
+
+ if uuid_bdev in [uuid_json]:
+ print("Info: UUID:{uuid} is found in RPC Command: "
+ "gets_bdevs response".format(uuid=uuid_bdev))
+ # Check if human-friendly alias is as expected
+ if bdev_alias and aliases:
+ if bdev_alias not in aliases:
+ print("ERROR: Expected bdev alias not found")
+ print("Expected: {name}".format(name=bdev_alias))
+ print("Actual: {aliases}".format(aliases=aliases))
+ return 1
+ # num_block and block_size have values in bytes
+ num_blocks = json_value[i]['num_blocks']
+ block_size = json_value[i]['block_size']
+ if num_blocks * block_size == bdev_size_mb * 1024 * 1024:
+ print("Info: Response get_bdevs command is "
+ "correct. Params: uuid_bdevs: {uuid}, bdev_size "
+ "{size}".format(uuid=uuid_bdev,
+ size=bdev_size_mb))
+ return 0
+ print("INFO: UUID:{uuid} or bdev_size:{bdev_size_mb} not found in "
+ "RPC COMMAND get_bdevs: "
+ "{json_value}".format(uuid=uuid_bdev, bdev_size_mb=bdev_size_mb,
+ json_value=json_value))
+ return 1
+
+ def check_get_lvol_stores(self, base_name, uuid, cluster_size=None, lvs_name=""):
+ print("INFO: RPC COMMAND get_lvol_stores")
+ json_value = self.get_lvol_stores()
+ if json_value:
+ for i in range(len(json_value)):
+ json_uuid = json_value[i]['uuid']
+ json_cluster = json_value[i]['cluster_size']
+ json_base_name = json_value[i]['base_bdev']
+ json_name = json_value[i]['name']
+
+ if base_name in json_base_name \
+ and uuid in json_uuid:
+ print("INFO: base_name:{base_name} is found in RPC "
+ "Command: get_lvol_stores "
+ "response".format(base_name=base_name))
+ print("INFO: UUID:{uuid} is found in RPC Command: "
+ "get_lvol_stores response".format(uuid=uuid))
+ if cluster_size:
+ if str(cluster_size) in str(json_cluster):
+ print("Info: Cluster size :{cluster_size} is found in RPC "
+ "Command: get_lvol_stores "
+ "response".format(cluster_size=cluster_size))
+ else:
+ print("ERROR: Wrong cluster size in lvol store")
+ print("Expected:".format(cluster_size))
+ print("Actual:".format(json_cluster))
+ return 1
+
+ # Also check name if param is provided:
+ if lvs_name:
+ if lvs_name not in json_name:
+ print("ERROR: Lvol store human-friendly name does not match")
+ print("Expected: {lvs_name}".format(lvs_name=lvs_name))
+ print("Actual: {name}".format(name=json_name))
+ return 1
+ return 0
+ print("FAILED: UUID: lvol store {uuid} on base_bdev: "
+ "{base_name} not found in get_lvol_stores()".format(uuid=uuid,
+ base_name=base_name))
+ return 1
+ else:
+ print("INFO: Lvol store not exist")
+ return 2
+ return 0
+
+ def construct_malloc_bdev(self, total_size, block_size):
+ print("INFO: RPC COMMAND construct_malloc_bdev")
+ output = self.rpc.construct_malloc_bdev(total_size, block_size)[0]
+ return output.rstrip('\n')
+
+ def construct_lvol_store(self, base_name, lvs_name, cluster_size=None):
+ print("INFO: RPC COMMAND construct_lvol_store")
+ if cluster_size:
+ output = self.rpc.construct_lvol_store(base_name,
+ lvs_name,
+ "-c {cluster_sz}".format(cluster_sz=cluster_size))[0]
+ else:
+ output = self.rpc.construct_lvol_store(base_name, lvs_name)[0]
+ return output.rstrip('\n')
+
+ def construct_lvol_bdev(self, uuid, lbd_name, size, thin=False):
+ print("INFO: RPC COMMAND construct_lvol_bdev")
+ try:
+ uuid_obj = UUID(uuid)
+ name_opt = "-u"
+ except ValueError:
+ name_opt = "-l"
+ thin_provisioned = ""
+ if thin:
+ thin_provisioned = "-t"
+ output = self.rpc.construct_lvol_bdev(name_opt, uuid, lbd_name, size, thin_provisioned)[0]
+ return output.rstrip('\n')
+
+ def destroy_lvol_store(self, uuid):
+ print("INFO: RPC COMMAND destroy_lvol_store")
+ try:
+ uuid_obj = UUID(uuid)
+ name_opt = "-u"
+ except ValueError:
+ name_opt = "-l"
+ output, rc = self.rpc.destroy_lvol_store(name_opt, uuid)
+ return rc
+
+ def delete_bdev(self, base_name):
+ print("INFO: RPC COMMAND delete_bdev")
+ output, rc = self.rpc.delete_bdev(base_name)
+ return rc
+
+ def delete_malloc_bdev(self, base_name):
+ print("INFO: RPC COMMAND delete_malloc_bdev")
+ output, rc = self.rpc.delete_malloc_bdev(base_name)
+ return rc
+
+ def destroy_lvol_bdev(self, bdev_name):
+ print("INFO: RPC COMMAND destroy_lvol_bdev")
+ output, rc = self.rpc.destroy_lvol_bdev(bdev_name)
+ return rc
+
+ def resize_lvol_bdev(self, uuid, new_size):
+ print("INFO: RPC COMMAND resize_lvol_bdev")
+ output, rc = self.rpc.resize_lvol_bdev(uuid, new_size)
+ return rc
+
+ def start_nbd_disk(self, bdev_name, nbd_name):
+ print("INFO: RPC COMMAND start_nbd_disk")
+ output, rc = self.rpc.start_nbd_disk(bdev_name, nbd_name)
+ return rc
+
+ def stop_nbd_disk(self, nbd_name):
+ print("INFO: RPC COMMAND stop_nbd_disk")
+ output, rc = self.rpc.stop_nbd_disk(nbd_name)
+ return rc
+
+ def get_lvol_stores(self, name=None):
+ print("INFO: RPC COMMAND get_lvol_stores")
+ if name:
+ output = json.loads(self.rpc.get_lvol_stores("-l", name)[0])
+ else:
+ output = json.loads(self.rpc.get_lvol_stores()[0])
+ return output
+
+ def get_lvol_bdevs(self):
+ print("INFO: RPC COMMAND get_bdevs; lvol bdevs only")
+ output = []
+ rpc_output = json.loads(self.rpc.get_bdevs()[0])
+ for bdev in rpc_output:
+ if bdev["product_name"] == "Logical Volume":
+ output.append(bdev)
+ return output
+
+ def get_lvol_bdev_with_name(self, name):
+ print("INFO: RPC COMMAND get_bdevs; lvol bdevs only")
+ rpc_output = json.loads(self.rpc.get_bdevs("-b", name)[0])
+ if len(rpc_output) > 0:
+ return rpc_output[0]
+
+ return None
+
+ def rename_lvol_store(self, old_name, new_name):
+ print("INFO: Renaming lvol store from {old} to {new}".format(old=old_name, new=new_name))
+ output, rc = self.rpc.rename_lvol_store(old_name, new_name)
+ return rc
+
+ def rename_lvol_bdev(self, old_name, new_name):
+ print("INFO: Renaming lvol bdev from {old} to {new}".format(old=old_name, new=new_name))
+ output, rc = self.rpc.rename_lvol_bdev(old_name, new_name)
+ return rc
+
+ def snapshot_lvol_bdev(self, bdev_name, snapshot_name):
+ print("INFO: RPC COMMAND snapshot_lvol_bdev")
+ output, rc = self.rpc.snapshot_lvol_bdev(bdev_name, snapshot_name)
+ return rc
+
+ def clone_lvol_bdev(self, snapshot_name, clone_name):
+ print("INFO: RPC COMMAND clone_lvol_bdev")
+ output, rc = self.rpc.clone_lvol_bdev(snapshot_name, clone_name)
+ return rc
+
+ def inflate_lvol_bdev(self, clone_name):
+ print("INFO: RPC COMMAND inflate_lvol_bdev")
+ output, rc = self.rpc.inflate_lvol_bdev(clone_name)
+ return rc
+
+ def decouple_parent_lvol_bdev(self, clone_name):
+ print("INFO: RPC COMMAND decouple_parent_lvol_bdev")
+ output, rc = self.rpc.decouple_parent_lvol_bdev(clone_name)
+ return rc
+
+ def construct_aio_bdev(self, aio_path, aio_name, aio_bs=""):
+ print("INFO: RPC COMMAND construct_aio_bdev")
+ output, rc = self.rpc.construct_aio_bdev(aio_path, aio_name, aio_bs)
+ return rc
+
+ def delete_aio_bdev(self, aio_name):
+ print("INFO: RPC COMMAND delete_aio_bdev")
+ output, rc = self.rpc.delete_aio_bdev(aio_name)
+ return rc
diff --git a/src/spdk/test/lvol/test_cases.py b/src/spdk/test/lvol/test_cases.py
new file mode 100644
index 00000000..ad85f529
--- /dev/null
+++ b/src/spdk/test/lvol/test_cases.py
@@ -0,0 +1,2591 @@
+import io
+import time
+import sys
+import random
+import signal
+import subprocess
+import pprint
+import socket
+import threading
+import os
+
+from errno import ESRCH
+from os import kill, path, unlink, path, listdir, remove
+from rpc_commands_lib import Commands_Rpc
+from time import sleep
+from uuid import uuid4
+
+
+MEGABYTE = 1024 * 1024
+
+
+current_fio_pid = -1
+
+
+def is_process_alive(pid):
+ try:
+ os.kill(pid, 0)
+ except Exception as e:
+ return 1
+
+ return 0
+
+
+def get_fio_cmd(nbd_disk, offset, size, rw, pattern, extra_params=""):
+ fio_template = "fio --name=fio_test --filename=%(file)s --offset=%(offset)s --size=%(size)s"\
+ " --rw=%(rw)s --direct=1 %(extra_params)s %(pattern)s"
+ pattern_template = ""
+ if pattern:
+ pattern_template = "--do_verify=1 --verify=pattern --verify_pattern=%s"\
+ " --verify_state_save=0" % pattern
+ fio_cmd = fio_template % {"file": nbd_disk, "offset": offset, "size": size,
+ "rw": rw, "pattern": pattern_template,
+ "extra_params": extra_params}
+
+ return fio_cmd
+
+
+def run_fio(fio_cmd, expected_ret_value):
+ global current_fio_pid
+ try:
+ proc = subprocess.Popen([fio_cmd], shell=True)
+ current_fio_pid = proc.pid
+ proc.wait()
+ rv = proc.returncode
+ except Exception as e:
+ print("ERROR: Fio test ended with unexpected exception.")
+ rv = 1
+ if expected_ret_value == rv:
+ return 0
+
+ if rv == 0:
+ print("ERROR: Fio test ended with unexpected success")
+ else:
+ print("ERROR: Fio test ended with unexpected failure")
+ return 1
+
+
+class FioThread(threading.Thread):
+ def __init__(self, nbd_disk, offset, size, rw, pattern, expected_ret_value,
+ extra_params=""):
+ super(FioThread, self).__init__()
+ self.fio_cmd = get_fio_cmd(nbd_disk, offset, size, rw, pattern,
+ extra_params=extra_params)
+ self.rv = 1
+ self.expected_ret_value = expected_ret_value
+
+ def run(self):
+ print("INFO: Starting fio")
+ self.rv = run_fio(self.fio_cmd, self.expected_ret_value)
+ print("INFO: Fio test finished")
+
+
+def test_counter():
+ '''
+ :return: the number of tests
+ '''
+ return ['test_case' in i for i in dir(TestCases)].count(True)
+
+
+def case_message(func):
+ def inner(*args, **kwargs):
+ test_name = {
+ 1: 'construct_lvs_positive',
+ 50: 'construct_logical_volume_positive',
+ 51: 'construct_multi_logical_volumes_positive',
+ 52: 'construct_lvol_bdev_using_name_positive',
+ 53: 'construct_lvol_bdev_duplicate_names_positive',
+ 100: 'construct_logical_volume_nonexistent_lvs_uuid',
+ 101: 'construct_lvol_bdev_on_full_lvol_store',
+ 102: 'construct_lvol_bdev_name_twice',
+ 150: 'resize_lvol_bdev_positive',
+ 200: 'resize_logical_volume_nonexistent_logical_volume',
+ 201: 'resize_logical_volume_with_size_out_of_range',
+ 250: 'destroy_lvol_store_positive',
+ 251: 'destroy_lvol_store_use_name_positive',
+ 252: 'destroy_lvol_store_with_lvol_bdev_positive',
+ 253: 'destroy_multi_logical_volumes_positive',
+ 254: 'destroy_after_resize_lvol_bdev_positive',
+ 255: 'delete_lvol_store_persistent_positive',
+ 300: 'destroy_lvol_store_nonexistent_lvs_uuid',
+ 301: 'delete_lvol_store_underlying_bdev',
+ 350: 'nested_destroy_logical_volume_negative',
+ 400: 'nested_construct_logical_volume_positive',
+ 450: 'construct_lvs_nonexistent_bdev',
+ 451: 'construct_lvs_on_bdev_twice',
+ 452: 'construct_lvs_name_twice',
+ 500: 'nested_construct_lvol_bdev_on_full_lvol_store',
+ 550: 'delete_bdev_positive',
+ 551: 'delete_lvol_bdev',
+ 552: 'destroy_lvol_store_with_clones',
+ 553: 'unregister_lvol_bdev',
+ 600: 'construct_lvol_store_with_cluster_size_max',
+ 601: 'construct_lvol_store_with_cluster_size_min',
+ 650: 'thin_provisioning_check_space',
+ 651: 'thin_provisioning_read_empty_bdev',
+ 652: 'thin_provisionind_data_integrity_test',
+ 653: 'thin_provisioning_resize',
+ 654: 'thin_overprovisioning',
+ 655: 'thin_provisioning_filling_disks_less_than_lvs_size',
+ 700: 'tasting_positive',
+ 701: 'tasting_lvol_store_positive',
+ 702: 'tasting_positive_with_different_lvol_store_cluster_size',
+ 750: 'snapshot_readonly',
+ 751: 'snapshot_compare_with_lvol_bdev',
+ 752: 'snapshot_during_io_traffic',
+ 753: 'snapshot_of_snapshot',
+ 754: 'clone_bdev_only',
+ 755: 'clone_writing_clone',
+ 756: 'clone_and_snapshot_consistency',
+ 757: 'clone_inflate',
+ 758: 'decouple_parent',
+ 759: 'decouple_parent_rw',
+ 800: 'rename_positive',
+ 801: 'rename_lvs_nonexistent',
+ 802: 'rename_lvs_EEXIST',
+ 803: 'rename_lvol_bdev_nonexistent',
+ 804: 'rename_lvol_bdev_EEXIST',
+ 10000: 'SIGTERM',
+ }
+ num = int(func.__name__.strip('test_case')[:])
+ print("************************************")
+ print("START TEST CASE {name}".format(name=test_name[num]))
+ print("************************************")
+ fail_count = func(*args, **kwargs)
+ print("************************************")
+ if not fail_count:
+ print("END TEST CASE {name} PASS".format(name=test_name[num]))
+ else:
+ print("END TEST CASE {name} FAIL".format(name=test_name[num]))
+ print("************************************")
+ return fail_count
+ return inner
+
+
+class TestCases(object):
+
+ def __init__(self, rpc_py, total_size, block_size, base_dir_path, app_path):
+ self.c = Commands_Rpc(rpc_py)
+ self.total_size = total_size
+ self.block_size = block_size
+ self.cluster_size = None
+ self.path = base_dir_path
+ self.app_path = app_path
+ self.lvs_name = "lvs_test"
+ self.lbd_name = "lbd_test"
+ self.vhost_config_path = path.join(path.dirname(sys.argv[0]), 'vhost.conf')
+
+ def _gen_lvs_uuid(self):
+ return str(uuid4())
+
+ def _gen_lvb_uuid(self):
+ return "_".join([str(uuid4()), str(random.randrange(9999999999))])
+
+ def compare_two_disks(self, disk1, disk2, expected_ret_value):
+ cmp_cmd = "cmp %s %s" % (disk1, disk2)
+ try:
+ process = subprocess.check_output(cmp_cmd, stderr=subprocess.STDOUT, shell=True)
+ rv = 0
+ except subprocess.CalledProcessError as ex:
+ rv = 1
+ except Exception as e:
+ print("ERROR: Cmp ended with unexpected exception.")
+ rv = 1
+
+ if expected_ret_value == rv:
+ return 0
+ elif rv == 0:
+ print("ERROR: Cmp ended with unexpected success")
+ else:
+ print("ERROR: Cmp ended with unexpected failure")
+
+ return 1
+
+ def run_fio_test(self, nbd_disk, offset, size, rw, pattern, expected_ret_value=0):
+ fio_cmd = get_fio_cmd(nbd_disk, offset, size, rw, pattern)
+ return run_fio(fio_cmd, expected_ret_value)
+
+ def _stop_vhost(self, pid_path):
+ with io.open(pid_path, 'r') as vhost_pid:
+ pid = int(vhost_pid.readline())
+ if pid:
+ try:
+ kill(pid, signal.SIGTERM)
+ for count in range(30):
+ sleep(1)
+ kill(pid, 0)
+ except OSError as err:
+ if err.errno == ESRCH:
+ pass
+ else:
+ return 1
+ else:
+ return 1
+ else:
+ return 1
+ return 0
+
+ def _start_vhost(self, vhost_path, pid_path):
+ subprocess.call("{app} -f "
+ "{pid} &".format(app=vhost_path,
+ pid=pid_path), shell=True)
+ for timeo in range(10):
+ if timeo == 9:
+ print("ERROR: Timeout on waiting for app start")
+ return 1
+ if not path.exists(pid_path):
+ print("Info: Waiting for PID file...")
+ sleep(1)
+ continue
+ else:
+ break
+
+ # Wait for RPC to open
+ sock = socket.socket(socket.AF_UNIX)
+ for timeo in range(30):
+ if timeo == 29:
+ print("ERROR: Timeout on waiting for RPC start")
+ return 1
+ try:
+ sock.connect("/var/tmp/spdk.sock")
+ break
+ except socket.error as e:
+ print("Info: Waiting for RPC Unix socket...")
+ sleep(1)
+ continue
+ else:
+ sock.close()
+ break
+
+ with io.open(pid_path, 'r') as vhost_pid:
+ pid = int(vhost_pid.readline())
+ if not pid:
+ return 1
+ return 0
+
+ def get_lvs_size(self, lvs_name="lvs_test"):
+ lvs = self.c.get_lvol_stores(lvs_name)[0]
+ return int(int(lvs['free_clusters'] * lvs['cluster_size']) / MEGABYTE)
+
+ def get_lvs_divided_size(self, split_num, lvs_name="lvs_test"):
+ # Actual size of lvol bdevs on creation is rounded up to multiple of cluster size.
+ # In order to avoid over provisioning, this function returns
+ # lvol store size in MB divided by split_num - rounded down to multiple of cluster size."
+ lvs = self.c.get_lvol_stores(lvs_name)[0]
+ return int(int(lvs['free_clusters'] / split_num) * lvs['cluster_size'] / MEGABYTE)
+
+ def get_lvs_cluster_size(self, lvs_name="lvs_test"):
+ lvs = self.c.get_lvol_stores(lvs_name)[0]
+ return int(int(lvs['cluster_size']) / MEGABYTE)
+
+ # positive tests
+ @case_message
+ def test_case1(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ self.c.destroy_lvol_store(uuid_store)
+ self.c.delete_malloc_bdev(base_name)
+ if self.c.check_get_lvol_stores("", "", "") == 1:
+ fail_count += 1
+ return fail_count
+
+ @case_message
+ def test_case50(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+
+ lvs_size = self.get_lvs_size()
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name,
+ lvs_size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev,
+ lvs_size)
+ self.c.destroy_lvol_bdev(uuid_bdev)
+ self.c.destroy_lvol_store(uuid_store)
+ self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case51(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ size = self.get_lvs_divided_size(4)
+
+ for j in range(2):
+ uuid_bdevs = []
+ for i in range(4):
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name + str(i),
+ size)
+ uuid_bdevs.append(uuid_bdev)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size)
+
+ for uuid_bdev in uuid_bdevs:
+ self.c.destroy_lvol_bdev(uuid_bdev)
+
+ self.c.destroy_lvol_store(uuid_store)
+ self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case52(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs_size = self.get_lvs_size()
+ uuid_bdev = self.c.construct_lvol_bdev(self.lvs_name,
+ self.lbd_name,
+ lvs_size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev,
+ lvs_size)
+
+ fail_count += self.c.destroy_lvol_bdev(uuid_bdev)
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ fail_count += self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case53(self):
+ base_name_1 = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ base_name_2 = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+
+ uuid_store_1 = self.c.construct_lvol_store(base_name_1,
+ self.lvs_name + "1")
+ uuid_store_2 = self.c.construct_lvol_store(base_name_2,
+ self.lvs_name + "2")
+ fail_count = self.c.check_get_lvol_stores(base_name_1, uuid_store_1,
+ self.cluster_size)
+ fail_count = self.c.check_get_lvol_stores(base_name_2, uuid_store_2,
+ self.cluster_size)
+
+ lvs_size = self.get_lvs_size(self.lvs_name + "1")
+ uuid_bdev_1 = self.c.construct_lvol_bdev(uuid_store_1,
+ self.lbd_name,
+ lvs_size)
+ uuid_bdev_2 = self.c.construct_lvol_bdev(uuid_store_2,
+ self.lbd_name,
+ lvs_size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev_1, lvs_size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev_2, lvs_size)
+
+ fail_count += self.c.destroy_lvol_bdev(uuid_bdev_1)
+ fail_count += self.c.destroy_lvol_bdev(uuid_bdev_2)
+ fail_count += self.c.destroy_lvol_store(uuid_store_1)
+ fail_count += self.c.destroy_lvol_store(uuid_store_2)
+ fail_count += self.c.delete_malloc_bdev(base_name_1)
+ fail_count += self.c.delete_malloc_bdev(base_name_2)
+ return fail_count
+
+ @case_message
+ def test_case100(self):
+ fail_count = 0
+ if self.c.construct_lvol_bdev(self._gen_lvs_uuid(),
+ self.lbd_name,
+ 32) == 0:
+ fail_count += 1
+ return fail_count
+
+ @case_message
+ def test_case101(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs_size = self.get_lvs_size()
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name,
+ lvs_size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev,
+ lvs_size)
+ if self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name + "_1",
+ lvs_size) == 0:
+ fail_count += 1
+
+ self.c.destroy_lvol_bdev(uuid_bdev)
+ self.c.destroy_lvol_store(uuid_store)
+ self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case102(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ size = self.get_lvs_size()
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name,
+ size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev,
+ size)
+ if self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name,
+ size) == 0:
+ fail_count += 1
+
+ self.c.destroy_lvol_bdev(uuid_bdev)
+ self.c.destroy_lvol_store(uuid_store)
+ self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case150(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ # size is equal to one quarter of size malloc bdev
+
+ size = self.get_lvs_divided_size(4)
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size)
+
+ # size is equal to half of size malloc bdev
+ size = self.get_lvs_divided_size(2)
+ self.c.resize_lvol_bdev(uuid_bdev, size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size)
+
+ # size is smaller by 1 cluster
+ size = (self.get_lvs_size() - self.get_lvs_cluster_size())
+ self.c.resize_lvol_bdev(uuid_bdev, size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size)
+
+ # size is equal 0 MiB
+ size = 0
+ self.c.resize_lvol_bdev(uuid_bdev, size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size)
+
+ self.c.destroy_lvol_bdev(uuid_bdev)
+ self.c.destroy_lvol_store(uuid_store)
+ self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case200(self):
+ fail_count = 0
+ if self.c.resize_lvol_bdev(self._gen_lvb_uuid(), 16) == 0:
+ fail_count += 1
+ return fail_count
+
+ @case_message
+ def test_case201(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs_size = self.get_lvs_size()
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name,
+ lvs_size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev,
+ lvs_size)
+ if self.c.resize_lvol_bdev(uuid_bdev, self.total_size + 1) == 0:
+ fail_count += 1
+
+ self.c.destroy_lvol_bdev(uuid_bdev)
+ self.c.destroy_lvol_store(uuid_store)
+ self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case250(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ self.c.destroy_lvol_store(uuid_store)
+ if self.c.check_get_lvol_stores("", "", "") == 1:
+ fail_count += 1
+ self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case251(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ fail_count += self.c.destroy_lvol_store(self.lvs_name)
+ if self.c.check_get_lvol_stores("", "", "") == 1:
+ fail_count += 1
+ fail_count += self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case252(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs_size = self.get_lvs_size()
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name,
+ lvs_size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev,
+ lvs_size)
+ if self.c.destroy_lvol_store(uuid_store) != 0:
+ fail_count += 1
+
+ if self.c.check_get_lvol_stores("", "", "") == 1:
+ fail_count += 1
+ self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case253(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ size = self.get_lvs_divided_size(4)
+
+ for i in range(4):
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name + str(i),
+ size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size)
+
+ self.c.destroy_lvol_store(uuid_store)
+ if self.c.check_get_lvol_stores("", "", "") == 1:
+ fail_count += 1
+ self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case254(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ size = self.get_lvs_divided_size(4)
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name,
+ size)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size)
+
+ sz = size + 4
+ self.c.resize_lvol_bdev(uuid_bdev, sz)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, sz)
+ sz = size * 2
+ self.c.resize_lvol_bdev(uuid_bdev, sz)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, sz)
+ sz = size * 3
+ self.c.resize_lvol_bdev(uuid_bdev, sz)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, sz)
+ sz = (size * 4) - 4
+ self.c.resize_lvol_bdev(uuid_bdev, sz)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, sz)
+ sz = 0
+ self.c.resize_lvol_bdev(uuid_bdev, sz)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, sz)
+
+ self.c.destroy_lvol_store(uuid_store)
+ if self.c.check_get_lvol_stores("", "", "") == 1:
+ fail_count += 1
+ self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case255(self):
+ base_path = path.dirname(sys.argv[0])
+ base_name = "aio_bdev0"
+ aio_bdev0 = path.join(base_path, "aio_bdev_0")
+ self.c.construct_aio_bdev(aio_bdev0, base_name, 4096)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ if self.c.destroy_lvol_store(self.lvs_name) != 0:
+ fail_count += 1
+
+ self.c.delete_aio_bdev(base_name)
+ self.c.construct_aio_bdev(aio_bdev0, base_name, 4096)
+ # wait 1 second to allow time for lvolstore tasting
+ sleep(1)
+
+ ret_value = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ if ret_value == 0:
+ fail_count += 1
+ self.c.delete_aio_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case300(self):
+ fail_count = 0
+ if self.c.destroy_lvol_store(self._gen_lvs_uuid()) == 0:
+ fail_count += 1
+ return fail_count
+
+ @case_message
+ def test_case301(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+
+ if self.c.delete_malloc_bdev(base_name) != 0:
+ fail_count += 1
+
+ if self.c.destroy_lvol_store(uuid_store) == 0:
+ fail_count += 1
+
+ return fail_count
+
+ def test_case350(self):
+ print("Test of this feature not yet implemented.")
+ pass
+ return 0
+
+ def test_case400(self):
+ print("Test of this feature not yet implemented.")
+ pass
+ return 0
+
+ # negative tests
+ @case_message
+ def test_case450(self):
+ fail_count = 0
+ bad_bdev_id = random.randrange(999999999)
+ if self.c.construct_lvol_store(bad_bdev_id,
+ self.lvs_name,
+ self.cluster_size) == 0:
+ fail_count += 1
+ return fail_count
+
+ @case_message
+ def test_case451(self):
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ if self.c.construct_lvol_store(base_name,
+ self.lvs_name) == 0:
+ fail_count += 1
+ self.c.destroy_lvol_store(uuid_store)
+ self.c.delete_malloc_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case452(self):
+ fail_count = 0
+ base_name_1 = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ base_name_2 = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store_1 = self.c.construct_lvol_store(base_name_1,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name_1,
+ uuid_store_1,
+ self.cluster_size)
+ if self.c.construct_lvol_store(base_name_2,
+ self.lvs_name) == 0:
+ fail_count += 1
+
+ fail_count += self.c.destroy_lvol_store(uuid_store_1)
+ fail_count += self.c.delete_malloc_bdev(base_name_1)
+ fail_count += self.c.delete_malloc_bdev(base_name_2)
+
+ return fail_count
+
+ def test_case500(self):
+ """
+ nested_construct_lvol_bdev_on_full_lvol_store
+
+ Negative test for constructing a new nested lvol bdev.
+ Call construct_lvol_bdev on a full lvol store.
+ """
+ # Steps:
+ # - create a malloc bdev
+ # - construct_lvol_store on created malloc bdev
+ # - check correct uuid values in response get_lvol_stores command
+ # - construct_lvol_bdev on correct lvs_uuid and size is
+ # equal to size malloc bdev
+ # - construct nested lvol store on previously created lvol_bdev
+ # - check correct uuid values in response get_lvol_stores command
+ # - construct nested lvol bdev on previously created nested lvol store
+ # and size is equal to size lvol store
+ # - try construct another lvol bdev as in previous step; this call should fail
+ # as nested lvol store space is already claimed by lvol bdev
+ # - delete nested lvol bdev
+ # - destroy nested lvol_store
+ # - delete base lvol bdev
+ # - delete base lvol store
+ # - delete malloc bdev
+ #
+ # Expected result:
+ # - second construct_lvol_bdev call on nested lvol store return code != 0
+ # - EEXIST response printed to stdout
+ # - no other operation fails
+ print("Test of this feature not yet implemented.")
+ pass
+ return 0
+
+ @case_message
+ def test_case550(self):
+ """
+ delete_bdev_positive
+
+ Positive test for deleting malloc bdev.
+ Call construct_lvol_store with correct base bdev name.
+ """
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Construct_lvol_store on correct, exisitng malloc bdev
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ # Check correct uuid values in response get_lvol_stores command
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ # Delete malloc bdev
+ self.c.delete_malloc_bdev(base_name)
+ # Check response get_lvol_stores command
+ if self.c.check_get_lvol_stores("", "", "") == 1:
+ fail_count += 1
+
+ # Expected result:
+ # - get_lvol_stores: response should be of no value after destroyed lvol store
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case551(self):
+ """
+ destroy_lvol_bdev_ordering
+
+ Test for destroying lvol bdevs in particular order.
+ Check destroying wrong one is not possible and returns error.
+ """
+
+ fail_count = 0
+ snapshot_name = "snapshot"
+ clone_name = "clone"
+
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Construct_lvol_store on correct, exisitng malloc bdev
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name,
+ self.cluster_size)
+ # Check correct uuid values in response get_lvol_stores command
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs = self.c.get_lvol_stores()
+ size = int(int(lvs[0]['free_clusters'] * lvs[0]['cluster_size']) / 4 / MEGABYTE)
+
+ # Construct thin provisioned lvol bdev
+ uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name, size, thin=True)
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0)
+
+ # Create snapshot of thin provisioned lvol bdev
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+
+ # Create clone of snapshot and check if it ends with success
+ fail_count += self.c.clone_lvol_bdev(self.lvs_name + "/" + snapshot_name, clone_name)
+ clone_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name)
+
+ # Try to destroy snapshot with clones and check if it fails
+ ret_value = self.c.destroy_lvol_bdev(snapshot_bdev['name'])
+ if ret_value == 0:
+ print("ERROR: Delete snapshot should fail but didn't")
+ fail_count += 1
+
+ # Destroy clone and then snapshot
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name'])
+ fail_count += self.c.destroy_lvol_bdev(clone_bdev['name'])
+ fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name'])
+
+ # Destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+
+ # Check response get_lvol_stores command
+ if self.c.check_get_lvol_stores("", "", "") == 1:
+ fail_count += 1
+
+ # Delete malloc bdev
+ self.c.delete_malloc_bdev(base_name)
+ # Expected result:
+ # - get_lvol_stores: response should be of no value after destroyed lvol store
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case552(self):
+ """
+ destroy_lvol_store_with_clones
+
+ Test for destroying lvol store with clones present,
+ without removing them first.
+ """
+
+ fail_count = 0
+ snapshot_name = "snapshot"
+ snapshot_name2 = "snapshot2"
+ clone_name = "clone"
+
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Construct_lvol_store on correct, exisitng malloc bdev
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name,
+ self.cluster_size)
+ # Check correct uuid values in response get_lvol_stores command
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs = self.c.get_lvol_stores()
+ size = int(int(lvs[0]['free_clusters'] * lvs[0]['cluster_size']) / 4 / MEGABYTE)
+
+ # Create lvol bdev, snapshot it, then clone it and then snapshot the clone
+ uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, size, thin=True)
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0)
+
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+
+ fail_count += self.c.clone_lvol_bdev(self.lvs_name + "/" + snapshot_name, clone_name)
+ clone_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name)
+
+ fail_count += self.c.snapshot_lvol_bdev(clone_bdev['name'], snapshot_name2)
+ snapshot_bdev2 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name2)
+
+ # Try to destroy snapshots with clones and check if it fails
+ ret_value = self.c.destroy_lvol_bdev(snapshot_bdev['name'])
+ if ret_value == 0:
+ print("ERROR: Delete snapshot should fail but didn't")
+ fail_count += 1
+ ret_value = self.c.destroy_lvol_bdev(snapshot_bdev2['name'])
+ if ret_value == 0:
+ print("ERROR: Delete snapshot should fail but didn't")
+ fail_count += 1
+
+ # Destroy lvol store without deleting lvol bdevs
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+
+ # Check response get_lvol_stores command
+ if self.c.check_get_lvol_stores("", "", "") == 1:
+ fail_count += 1
+
+ # Delete malloc bdev
+ self.c.delete_malloc_bdev(base_name)
+ # Expected result:
+ # - get_lvol_stores: response should be of no value after destroyed lvol store
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case553(self):
+ """
+ unregister_lvol_bdev
+
+ Test for unregistering the lvol bdevs.
+ Removing malloc bdev under an lvol store triggers unregister of
+ all lvol bdevs. Verify it with clones present.
+ """
+
+ fail_count = 0
+ snapshot_name = "snapshot"
+ snapshot_name2 = "snapshot2"
+ clone_name = "clone"
+
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Construct_lvol_store on correct, exisitng malloc bdev
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name,
+ self.cluster_size)
+ # Check correct uuid values in response get_lvol_stores command
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs = self.c.get_lvol_stores()
+ size = int(int(lvs[0]['free_clusters'] * lvs[0]['cluster_size']) / 4 / MEGABYTE)
+
+ # Create lvol bdev, snapshot it, then clone it and then snapshot the clone
+ uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store, self.lbd_name, size, thin=True)
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0)
+
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+
+ fail_count += self.c.clone_lvol_bdev(self.lvs_name + "/" + snapshot_name, clone_name)
+ clone_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name)
+
+ fail_count += self.c.snapshot_lvol_bdev(clone_bdev['name'], snapshot_name2)
+ snapshot_bdev2 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name2)
+
+ # Delete malloc bdev
+ self.c.delete_malloc_bdev(base_name)
+
+ # Check response get_lvol_stores command
+ if self.c.check_get_lvol_stores("", "", "") == 1:
+ fail_count += 1
+
+ # Expected result:
+ # - get_lvol_stores: response should be of no value after destroyed lvol store
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case600(self):
+ """
+ construct_lvol_store_with_cluster_size_max
+
+ Negative test for constructing a new lvol store.
+ Call construct_lvol_store with cluster size is equal malloc bdev size + 1B.
+ """
+ fail_count = 0
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Construct_lvol_store on correct, exisitng malloc bdev and cluster size equal
+ # malloc bdev size in bytes + 1B
+ lvol_uuid = self.c.construct_lvol_store(base_name,
+ self.lvs_name,
+ (self.total_size * 1024 * 1024) + 1) == 0
+ if self.c.check_get_lvol_stores(base_name, lvol_uuid) == 0:
+ fail_count += 1
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - return code != 0
+ # - Error code response printed to stdout
+ return fail_count
+
+ @case_message
+ def test_case601(self):
+ """
+ construct_lvol_store_with_cluster_size_min
+
+ Negative test for constructing a new lvol store.
+ Call construct_lvol_store with cluster size smaller than minimal value of 8192.
+ """
+ fail_count = 0
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Try construct lvol store on malloc bdev with cluster size 8191
+ lvol_uuid = self.c.construct_lvol_store(base_name, self.lvs_name, 8191)
+ # Verify that lvol store was not created
+ if self.c.check_get_lvol_stores(base_name, lvol_uuid) == 0:
+ fail_count += 1
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - construct lvol store return code != 0
+ # - Error code response printed to stdout
+ return fail_count
+
+ @case_message
+ def test_case650(self):
+ """
+ thin_provisioning_check_space
+
+ Check if free clusters number on lvol store decreases
+ if we write to created thin provisioned lvol bdev
+ """
+ # create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # create lvol store on mamloc bdev
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs = self.c.get_lvol_stores(self.lvs_name)[0]
+ free_clusters_start = int(lvs['free_clusters'])
+ bdev_size = self.get_lvs_size()
+ # create thin provisioned lvol bdev with size equals to lvol store free space
+ bdev_name = self.c.construct_lvol_bdev(uuid_store, self.lbd_name,
+ bdev_size, thin=True)
+ lvs = self.c.get_lvol_stores(self.lvs_name)[0]
+ free_clusters_create_lvol = int(lvs['free_clusters'])
+ # check and save number of free clusters for lvol store
+ if free_clusters_start != free_clusters_create_lvol:
+ fail_count += 1
+ lvol_bdev = self.c.get_lvol_bdev_with_name(bdev_name)
+ nbd_name = "/dev/nbd0"
+ fail_count += self.c.start_nbd_disk(bdev_name, nbd_name)
+
+ size = int(lvs['cluster_size'])
+ # write data (lvs cluster size) to created lvol bdev starting from offset 0.
+ fail_count += self.run_fio_test("/dev/nbd0", 0, size, "write", "0xcc")
+ lvs = self.c.get_lvol_stores(self.lvs_name)[0]
+ free_clusters_first_fio = int(lvs['free_clusters'])
+ # check that free clusters on lvol store was decremented by 1
+ if free_clusters_start != free_clusters_first_fio + 1:
+ fail_count += 1
+
+ size = int(lvs['cluster_size'])
+ # calculate size of one and half cluster
+ offset = int((int(lvol_bdev['num_blocks']) * int(lvol_bdev['block_size']) /
+ free_clusters_create_lvol) * 1.5)
+ # write data (lvs cluster size) to lvol bdev with offset set to one and half of cluster size
+ fail_count += self.run_fio_test(nbd_name, offset, size, "write", "0xcc")
+ lvs = self.c.get_lvol_stores(self.lvs_name)[0]
+ free_clusters_second_fio = int(lvs['free_clusters'])
+ # check that free clusters on lvol store was decremented by 2
+ if free_clusters_start != free_clusters_second_fio + 3:
+ fail_count += 1
+
+ size = (free_clusters_create_lvol - 3) * int(lvs['cluster_size'])
+ offset = int(int(lvol_bdev['num_blocks']) * int(lvol_bdev['block_size']) /
+ free_clusters_create_lvol * 3)
+ # write data to lvol bdev to the end of its size
+ fail_count += self.run_fio_test(nbd_name, offset, size, "write", "0xcc")
+ lvs = self.c.get_lvol_stores(self.lvs_name)[0]
+ free_clusters_third_fio = int(lvs['free_clusters'])
+ # check that lvol store free clusters number equals to 0
+ if free_clusters_third_fio != 0:
+ fail_count += 1
+
+ fail_count += self.c.stop_nbd_disk(nbd_name)
+ # destroy thin provisioned lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name'])
+ lvs = self.c.get_lvol_stores(self.lvs_name)[0]
+ free_clusters_end = int(lvs['free_clusters'])
+ # check that saved number of free clusters equals to current free clusters
+ if free_clusters_start != free_clusters_end:
+ fail_count += 1
+ # destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ # destroy malloc bdev
+ fail_count += self.c.delete_malloc_bdev(base_name)
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case651(self):
+ """
+ thin_provisioning_read_empty_bdev
+
+ Check if we can create thin provisioned bdev on empty lvol store
+ and check if we can read from this device and it returns zeroes.
+ """
+ # create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # construct lvol store on malloc bdev
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs = self.c.get_lvol_stores(self.lvs_name)[0]
+ free_clusters_start = int(lvs['free_clusters'])
+ lbd_name0 = self.lbd_name + str("0")
+ lbd_name1 = self.lbd_name + str("1")
+ # calculate bdev size in megabytes
+ bdev_size = self.get_lvs_size()
+ # create thick provisioned lvol bvdev with size equal to lvol store
+ bdev_name0 = self.c.construct_lvol_bdev(uuid_store, lbd_name0,
+ bdev_size, thin=False)
+ # create thin provisioned lvol bdev with the same size
+ bdev_name1 = self.c.construct_lvol_bdev(uuid_store, lbd_name1,
+ bdev_size, thin=True)
+ lvol_bdev0 = self.c.get_lvol_bdev_with_name(bdev_name0)
+ lvol_bdev1 = self.c.get_lvol_bdev_with_name(bdev_name1)
+ nbd_name0 = "/dev/nbd0"
+ fail_count += self.c.start_nbd_disk(lvol_bdev0['name'], nbd_name0)
+ nbd_name1 = "/dev/nbd1"
+ fail_count += self.c.start_nbd_disk(lvol_bdev1['name'], nbd_name1)
+
+ size = bdev_size * MEGABYTE
+ # fill the whole thick provisioned lvol bdev
+ fail_count += self.run_fio_test(nbd_name0, 0, size, "write", False)
+
+ size = bdev_size * MEGABYTE
+ # perform read operations on thin provisioned lvol bdev
+ # and check if they return zeroes
+ fail_count += self.run_fio_test(nbd_name1, 0, size, "read", "0x00")
+
+ fail_count += self.c.stop_nbd_disk(nbd_name0)
+ fail_count += self.c.stop_nbd_disk(nbd_name1)
+ # destroy thin provisioned lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev0['name'])
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev1['name'])
+ # destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ # destroy malloc bdev
+ fail_count += self.c.delete_malloc_bdev(base_name)
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case652(self):
+ """
+ thin_provisioning_data_integrity_test
+
+ Check if data written to thin provisioned lvol bdev
+ were properly written (fio test with verification).
+ """
+ # create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # construct lvol store on malloc bdev
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs = self.c.get_lvol_stores(self.lvs_name)[0]
+ free_clusters_start = int(lvs['free_clusters'])
+ bdev_size = self.get_lvs_size()
+ # construct thin provisioned lvol bdev with size equal to lvol store
+ bdev_name = self.c.construct_lvol_bdev(uuid_store, self.lbd_name,
+ bdev_size, thin=True)
+
+ lvol_bdev = self.c.get_lvol_bdev_with_name(bdev_name)
+ nbd_name = "/dev/nbd0"
+ fail_count += self.c.start_nbd_disk(lvol_bdev['name'], nbd_name)
+ size = bdev_size * MEGABYTE
+ # on the whole lvol bdev perform write operation with verification
+ fail_count += self.run_fio_test(nbd_name, 0, size, "write", "0xcc")
+
+ fail_count += self.c.stop_nbd_disk(nbd_name)
+ # destroy thin provisioned lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name'])
+ # destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ # destroy malloc bdev
+ fail_count += self.c.delete_malloc_bdev(base_name)
+ # Expected result:
+ # - calls successful, return code = 0
+ # - verification ends with success
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case653(self):
+ """
+ thin_provisioning_resize
+
+ Check thin provisioned bdev resize. To be implemented.
+ """
+ # TODO
+ # create malloc bdev
+ # construct lvol store on malloc bdev
+ # construct thin provisioned lvol bdevs on created lvol store
+ # with size equal to 50% of lvol store
+ # fill all free space of lvol bdev with data
+ # save number of free clusters for lvs
+ # resize bdev to full size of lvs
+ # check if bdev size changed (total_data_clusters*cluster_size
+ # equal to num_blocks*block_size)
+ # check if free_clusters on lvs remain unaffected
+ # perform write operation with verification
+ # to newly created free space of lvol bdev
+ # resize bdev to 30M and check if it ended with success
+ # check if free clusters on lvs equals to saved counter
+ # destroy thin provisioned lvol bdev
+ # destroy lvol store
+ # destroy malloc bdev
+ fail_count = 0
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case654(self):
+ """
+ thin_overprovisioning
+
+ Create two thin provisioned lvol bdevs with max size
+ and check if writting more than total size of lvol store
+ will cause failures.
+ """
+ # create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # construct lvol store on malloc bdev
+ uuid_store = self.c.construct_lvol_store(base_name, self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs = self.c.get_lvol_stores(self.lvs_name)[0]
+ free_clusters_start = int(lvs['free_clusters'])
+ lbd_name0 = self.lbd_name + str("0")
+ lbd_name1 = self.lbd_name + str("1")
+ bdev_size = self.get_lvs_size()
+ # construct two thin provisioned lvol bdevs on created lvol store
+ # with size equals to free lvs size
+ bdev_name0 = self.c.construct_lvol_bdev(uuid_store, lbd_name0,
+ bdev_size, thin=True)
+ bdev_name1 = self.c.construct_lvol_bdev(uuid_store, lbd_name1,
+ bdev_size, thin=True)
+
+ lvs = self.c.get_lvol_stores(self.lvs_name)[0]
+ free_clusters_create_lvol = int(lvs['free_clusters'])
+ if free_clusters_start != free_clusters_create_lvol:
+ fail_count += 1
+ lvol_bdev0 = self.c.get_lvol_bdev_with_name(bdev_name0)
+ lvol_bdev1 = self.c.get_lvol_bdev_with_name(bdev_name1)
+
+ nbd_name0 = "/dev/nbd0"
+ nbd_name1 = "/dev/nbd1"
+ fail_count += self.c.start_nbd_disk(lvol_bdev0['name'], nbd_name0)
+ fail_count += self.c.start_nbd_disk(lvol_bdev1['name'], nbd_name1)
+
+ size = "75%"
+ # fill first bdev to 75% of its space with specific pattern
+ fail_count += self.run_fio_test(nbd_name0, 0, size, "write", "0xcc")
+
+ size = "75%"
+ # fill second bdev up to 75% of its space
+ # check that error message occured while filling second bdev with data
+ fail_count += self.run_fio_test(nbd_name1, 0, size, "write", "0xee",
+ expected_ret_value=1)
+
+ size = "75%"
+ # check if data on first disk stayed unchanged
+ fail_count += self.run_fio_test(nbd_name0, 0, size, "read", "0xcc")
+
+ size = "25%"
+ offset = "75%"
+ fail_count += self.run_fio_test(nbd_name0, offset, size, "read", "0x00")
+
+ fail_count += self.c.stop_nbd_disk(nbd_name0)
+ fail_count += self.c.stop_nbd_disk(nbd_name1)
+ # destroy thin provisioned lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev0['name'])
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev1['name'])
+ # destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ # destroy malloc bdev
+ fail_count += self.c.delete_malloc_bdev(base_name)
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case655(self):
+ """
+ thin_provisioning_filling_disks_less_than_lvs_size
+
+ Check if writing to two thin provisioned lvol bdevs
+ less than total size of lvol store will end with success
+ """
+ # create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # construct lvol store on malloc bdev
+ uuid_store = self.c.construct_lvol_store(base_name, self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs = self.c.get_lvol_stores(self.lvs_name)[0]
+ free_clusters_start = int(lvs['free_clusters'])
+ lbd_name0 = self.lbd_name + str("0")
+ lbd_name1 = self.lbd_name + str("1")
+ lvs_size = self.get_lvs_size()
+ bdev_size = int(lvs_size * 0.7)
+ # construct two thin provisioned lvol bdevs on created lvol store
+ # with size equal to 70% of lvs size
+ bdev_name0 = self.c.construct_lvol_bdev(uuid_store, lbd_name0,
+ bdev_size, thin=True)
+ bdev_name1 = self.c.construct_lvol_bdev(uuid_store, lbd_name1,
+ bdev_size, thin=True)
+
+ lvol_bdev0 = self.c.get_lvol_bdev_with_name(bdev_name0)
+ lvol_bdev1 = self.c.get_lvol_bdev_with_name(bdev_name1)
+ # check if bdevs are available and size of every disk is equal to 70% of lvs size
+ nbd_name0 = "/dev/nbd0"
+ nbd_name1 = "/dev/nbd1"
+ fail_count += self.c.start_nbd_disk(lvol_bdev0['name'], nbd_name0)
+ fail_count += self.c.start_nbd_disk(lvol_bdev1['name'], nbd_name1)
+ size = int(int(lvol_bdev0['num_blocks']) * int(lvol_bdev0['block_size']) * 0.7)
+ # fill first disk with 70% of its size
+ # check if operation didn't fail
+ fail_count += self.run_fio_test(nbd_name0, 0, size, "write", "0xcc")
+ size = int(int(lvol_bdev1['num_blocks']) * int(lvol_bdev1['block_size']) * 0.7)
+ # fill second disk also with 70% of its size
+ # check if operation didn't fail
+ fail_count += self.run_fio_test(nbd_name1, 0, size, "write", "0xee")
+
+ fail_count += self.c.stop_nbd_disk(nbd_name0)
+ fail_count += self.c.stop_nbd_disk(nbd_name1)
+ # destroy thin provisioned lvol bdevs
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev0['name'])
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev1['name'])
+ # destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ # destroy malloc bdev
+ fail_count += self.c.delete_malloc_bdev(base_name)
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case700(self):
+ """
+ tasting_positive
+
+ Positive test for tasting a multi lvol bdev configuration.
+ Create a lvol store with some lvol bdevs on aio bdev and restart vhost app.
+ After restarting configuration should be automatically loaded and should be exactly
+ the same as before restarting.
+ Check that running configuration can be modified after restarting and tasting.
+ """
+ fail_count = 0
+ uuid_bdevs = []
+ base_name = "aio_bdev0"
+
+ base_path = path.dirname(sys.argv[0])
+ vhost_path = path.join(self.app_path, 'vhost')
+ pid_path = path.join(base_path, 'vhost.pid')
+ aio_bdev0 = path.join(base_path, 'aio_bdev_0')
+
+ self.c.construct_aio_bdev(aio_bdev0, base_name, 4096)
+ # Create initial configuration on running vhost instance
+ # create lvol store, create 5 bdevs
+ # save info of all lvs and lvol bdevs
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name,
+ uuid_store,
+ self.cluster_size)
+
+ size = self.get_lvs_divided_size(10)
+
+ for i in range(5):
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name + str(i),
+ size)
+ uuid_bdevs.append(uuid_bdev)
+ # Using get_bdevs command verify lvol bdevs were correctly created
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size)
+
+ old_bdevs = sorted(self.c.get_lvol_bdevs(), key=lambda x: x["name"])
+ old_stores = self.c.get_lvol_stores()
+
+ # Shut down vhost instance and restart with new instance
+ fail_count += self._stop_vhost(pid_path)
+ remove(pid_path)
+ if self._start_vhost(vhost_path, pid_path) != 0:
+ fail_count += 1
+ return fail_count
+
+ self.c.construct_aio_bdev(aio_bdev0, base_name, 4096)
+ # Check if configuration was properly loaded after tasting
+ # get all info all lvs and lvol bdevs, compare with previous info
+ new_bdevs = sorted(self.c.get_lvol_bdevs(), key=lambda x: x["name"])
+ new_stores = self.c.get_lvol_stores()
+
+ if old_stores != new_stores:
+ fail_count += 1
+ print("ERROR: old and loaded lvol store is not the same")
+ print("DIFF:")
+ print(old_stores)
+ print(new_stores)
+
+ if len(old_bdevs) != len(new_bdevs):
+ fail_count += 1
+ print("ERROR: old and loaded lvol bdev list count is not equal")
+
+ for o, n in zip(old_bdevs, new_bdevs):
+ if o != n:
+ fail_count += 1
+ print("ERROR: old and loaded lvol bdev is not the same")
+ print("DIFF:")
+ pprint.pprint([o, n])
+
+ if fail_count != 0:
+ self.c.delete_aio_bdev(aio_bdev0)
+ return fail_count
+
+ # Try modifying loaded configuration
+ # Add some lvol bdevs to existing lvol store then
+ # remove all lvol configuration and re-create it again
+ for i in range(5, 10):
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name + str(i),
+ size)
+ uuid_bdevs.append(uuid_bdev)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size)
+
+ for uuid_bdev in uuid_bdevs:
+ self.c.destroy_lvol_bdev(uuid_bdev)
+
+ if self.c.destroy_lvol_store(uuid_store) != 0:
+ fail_count += 1
+
+ uuid_bdevs = []
+
+ # Create lvol store on aio bdev, create ten lvol bdevs on lvol store and
+ # verify all configuration call results
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name,
+ uuid_store,
+ self.cluster_size)
+
+ for i in range(10):
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name + str(i),
+ size)
+ uuid_bdevs.append(uuid_bdev)
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size)
+
+ # Destroy lvol store
+ if self.c.destroy_lvol_store(uuid_store) != 0:
+ fail_count += 1
+
+ self.c.delete_aio_bdev(base_name)
+
+ return fail_count
+
+ @case_message
+ def test_case701(self):
+ """
+ tasting_lvol_store_positive
+
+ Positive test for tasting lvol store.
+ """
+ base_path = path.dirname(sys.argv[0])
+ aio_bdev0 = path.join(base_path, 'aio_bdev_0')
+ base_name = "aio_bdev0"
+
+ self.c.construct_aio_bdev(aio_bdev0, base_name, 4096)
+ # construct lvol store on aio bdev
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+
+ self.c.delete_aio_bdev(base_name)
+ self.c.construct_aio_bdev(aio_bdev0, base_name, 4096)
+ # wait 1 second to allow time for lvolstore tasting
+ sleep(1)
+ # check if lvol store still exists in vhost configuration
+ if self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size) != 0:
+ fail_count += 1
+ # destroy lvol store from aio bdev
+ if self.c.destroy_lvol_store(uuid_store) != 0:
+ fail_count += 1
+
+ self.c.delete_aio_bdev(base_name)
+ return fail_count
+
+ @case_message
+ def test_case702(self):
+ """
+ tasting_positive_with_different_lvol_store_cluster_size
+
+ Positive test for tasting a multi lvol bdev configuration.
+ Create two lvol stores with different cluster sizes with some lvol bdevs on aio
+ drive and restart vhost app.
+ After restarting configuration should be automatically loaded and should be exactly
+ the same as before restarting.
+ """
+ fail_count = 0
+ uuid_bdevs = []
+ cluster_size_1M = MEGABYTE
+ cluster_size_32M = 32 * MEGABYTE
+ base_name_1M = "aio_bdev0"
+ base_name_32M = "aio_bdev1"
+
+ base_path = path.dirname(sys.argv[0])
+ vhost_path = path.join(self.app_path, 'vhost')
+ pid_path = path.join(base_path, 'vhost.pid')
+ aio_bdev0 = path.join(base_path, 'aio_bdev_0')
+ aio_bdev1 = path.join(base_path, 'aio_bdev_1')
+
+ self.c.construct_aio_bdev(aio_bdev0, base_name_1M, 4096)
+ self.c.construct_aio_bdev(aio_bdev1, base_name_32M, 4096)
+
+ # Create initial configuration on running vhost instance
+ # create lvol store, create 5 bdevs
+ # save info of all lvs and lvol bdevs
+ uuid_store_1M = self.c.construct_lvol_store(base_name_1M,
+ self.lvs_name + "_1M",
+ cluster_size_1M)
+
+ fail_count += self.c.check_get_lvol_stores(base_name_1M,
+ uuid_store_1M,
+ cluster_size_1M)
+
+ uuid_store_32M = self.c.construct_lvol_store(base_name_32M,
+ self.lvs_name + "_32M",
+ cluster_size_32M)
+
+ fail_count += self.c.check_get_lvol_stores(base_name_32M,
+ uuid_store_32M,
+ cluster_size_32M)
+
+ # size = approx 20% of total aio bdev size
+ size_1M = self.get_lvs_divided_size(5, self.lvs_name + "_1M")
+ size_32M = self.get_lvs_divided_size(5, self.lvs_name + "_32M")
+
+ for i in range(5):
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store_1M,
+ self.lbd_name + str(i) + "_1M",
+ size_1M)
+ uuid_bdevs.append(uuid_bdev)
+ # Using get_bdevs command verify lvol bdevs were correctly created
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size_1M)
+
+ for i in range(5):
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store_32M,
+ self.lbd_name + str(i) + "_32M",
+ size_32M)
+ uuid_bdevs.append(uuid_bdev)
+ # Using get_bdevs command verify lvol bdevs were correctly created
+ fail_count += self.c.check_get_bdevs_methods(uuid_bdev, size_32M)
+
+ old_bdevs = sorted(self.c.get_lvol_bdevs(), key=lambda x: x["name"])
+ old_stores = sorted(self.c.get_lvol_stores(), key=lambda x: x["name"])
+
+ # Shut down vhost instance and restart with new instance
+ fail_count += self._stop_vhost(pid_path)
+ remove(pid_path)
+ if self._start_vhost(vhost_path, pid_path) != 0:
+ fail_count += 1
+ return fail_count
+
+ self.c.construct_aio_bdev(aio_bdev0, base_name_1M, 4096)
+ self.c.construct_aio_bdev(aio_bdev1, base_name_32M, 4096)
+
+ # Check if configuration was properly loaded after tasting
+ # get all info all lvs and lvol bdevs, compare with previous info
+ new_bdevs = sorted(self.c.get_lvol_bdevs(), key=lambda x: x["name"])
+ new_stores = sorted(self.c.get_lvol_stores(), key=lambda x: x["name"])
+
+ if old_stores != new_stores:
+ fail_count += 1
+ print("ERROR: old and loaded lvol store is not the same")
+ print("DIFF:")
+ print(old_stores)
+ print(new_stores)
+
+ if len(old_bdevs) != len(new_bdevs):
+ fail_count += 1
+ print("ERROR: old and loaded lvol bdev list count is not equal")
+
+ for o, n in zip(old_bdevs, new_bdevs):
+ if o != n:
+ fail_count += 1
+ print("ERROR: old and loaded lvol bdev is not the same")
+ print("DIFF:")
+ pprint.pprint([o, n])
+
+ if fail_count != 0:
+ self.c.delete_aio_bdev(base_name_1M)
+ self.c.delete_aio_bdev(base_name_32M)
+ return fail_count
+
+ for uuid_bdev in uuid_bdevs:
+ self.c.destroy_lvol_bdev(uuid_bdev)
+
+ if self.c.destroy_lvol_store(uuid_store_1M) != 0:
+ fail_count += 1
+
+ if self.c.destroy_lvol_store(uuid_store_32M) != 0:
+ fail_count += 1
+
+ self.c.delete_aio_bdev(base_name_1M)
+ self.c.delete_aio_bdev(base_name_32M)
+
+ return fail_count
+
+ @case_message
+ def test_case750(self):
+ """
+ snapshot readonly
+
+ Create snaphot of lvol bdev and check if it is readonly.
+ """
+ fail_count = 0
+ nbd_name0 = "/dev/nbd0"
+ snapshot_name = "snapshot0"
+ # Construct malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Construct lvol store on malloc bdev
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+
+ lvs = self.c.get_lvol_stores()[0]
+ free_clusters_start = int(lvs['free_clusters'])
+ bdev_size = self.get_lvs_divided_size(3)
+ # Create lvol bdev with 33% of lvol store space
+ bdev_name = self.c.construct_lvol_bdev(uuid_store, self.lbd_name,
+ bdev_size)
+ lvol_bdev = self.c.get_lvol_bdev_with_name(bdev_name)
+ # Create snapshot of lvol bdev
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+
+ fail_count += self.c.start_nbd_disk(snapshot_bdev['name'], nbd_name0)
+ size = bdev_size * MEGABYTE
+ # Try to perform write operation on created snapshot
+ # Check if filling snapshot of lvol bdev fails
+ fail_count += self.run_fio_test(nbd_name0, 0, size, "write", "0xcc", 1)
+
+ fail_count += self.c.stop_nbd_disk(nbd_name0)
+ # Destroy lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name'])
+ # Destroy snapshot
+ fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name'])
+ # Destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ # Delete malloc bdev
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case751(self):
+ """
+ snapshot_compare_with_lvol_bdev
+
+ Check if lvol bdevs and snapshots contain the same data.
+ Check if lvol bdev and snapshot differ when writing to lvol bdev
+ after creating snapshot.
+ """
+ fail_count = 0
+ nbd_name = ["/dev/nbd0", "/dev/nbd1", "/dev/nbd2", "/dev/nbd3"]
+ snapshot_name0 = "snapshot0"
+ snapshot_name1 = "snapshot1"
+ # Construct mallov bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Construct lvol store
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ size = self.get_lvs_divided_size(6)
+ lbd_name0 = self.lbd_name + str(0)
+ lbd_name1 = self.lbd_name + str(1)
+ # Create thin provisioned lvol bdev with size less than 25% of lvs
+ uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store,
+ lbd_name0, size, thin=True)
+ # Create thick provisioned lvol bdev with size less than 25% of lvs
+ uuid_bdev1 = self.c.construct_lvol_bdev(uuid_store,
+ lbd_name1, size, thin=False)
+ lvol_bdev0 = self.c.get_lvol_bdev_with_name(uuid_bdev0)
+ fail_count += self.c.start_nbd_disk(lvol_bdev0['name'], nbd_name[0])
+ fill_size = int(size * MEGABYTE / 2)
+ # Fill thin provisoned lvol bdev with 50% of its space
+ fail_count += self.run_fio_test(nbd_name[0], 0, fill_size, "write", "0xcc", 0)
+ lvol_bdev1 = self.c.get_lvol_bdev_with_name(uuid_bdev1)
+ fail_count += self.c.start_nbd_disk(lvol_bdev1['name'], nbd_name[1])
+ fill_size = int(size * MEGABYTE)
+ # Fill whole thic provisioned lvol bdev
+ fail_count += self.run_fio_test(nbd_name[1], 0, fill_size, "write", "0xcc", 0)
+
+ # Create snapshots of lvol bdevs
+ fail_count += self.c.snapshot_lvol_bdev(uuid_bdev0, snapshot_name0)
+ fail_count += self.c.snapshot_lvol_bdev(uuid_bdev1, snapshot_name1)
+ fail_count += self.c.start_nbd_disk(self.lvs_name + "/" + snapshot_name0, nbd_name[2])
+ fail_count += self.c.start_nbd_disk(self.lvs_name + "/" + snapshot_name1, nbd_name[3])
+ # Compare every lvol bdev with corresponding snapshot
+ # and check that data are the same
+ fail_count += self.compare_two_disks(nbd_name[0], nbd_name[2], 0)
+ fail_count += self.compare_two_disks(nbd_name[1], nbd_name[3], 0)
+
+ fill_size = int(size * MEGABYTE / 2)
+ offset = fill_size
+ # Fill second half of thin provisioned lvol bdev
+ fail_count += self.run_fio_test(nbd_name[0], offset, fill_size, "write", "0xaa", 0)
+ # Compare thin provisioned lvol bdev with its snapshot and check if it fails
+ fail_count += self.compare_two_disks(nbd_name[0], nbd_name[2], 1)
+ for nbd in nbd_name:
+ fail_count += self.c.stop_nbd_disk(nbd)
+ # Delete lvol bdevs
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev0['name'])
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev1['name'])
+ # Delete snapshots
+ fail_count += self.c.destroy_lvol_bdev(self.lvs_name + "/" + snapshot_name0)
+ fail_count += self.c.destroy_lvol_bdev(self.lvs_name + "/" + snapshot_name1)
+ # Destroy snapshot
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ # Delete malloc bdev
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - calls successful, return code = 0
+ # - removing snapshot should always end with success
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case752(self):
+ """
+ snapshot_during_io_traffic
+
+ Check that when writing to lvol bdev
+ creating snapshot ends with success
+ """
+ global current_fio_pid
+ fail_count = 0
+ nbd_name = "/dev/nbd0"
+ snapshot_name = "snapshot"
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Construct lvol store
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ # Create thin provisioned lvol bdev with size equal to 50% of lvs space
+ size = self.get_lvs_divided_size(2)
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store, self.lbd_name,
+ size, thin=True)
+
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev)
+ fail_count += self.c.start_nbd_disk(lvol_bdev['name'], nbd_name)
+ fill_size = int(size * MEGABYTE)
+ # Create thread that will run fio in background
+ thread = FioThread(nbd_name, 0, fill_size, "write", "0xcc", 0,
+ extra_params="--time_based --runtime=8")
+ # Perform write operation with verification to created lvol bdev
+ thread.start()
+ time.sleep(4)
+ fail_count += is_process_alive(current_fio_pid)
+ # During write operation create snapshot of created lvol bdev
+ # and check that snapshot has been created successfully
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
+ fail_count += is_process_alive(current_fio_pid)
+ thread.join()
+ # Check that write operation ended with success
+ fail_count += thread.rv
+ fail_count += self.c.stop_nbd_disk(nbd_name)
+ # Destroy lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name'])
+ # Delete snapshot
+ fail_count += self.c.destroy_lvol_bdev(self.lvs_name + "/" + snapshot_name)
+ # Destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ # Delete malloc bdevs
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case753(self):
+ """
+ snapshot_of_snapshot
+
+ Check that creating snapshot of snapshot will fail
+ """
+ fail_count = 0
+ snapshot_name0 = "snapshot0"
+ snapshot_name1 = "snapshot1"
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Construct lvol store
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ # Create thick provisioned lvol bdev
+ size = self.get_lvs_divided_size(2)
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store, self.lbd_name,
+ size, thin=False)
+
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev)
+ # Create snapshot of created lvol bdev
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name0)
+ # Create snapshot of previously created snapshot
+ # and check if operation will fail
+ if self.c.snapshot_lvol_bdev(snapshot_name0, snapshot_name1) == 0:
+ print("ERROR: Creating snapshot of snapshot should fail")
+ fail_count += 1
+ # Delete lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name'])
+ # Destroy snapshot
+ fail_count += self.c.destroy_lvol_bdev(self.lvs_name + "/" + snapshot_name0)
+ # Destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ # Delete malloc bdev
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - calls successful, return code = 0
+ # - creating snapshot of snapshot should fail
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case754(self):
+ """
+ clone_bdev_only
+
+ Check that only clone of snapshot can be created.
+ Creating clone of lvol bdev should fail.
+ """
+ fail_count = 0
+ clone_name = "clone"
+ snapshot_name = "snapshot"
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Construct lvol store
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs = self.c.get_lvol_stores()
+ # Create thick provisioned lvol bdev with size equal to 50% of lvs space
+ size = self.get_lvs_divided_size(2)
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store, self.lbd_name,
+ size, thin=False)
+
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev)
+ # Create clone of lvol bdev and check if it fails
+ rv = self.c.clone_lvol_bdev(lvol_bdev['name'], clone_name)
+ if rv == 0:
+ print("ERROR: Creating clone of lvol bdev ended with unexpected success")
+ fail_count += 1
+ # Create snapshot of lvol bdev
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
+ # Create again clone of lvol bdev and check if it fails
+ rv = self.c.clone_lvol_bdev(lvol_bdev['name'], clone_name)
+ if rv == 0:
+ print("ERROR: Creating clone of lvol bdev ended with unexpected success")
+ fail_count += 1
+ # Create clone of snapshot and check if it ends with success
+ rv = self.c.clone_lvol_bdev(self.lvs_name + "/" + snapshot_name, clone_name)
+ if rv != 0:
+ print("ERROR: Creating clone of snapshot ended with unexpected failure")
+ fail_count += 1
+ clone_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name)
+
+ # Delete lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name'])
+ # Destroy clone
+ fail_count += self.c.destroy_lvol_bdev(clone_bdev['name'])
+ # Delete snapshot
+ fail_count += self.c.destroy_lvol_bdev(self.lvs_name + "/" + snapshot_name)
+ # Delete lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ # Destroy malloc bdev
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - calls successful, return code = 0
+ # - cloning thick provisioned lvol bdev should fail
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case755(self):
+ """
+ clone_writing_to_clone
+
+
+ """
+ fail_count = 0
+ nbd_name = ["/dev/nbd0", "/dev/nbd1", "/dev/nbd2", "/dev/nbd3"]
+ snapshot_name = "snapshot"
+ clone_name0 = "clone0"
+ clone_name1 = "clone1"
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Create lvol store
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ size = self.get_lvs_divided_size(6)
+ lbd_name0 = self.lbd_name + str(0)
+ # Construct thick provisioned lvol bdev
+ uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store,
+ lbd_name0, size, thin=False)
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0)
+ # Install lvol bdev on /dev/nbd0
+ fail_count += self.c.start_nbd_disk(lvol_bdev['name'], nbd_name[0])
+ fill_size = size * MEGABYTE
+ # Fill lvol bdev with 100% of its space
+ fail_count += self.run_fio_test(nbd_name[0], 0, fill_size, "write", "0xcc", 0)
+
+ # Create snapshot of thick provisioned lvol bdev
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+ # Create two clones of created snapshot
+ fail_count += self.c.clone_lvol_bdev(snapshot_bdev['name'], clone_name0)
+ fail_count += self.c.clone_lvol_bdev(snapshot_bdev['name'], clone_name1)
+
+ lvol_clone0 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name0)
+ fail_count += self.c.start_nbd_disk(lvol_clone0['name'], nbd_name[1])
+ fill_size = int(size * MEGABYTE / 2)
+ # Perform write operation to first clone
+ # Change first half of its space
+ fail_count += self.run_fio_test(nbd_name[1], 0, fill_size, "write", "0xaa", 0)
+ fail_count += self.c.start_nbd_disk(self.lvs_name + "/" + snapshot_name, nbd_name[2])
+ lvol_clone1 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name1)
+ fail_count += self.c.start_nbd_disk(lvol_clone1['name'], nbd_name[3])
+ # Compare snapshot with second clone. Data on both bdevs should be the same
+ time.sleep(1)
+ fail_count += self.compare_two_disks(nbd_name[2], nbd_name[3], 0)
+
+ for nbd in nbd_name:
+ fail_count += self.c.stop_nbd_disk(nbd)
+ # Destroy lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name'])
+ # Destroy two clones
+ fail_count += self.c.destroy_lvol_bdev(lvol_clone0['name'])
+ fail_count += self.c.destroy_lvol_bdev(lvol_clone1['name'])
+ # Delete snapshot
+ fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name'])
+ # Destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+ # Delete malloc
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case756(self):
+ """
+ clone_and_snapshot_relations
+
+ Check if relations between clones and snapshots
+ are properly set in configuration
+ """
+ fail_count = 0
+ snapshot_name = 'snapshot'
+ clone_name0 = 'clone1'
+ clone_name1 = 'clone2'
+ lbd_name = clone_name1
+
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ # Create lvol store
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ size = self.get_lvs_divided_size(6)
+
+ # Construct thick provisioned lvol bdev
+ uuid_bdev = self.c.construct_lvol_bdev(uuid_store,
+ lbd_name, size, thin=False)
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev)
+
+ # Create snapshot of thick provisioned lvol bdev
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+
+ # Create clone of created snapshot
+ fail_count += self.c.clone_lvol_bdev(snapshot_bdev['name'], clone_name0)
+
+ # Get current bdevs configuration
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+ lvol_clone0 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name0)
+ lvol_clone1 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + clone_name1)
+
+ # Check snapshot consistency
+ snapshot_lvol = snapshot_bdev['driver_specific']['lvol']
+ if snapshot_lvol['snapshot'] is not True:
+ fail_count += 1
+ if snapshot_lvol['clone'] is not False:
+ fail_count += 1
+ if sorted([clone_name0, clone_name1]) != sorted(snapshot_lvol['clones']):
+ fail_count += 1
+
+ # Check first clone consistency
+ lvol_clone0_lvol = lvol_clone0['driver_specific']['lvol']
+ if lvol_clone0_lvol['snapshot'] is not False:
+ fail_count += 1
+ if lvol_clone0_lvol['clone'] is not True:
+ fail_count += 1
+ if lvol_clone0_lvol['base_snapshot'] != 'snapshot':
+ fail_count += 1
+
+ # Check second clone consistency
+ lvol_clone1_lvol = lvol_clone1['driver_specific']['lvol']
+ if lvol_clone1_lvol['snapshot'] is not False:
+ fail_count += 1
+ if lvol_clone1_lvol['clone'] is not True:
+ fail_count += 1
+ if lvol_clone1_lvol['base_snapshot'] != 'snapshot':
+ fail_count += 1
+
+ # Destroy first clone and check if it is deleted from snapshot
+ fail_count += self.c.destroy_lvol_bdev(lvol_clone0['name'])
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+ if [clone_name1] != snapshot_bdev['driver_specific']['lvol']['clones']:
+ fail_count += 1
+
+ # Destroy second clone
+ fail_count += self.c.destroy_lvol_bdev(lvol_clone1['name'])
+
+ # Delete snapshot
+ fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name'])
+
+ # Destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+
+ # Delete malloc
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case757(self):
+ """
+ clone_inflate
+
+
+ Test inflate rpc method
+ """
+ fail_count = 0
+ snapshot_name = "snapshot"
+ nbd_name = "/dev/nbd0"
+
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+
+ # Create lvol store
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ size = self.get_lvs_divided_size(4)
+
+ # Construct thick provisioned lvol bdev
+ uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name, size, thin=False)
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0)
+
+ # Fill bdev with data of knonw pattern
+ fail_count += self.c.start_nbd_disk(lvol_bdev['name'], nbd_name)
+ fill_size = size * MEGABYTE
+ fail_count += self.run_fio_test(nbd_name, 0, fill_size, "write", "0xcc", 0)
+ self.c.stop_nbd_disk(nbd_name)
+
+ # Create snapshot of thick provisioned lvol bdev
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+
+ # Create two clones of created snapshot
+ lvol_clone = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + self.lbd_name)
+ if lvol_clone['driver_specific']['lvol']['thin_provision'] is not True:
+ fail_count += 1
+
+ # Fill part of clone with data of known pattern
+ fail_count += self.c.start_nbd_disk(lvol_clone['name'], nbd_name)
+ first_fill = 0
+ second_fill = int(size * 3 / 4)
+ fail_count += self.run_fio_test(nbd_name, first_fill * MEGABYTE,
+ MEGABYTE, "write", "0xdd", 0)
+ fail_count += self.run_fio_test(nbd_name, second_fill * MEGABYTE,
+ MEGABYTE, "write", "0xdd", 0)
+ self.c.stop_nbd_disk(nbd_name)
+
+ # Do inflate
+ fail_count += self.c.inflate_lvol_bdev(lvol_clone['name'])
+ lvol_clone = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + self.lbd_name)
+ if lvol_clone['driver_specific']['lvol']['thin_provision'] is not False:
+ fail_count += 1
+
+ # Delete snapshot
+ fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name'])
+
+ # Check data consistency
+ fail_count += self.c.start_nbd_disk(lvol_clone['name'], nbd_name)
+ fail_count += self.run_fio_test(nbd_name, first_fill * MEGABYTE,
+ MEGABYTE, "read", "0xdd")
+ fail_count += self.run_fio_test(nbd_name, (first_fill + 1) * MEGABYTE,
+ (second_fill - first_fill - 1) * MEGABYTE,
+ "read", "0xcc")
+ fail_count += self.run_fio_test(nbd_name, (second_fill) * MEGABYTE,
+ MEGABYTE, "read", "0xdd")
+ fail_count += self.run_fio_test(nbd_name, (second_fill + 1) * MEGABYTE,
+ (size - second_fill - 1) * MEGABYTE,
+ "read", "0xcc")
+ self.c.stop_nbd_disk(nbd_name)
+
+ # Destroy lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name'])
+
+ # Destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+
+ # Delete malloc
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case758(self):
+ """
+ clone_decouple_parent
+
+ Detach parent from clone and check if parent can be safely removed.
+ Check data consistency.
+ """
+
+ fail_count = 0
+ snapshot_name = "snapshot"
+ nbd_name = "/dev/nbd0"
+
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+
+ # Create lvol store
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ size = self.get_lvs_divided_size(4)
+
+ # Construct thin provisioned lvol bdev
+ uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name, size, thin=True)
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0)
+
+ # Decouple parent lvol bdev and check if it fails
+ ret_value = self.c.decouple_parent_lvol_bdev(lvol_bdev['name'])
+ if ret_value == 0:
+ print("ERROR: Decouple parent on bdev without parent should "
+ "fail but didn't")
+ fail_count += 1
+
+ # Create snapshot of thin provisioned lvol bdev
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+
+ # Try to destroy snapshot and check if it fails
+ ret_value = self.c.destroy_lvol_bdev(snapshot_bdev['name'])
+ if ret_value == 0:
+ print("ERROR: Delete snapshot should fail but didn't")
+ fail_count += 1
+
+ # Decouple parent lvol bdev
+ fail_count += self.c.decouple_parent_lvol_bdev(lvol_bdev['name'])
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0)
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+ if lvol_bdev['driver_specific']['lvol']['thin_provision'] is not True:
+ fail_count += 1
+ if lvol_bdev['driver_specific']['lvol']['clone'] is not False:
+ fail_count += 1
+ if lvol_bdev['driver_specific']['lvol']['snapshot'] is not False:
+ fail_count += 1
+ if snapshot_bdev['driver_specific']['lvol']['clone'] is not False:
+ fail_count += 1
+
+ # Destroy snapshot
+ fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name'])
+
+ # Destroy lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name'])
+
+ # Destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+
+ # Delete malloc
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case759(self):
+ """
+ clone_decouple_parent_rw
+
+ Create tree level snaphot-snapshot2-clone structure.
+ Detach snapshot2 from clone. Check if snapshot2 can be safely removed.
+ Each time check consistency of snapshot-clone relations and written data.
+ """
+ fail_count = 0
+ snapshot_name = "snapshot"
+ snapshot_name2 = "snapshot2"
+ nbd_name = "/dev/nbd0"
+
+ # Create malloc bdev
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+
+ # Create lvol store
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+ lvs = self.c.get_lvol_stores()
+ size = int(5 * lvs[0]['cluster_size'] / MEGABYTE)
+
+ # Construct thin provisioned lvol bdev
+ uuid_bdev0 = self.c.construct_lvol_bdev(uuid_store,
+ self.lbd_name, size, thin=True)
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0)
+
+ # Fill first four out of 5 culsters of clone with data of known pattern
+ fail_count += self.c.start_nbd_disk(lvol_bdev['name'], nbd_name)
+ begin_fill = 0
+ end_fill = int(size * 4 / 5)
+ fail_count += self.run_fio_test(nbd_name, begin_fill * MEGABYTE,
+ end_fill * MEGABYTE, "write", "0xdd", 0)
+
+ # Create snapshot of thin provisioned lvol bdev
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
+ snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
+
+ # Fill second and fourth cluster of clone with data of known pattern
+ start_fill = int(size / 5)
+ fill_range = int(size / 5)
+ fail_count += self.run_fio_test(nbd_name, start_fill * MEGABYTE,
+ fill_range * MEGABYTE, "write", "0xcc", 0)
+ start_fill = int(size * 3 / 5)
+ fail_count += self.run_fio_test(nbd_name, start_fill * MEGABYTE,
+ fill_range * MEGABYTE, "write", "0xcc", 0)
+
+ # Create second snapshot of thin provisioned lvol bdev
+ fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name2)
+ snapshot_bdev2 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name2)
+
+ # Fill second cluster of clone with data of known pattern
+ start_fill = int(size / 5)
+ fail_count += self.run_fio_test(nbd_name, start_fill * MEGABYTE,
+ fill_range * MEGABYTE, "write", "0xee", 0)
+
+ # Check data consistency
+ pattern = ["0xdd", "0xee", "0xdd", "0xcc", "0x00"]
+ for i in range(0, 5):
+ begin_fill = int(size * i / 5)
+ fail_count += self.run_fio_test(nbd_name, begin_fill * MEGABYTE,
+ fill_range * MEGABYTE, "read", pattern[i])
+
+ # Delete snapshot and check if it fails
+ ret_value = self.c.destroy_lvol_bdev(snapshot_bdev2['name'])
+ if ret_value == 0:
+ print("ERROR: Delete snapshot should fail but didn't")
+ fail_count += 1
+
+ # Decouple parent
+ fail_count += self.c.decouple_parent_lvol_bdev(lvol_bdev['name'])
+ lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0)
+
+ # Check data consistency
+ for i in range(0, 5):
+ begin_fill = int(size * i / 5)
+ fail_count += self.run_fio_test(nbd_name, begin_fill * MEGABYTE,
+ fill_range * MEGABYTE, "read", pattern[i])
+
+ # Delete second snapshot
+ ret_value = self.c.destroy_lvol_bdev(snapshot_bdev2['name'])
+
+ # Check data consistency
+ for i in range(0, 5):
+ begin_fill = int(size * i / 5)
+ fail_count += self.run_fio_test(nbd_name, begin_fill * MEGABYTE,
+ fill_range * MEGABYTE, "read", pattern[i])
+
+ # Destroy lvol bdev
+ fail_count += self.c.destroy_lvol_bdev(lvol_bdev['name'])
+
+ # Destroy snapshot
+ fail_count += self.c.destroy_lvol_bdev(snapshot_bdev['name'])
+
+ # Destroy lvol store
+ fail_count += self.c.destroy_lvol_store(uuid_store)
+
+ # Delete malloc
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ # Expected result:
+ # - calls successful, return code = 0
+ # - no other operation fails
+ return fail_count
+
+ @case_message
+ def test_case800(self):
+ fail_count = 0
+
+ bdev_uuids = []
+ bdev_names = [self.lbd_name + str(i) for i in range(4)]
+ bdev_aliases = ["/".join([self.lvs_name, name]) for name in bdev_names]
+
+ # Create a lvol store with 4 lvol bdevs
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ lvs_uuid = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_name,
+ lvs_uuid,
+ self.cluster_size,
+ self.lvs_name)
+ bdev_size = self.get_lvs_divided_size(4)
+ for name, alias in zip(bdev_names, bdev_aliases):
+ uuid = self.c.construct_lvol_bdev(lvs_uuid,
+ name,
+ bdev_size)
+ fail_count += self.c.check_get_bdevs_methods(uuid,
+ bdev_size,
+ alias)
+ bdev_uuids.append(uuid)
+
+ # Rename lvol store and check if lvol store name and
+ # lvol bdev aliases were updated properly
+ new_lvs_name = "lvs_new"
+ bdev_aliases = [alias.replace(self.lvs_name, new_lvs_name) for alias in bdev_aliases]
+
+ fail_count += self.c.rename_lvol_store(self.lvs_name, new_lvs_name)
+
+ fail_count += self.c.check_get_lvol_stores(base_name,
+ lvs_uuid,
+ self.cluster_size,
+ new_lvs_name)
+
+ for uuid, alias in zip(bdev_uuids, bdev_aliases):
+ fail_count += self.c.check_get_bdevs_methods(uuid,
+ bdev_size,
+ alias)
+
+ # Now try to rename the bdevs using their uuid as "old_name"
+ bdev_names = ["lbd_new" + str(i) for i in range(4)]
+ bdev_aliases = ["/".join([new_lvs_name, name]) for name in bdev_names]
+ print(bdev_aliases)
+ for uuid, new_name, new_alias in zip(bdev_uuids, bdev_names, bdev_aliases):
+ fail_count += self.c.rename_lvol_bdev(uuid, new_name)
+ fail_count += self.c.check_get_bdevs_methods(uuid,
+ bdev_size,
+ new_alias)
+ # Same thing but only use aliases
+ bdev_names = ["lbd_even_newer" + str(i) for i in range(4)]
+ new_bdev_aliases = ["/".join([new_lvs_name, name]) for name in bdev_names]
+ print(bdev_aliases)
+ for uuid, old_alias, new_alias, new_name in zip(bdev_uuids, bdev_aliases, new_bdev_aliases, bdev_names):
+ fail_count += self.c.rename_lvol_bdev(old_alias, new_name)
+ fail_count += self.c.check_get_bdevs_methods(uuid,
+ bdev_size,
+ new_alias)
+
+ # Delete configuration using names after rename operation
+ for bdev in new_bdev_aliases:
+ fail_count += self.c.destroy_lvol_bdev(bdev)
+ fail_count += self.c.destroy_lvol_store(new_lvs_name)
+ fail_count += self.c.delete_malloc_bdev(base_name)
+
+ return fail_count
+
+ @case_message
+ def test_case801(self):
+ fail_count = 0
+ if self.c.rename_lvol_store("NOTEXIST", "WHATEVER") == 0:
+ fail_count += 1
+ return fail_count
+
+ @case_message
+ def test_case802(self):
+ fail_count = 0
+
+ lvs_name_1 = "lvs_1"
+ lvs_name_2 = "lvs_2"
+
+ # Create lists with lvol bdev names and aliases for later use
+ bdev_names_1 = ["lvol_1_" + str(i) for i in range(4)]
+ bdev_aliases_1 = ["/".join([lvs_name_1, name]) for name in bdev_names_1]
+ bdev_uuids_1 = []
+ bdev_names_2 = ["lvol_2_" + str(i) for i in range(4)]
+ bdev_aliases_2 = ["/".join([lvs_name_2, name]) for name in bdev_names_2]
+ bdev_uuids_2 = []
+
+ base_bdev_1 = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ base_bdev_2 = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+
+ # Create lvol store on each malloc bdev
+ lvs_uuid_1 = self.c.construct_lvol_store(base_bdev_1,
+ lvs_name_1)
+ fail_count += self.c.check_get_lvol_stores(base_bdev_1,
+ lvs_uuid_1,
+ self.cluster_size,
+ lvs_name_1)
+ lvs_uuid_2 = self.c.construct_lvol_store(base_bdev_2,
+ lvs_name_2)
+ fail_count += self.c.check_get_lvol_stores(base_bdev_2,
+ lvs_uuid_2,
+ self.cluster_size,
+ lvs_name_2)
+
+ # Create 4 lvol bdevs on top of each lvol store
+ bdev_size_1 = self.get_lvs_divided_size(4, lvs_name_1)
+ bdev_size_2 = self.get_lvs_divided_size(4, lvs_name_2)
+ for name, alias in zip(bdev_names_1, bdev_aliases_1):
+ uuid = self.c.construct_lvol_bdev(lvs_uuid_1,
+ name,
+ bdev_size_1)
+ fail_count += self.c.check_get_bdevs_methods(uuid,
+ bdev_size_1,
+ alias)
+ bdev_uuids_1.append(uuid)
+ for name, alias in zip(bdev_names_2, bdev_aliases_2):
+ uuid = self.c.construct_lvol_bdev(lvs_uuid_2,
+ name,
+ bdev_size_2)
+ fail_count += self.c.check_get_bdevs_methods(uuid,
+ bdev_size_2,
+ alias)
+ bdev_uuids_2.append(uuid)
+
+ # Try to rename lvol store to already existing name
+ if self.c.rename_lvol_store(lvs_name_1, lvs_name_2) == 0:
+ fail_count += 1
+
+ # Verify that names of lvol stores and lvol bdevs did not change
+ fail_count += self.c.check_get_lvol_stores(base_bdev_1,
+ lvs_uuid_1,
+ self.cluster_size,
+ lvs_name_1)
+ fail_count += self.c.check_get_lvol_stores(base_bdev_2,
+ lvs_uuid_2,
+ self.cluster_size,
+ lvs_name_2)
+
+ for name, alias, uuid in zip(bdev_names_1, bdev_aliases_1, bdev_uuids_1):
+ fail_count += self.c.check_get_bdevs_methods(uuid,
+ bdev_size_1,
+ alias)
+
+ for name, alias, uuid in zip(bdev_names_2, bdev_aliases_2, bdev_uuids_2):
+ fail_count += self.c.check_get_bdevs_methods(uuid,
+ bdev_size_2,
+ alias)
+
+ # Clean configuration
+ for lvol_uuid in bdev_uuids_1 + bdev_uuids_2:
+ fail_count += self.c.destroy_lvol_bdev(lvol_uuid)
+ fail_count += self.c.destroy_lvol_store(lvs_uuid_1)
+ fail_count += self.c.destroy_lvol_store(lvs_uuid_2)
+ fail_count += self.c.delete_malloc_bdev(base_bdev_1)
+ fail_count += self.c.delete_malloc_bdev(base_bdev_2)
+
+ return fail_count
+
+ @case_message
+ def test_case803(self):
+ fail_count = 0
+ if self.c.rename_lvol_bdev("NOTEXIST", "WHATEVER") == 0:
+ fail_count += 1
+ return fail_count
+
+ @case_message
+ def test_case804(self):
+ fail_count = 0
+
+ base_bdev = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ lvs_uuid = self.c.construct_lvol_store(base_bdev,
+ self.lvs_name)
+ fail_count += self.c.check_get_lvol_stores(base_bdev,
+ lvs_uuid,
+ self.cluster_size,
+ self.lvs_name)
+ bdev_size = self.get_lvs_divided_size(2)
+ bdev_uuid_1 = self.c.construct_lvol_bdev(lvs_uuid,
+ self.lbd_name + "1",
+ bdev_size)
+ fail_count += self.c.check_get_bdevs_methods(bdev_uuid_1,
+ bdev_size)
+ bdev_uuid_2 = self.c.construct_lvol_bdev(lvs_uuid,
+ self.lbd_name + "2",
+ bdev_size)
+ fail_count += self.c.check_get_bdevs_methods(bdev_uuid_2,
+ bdev_size)
+
+ if self.c.rename_lvol_bdev(self.lbd_name + "1", self.lbd_name + "2") == 0:
+ fail_count += 1
+ fail_count += self.c.check_get_bdevs_methods(bdev_uuid_1,
+ bdev_size,
+ "/".join([self.lvs_name, self.lbd_name + "1"]))
+
+ fail_count += self.c.destroy_lvol_bdev(bdev_uuid_1)
+ fail_count += self.c.destroy_lvol_bdev(bdev_uuid_2)
+ fail_count += self.c.destroy_lvol_store(lvs_uuid)
+ fail_count += self.c.delete_malloc_bdev(base_bdev)
+
+ return fail_count
+
+ @case_message
+ def test_case10000(self):
+ pid_path = path.join(self.path, 'vhost.pid')
+
+ base_name = self.c.construct_malloc_bdev(self.total_size,
+ self.block_size)
+ uuid_store = self.c.construct_lvol_store(base_name,
+ self.lvs_name)
+ fail_count = self.c.check_get_lvol_stores(base_name, uuid_store,
+ self.cluster_size)
+
+ fail_count += self._stop_vhost(pid_path)
+ return fail_count
diff --git a/src/spdk/test/lvol/test_plan.md b/src/spdk/test/lvol/test_plan.md
new file mode 100644
index 00000000..5e38699a
--- /dev/null
+++ b/src/spdk/test/lvol/test_plan.md
@@ -0,0 +1,585 @@
+# Lvol feature test plan
+
+## Objective
+The purpose of these tests is to verify the possibility of using lvol configuration in SPDK.
+
+## Methodology
+Configuration in test is to be done using example stub application.
+All management is done using RPC calls, including logical volumes management.
+All tests are performed using malloc backends.
+One exception to malloc backends are tests for logical volume
+tasting - these require persistent merory like NVMe backend.
+
+Tests will be executed as scenarios - sets of smaller test step
+in which return codes from RPC calls is validated.
+Some configuration calls may also be validated by use of
+"get_*" RPC calls, which provide additional information for verifying
+results.
+
+Tests with thin provisioned lvol bdevs, snapshots and clones are using nbd devices.
+Before writing/reading to lvol bdev, bdev is installed with rpc start_nbd_disk.
+After finishing writing/reading, rpc stop_nbd_disk is used.
+
+## Tests
+
+### construct_lvol_store - positive tests
+
+#### TEST CASE 1 - Name: construct_lvs_positive
+Positive test for constructing a new lvol store.
+Call construct_lvol_store with correct base bdev name.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on correct, exisitng malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- destroy lvol store
+- delete malloc bdev
+
+Expected result:
+- call successful, return code = 0, uuid printed to stdout
+- get_lvol_stores: backend used for construct_lvol_store has uuid
+ field set with the same uuid as returned from RPC call
+- no other operation fails
+
+### construct_lvol_bdev - positive tests
+
+#### TEST CASE 50 - Name: construct_logical_volume_positive
+Positive test for constructing a new logical volume.
+Call construct_lvol_bdev with correct lvol store UUID and size in MiB for this bdev.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on correct, exisitng malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- construct_lvol_bdev on correct lvs_uuid and size
+- delete lvol bdev
+- destroy lvol store
+- delete malloc bdev
+
+Expected result:
+- call successful, return code = 0
+- get_bdevs: backend used for construct_lvol_bdev has name
+ field set with the same name as returned value from call RPC method: construct_lvol_bdev
+- no other operation fails
+
+#### TEST CASE 51 - Name: construct_multi_logical_volumes_positive
+Positive test for constructing a multi logical volumes.
+Call construct_lvol_bdev with correct lvol store UUID and
+size is equal one quarter of the this bdev size.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on correct, exisitng malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- construct_lvol_bdev on correct lvs_uuid and size
+ (size is approximately equal to one quarter of the bdev size,
+ because of lvol metadata)
+- repeat the previous step three more times
+- delete lvol bdevs
+- create and delete four lvol bdevs again from steps above
+- destroy lvol store
+- delete malloc bdev
+
+Expected result:
+- call successful, return code = 0
+- get_lvol_store: backend used for construct_lvol_bdev has name
+ field set with the same name as returned from RPC call for all repeat
+- no other operation fails
+
+#### TEST CASE 52 - Name: construct_lvol_bdev_using_name_positive
+Positive test for constructing a logical volume using friendly names.
+Verify that logical volumes can be created by using a friendly name
+instead of uuid when referencing to lvol store.
+Steps:
+- create malloc bdev
+- create logical volume store on created malloc bdev
+- verify lvol store was created correctly
+- create logical volume on lvol store by using a friendly name
+ as a reference
+- verify logical volume was correctly created
+- delete logical volume bdev
+- destroy logical volume store
+- delete malloc bdev
+
+Expected result:
+- calls successful, return code = 0
+- no other operation fails
+
+#### TEST CASE 53 - Name: construct_lvol_bdev_duplicate_names_positive
+Positive test for constructing a logical volumes using friendly names.
+Verify that logical volumes can use the same argument for friendly names
+if they are created on separate logical volume stores.
+Steps:
+- create two malloc bdevs
+- create logical volume stores on created malloc bdevs
+- verify stores were created correctly
+- create logical volume on first lvol store
+- verify it was correctly created
+- using the same friendly name argument create logical volume on second
+ lvol store
+- verify logical volume was correctly created
+- delete logical volume bdevs
+- destroy logical volume stores
+- delete malloc bdevs
+
+Expected result:
+- calls successful, return code = 0
+- no other operation fails
+
+### construct_lvol_bdev - negative tests
+
+#### TEST CASE 100 - Name: construct_logical_volume_nonexistent_lvs_uuid
+Negative test for constructing a new logical_volume.
+Call construct_lvol_bdev with lvs_uuid which does not
+exist in configuration.
+Steps:
+- try to call construct_lvol_bdev with lvs_uuid which does not exist
+
+Expected result:
+- return code != 0
+- ENODEV response printed to stdout
+
+#### TEST CASE 101 - Name: construct_lvol_bdev_on_full_lvol_store
+Negative test for constructing a new lvol bdev.
+Call construct_lvol_bdev on a full lvol store.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response from get_lvol_stores command
+- construct_lvol_bdev on correct lvs_uuid and size is smaller by 1 MB
+ from the full size malloc bdev
+- try construct_lvol_bdev on the same lvs_uuid as in last step;
+ this call should fail as lvol store space is taken by previously created bdev
+- destroy_lvol_store
+- delete malloc bdev
+
+Expected result:
+- first call successful
+- second construct_lvol_bdev call return code != 0
+- EEXIST response printed to stdout
+- no other operation fails
+
+#### TEST CASE 102 - Name: construct_lvol_bdev_name_twice
+Negative test for constructing lvol bdev using the same
+friendly name twice on the same logical volume store.
+Steps:
+- create malloc bdev
+- create logical volume store on malloc bdev
+- using get_lvol_stores verify that logical volume store was correctly created
+ and has arguments as provided in step earlier (cluster size, friendly name, base bdev)
+- construct logical volume on lvol store and verify it was correctly created
+- try to create another logical volume on the same lvol store using
+the same friendly name as in previous step; this step should fail
+- delete existing lvol bdev
+- delete existing lvol store
+- delete malloc bdevs
+
+Expected results:
+- creating two logical volumes with the same friendly name within the same
+ lvol store should not be possible
+- no other operation fails
+
+### resize_lvol_store - positive tests
+
+#### TEST CASE 150 - Name: resize_logical_volume_positive
+Positive test for resizing a logical_volume.
+Call resize_lvol_bdev with correct logical_volumes name and new size.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- construct_lvol_bdev on correct lvs_uuid and size is
+ equal to one quarter of size malloc bdev
+- check size of the lvol bdev by command RPC : get_bdevs
+- resize_lvol_bdev on correct lvs_uuid and size is
+ equal half to size malloc bdev
+- check size of the lvol bdev by command RPC : get_bdevs
+- resize_lvol_bdev on the correct lvs_uuid and size is smaller by 1 MB
+ from the full size malloc bdev
+- check size of the lvol bdev by command RPC : get_bdevs
+- resize_lvol_bdev on the correct lvs_uuid and size is equal 0 MiB
+- check size of the lvol bdev by command RPC : get_bdevs
+- delete lvol bdev
+- destroy lvol store
+- delete malloc bdev
+
+Expected result:
+- lvol bdev should change size after resize operations
+- calls successful, return code = 0
+- no other operation fails
+
+### resize lvol store - negative tests
+
+#### TEST CASE 200 - Name: resize_logical_volume_nonexistent_logical_volume
+Negative test for resizing a logical_volume.
+Call resize_lvol_bdev with logical volume which does not
+exist in configuration.
+Steps:
+- try resize_lvol_store on logical volume which does not exist
+
+Expected result:
+- return code != 0
+- Error code: ENODEV ("No such device") response printed to stdout
+
+#### TEST CASE 201 - Name: resize_logical_volume_with_size_out_of_range
+Negative test for resizing a logical volume.
+Call resize_lvol_store with size argument bigger than size of base bdev.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- construct_lvol_bdev on correct lvs_uuid and
+ size is equal one quarter of size malloc bdev
+- try resize_lvol_bdev on correct lvs_uuid and size is
+ equal to size malloc bdev + 1MiB; this call should fail
+- delete lvol bdev
+- destroy lvol store
+- delete malloc bdev
+
+Expected result:
+- resize_lvol_bdev call return code != 0
+- Error code: ENODEV ("Not enough free clusters left on lvol store")
+ response printed to stdout
+- no other operation fails
+
+### destroy_lvol_store - positive tests
+
+#### TEST CASE 250 - Name: destroy_lvol_store_positive
+Positive test for destroying a logical volume store.
+Call destroy_lvol_store with correct logical_volumes name
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- destroy_lvol_store
+- check correct response get_lvol_stores command
+- delete malloc bdev
+
+Expected result:
+- calls successful, return code = 0
+- get_lvol_stores: response should be of no value after destroyed lvol store
+- no other operation fails
+
+#### TEST CASE 251 - Name: destroy_lvol_store_use_name_positive
+Positive test for destroying a logical volume store using
+lvol store name instead of uuid for reference.
+Call destroy_lvol_store with correct logical volume name
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response from get_lvol_stores command
+- destroy_lvol_store
+- check correct response from get_lvol_stores command
+- delete malloc bdev
+
+Expected result:
+- calls successful, return code = 0
+- get_lvol_stores: response should be of no value after destroyed lvol store
+- no other operation fails
+
+#### TEST CASE 252 - Name: destroy_lvol_store_with_lvol_bdev_positive
+Positive test for destroying a logical volume store with lvol bdev
+created on top.
+Call destroy_lvol_store with correct logical_volumes name
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- construct_lvol_bdev on correct lvs_uuid and size is equal to size malloc bdev
+- destroy_lvol_store
+- check correct response get_lvol_stores command
+- delete malloc bdev
+
+Expected result:
+- calls successful, return code = 0
+- get_lvol_stores: response should be of no value after destroyed lvol store
+- no other operation fails
+
+#### TEST CASE 253 - Name: destroy_multi_logical_volumes_positive
+Positive test for destroying a logical volume store with multiple lvol
+bdevs created on top.
+Call construct_lvol_bdev with correct lvol store UUID and
+size is equal to one quarter of the this bdev size.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on correct, exisitng malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- construct_lvol_bdev on correct lvs_uuid and size
+ (size is equal to one quarter of the bdev size)
+- repeat the previous step four times
+- destroy_lvol_store
+- check correct response get_lvol_stores command
+- delete malloc bdev
+
+Expected result:
+- call successful, return code = 0
+- get_lvol_store: backend used for construct_lvol_bdev has name
+ field set with the same name as returned from RPC call for all repeat
+- no other operation fails
+
+#### TEST CASE 254 - Name: destroy_resize_logical_volume_positive
+Positive test for destroying a logical_volume after resizing.
+Call destroy_lvol_store with correct logical_volumes name.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- construct_lvol_bdev on correct lvs_uuid and size is
+ equal to one quarter of size malloc bdev
+- check size of the lvol bdev
+- resize_lvol_bdev on correct lvs_uuid and size is
+ equal half of size malloc bdev
+- check size of the lvol bdev by command RPC : get_bdevs
+- Resize_lvol_bdev on the correct lvs_uuid and the size is smaller by 1 MB
+ from the full size malloc bdev
+- check size of the lvol bdev by command RPC : get_bdevs
+- resize_lvol_bdev on the correct lvs_uuid and size is equal 0 MiB
+- check size of the lvol bdev by command RPC : get_bdevs
+- destroy_lvol_store
+- delete malloc bdev
+
+Expected result:
+- lvol bdev should change size after resize operations
+- calls successful, return code = 0
+- no other operation fails
+- get_lvol_stores: response should be of no value after destroyed lvol store
+
+#### TEST CASE 255 - Name: delete_lvol_store_persistent_positive
+Positive test for removing lvol store persistently
+Steps:
+- construct_lvol_store on NVMe bdev
+- destroy lvol store
+- delete NVMe bdev
+- add NVMe bdev
+- check if destroyed lvol store does not exist on NVMe bdev
+
+Expected result:
+- get_lvol_stores should not report any existsing lvol stores in configuration
+ after deleting and adding NVMe bdev
+- no other operation fails
+
+### destroy_lvol_store - negative tests
+
+#### TEST CASE 300 - Name: destroy_lvol_store_nonexistent_lvs_uuid
+Call destroy_lvol_store with nonexistent logical_volumes name
+exist in configuration.
+Steps:
+- try to call destroy_lvol_store with lvs_uuid which does not exist
+
+Expected result:
+- return code != 0
+- Error code response printed to stdout
+
+#### TEST CASE 301 - Name: delete_lvol_store_underlying_bdev
+Call destroy_lvol_store after deleting it's base bdev.
+Lvol store should be automatically removed on deleting underlying bdev.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- delete malloc bdev
+- try to destroy lvol store; this call should fail as lvol store
+ is no longer present
+
+Expected result:
+- destroy_lvol_store retudn code != 0
+- Error code: ENODEV ("No such device") response printed to stdout
+- no other operation fails
+
+### nested destroy_lvol_bdev - negative tests
+
+#### TEST CASE 350 - Name: nested_destroy_logical_volume_negative
+Negative test for destroying a nested first lvol store.
+Call destroy_lvol_store with correct base bdev name.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- construct_lvol_bdev on correct lvs_uuid and size is
+ equal to size malloc bdev
+- construct first nested lvol store on created lvol_bdev
+- check correct uuid values in response get_lvol_stores command
+- construct first nested lvol bdev on correct lvs_uuid and size
+- check size of the lvol bdev by command RPC : get_bdevs
+- destroy first lvol_store
+- delete malloc bdev
+
+Expected result:
+- Error code: ENODEV ("the device is busy") response printed to stdout
+- no other operation fails
+
+### nested construct_logical_volume - positive tests
+
+#### TEST CASE 400 - Name: nested_construct_logical_volume_positive
+Positive test for constructing a nested new lvol store.
+Call construct_lvol_store with correct base bdev name.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- construct_lvol_bdev on correct lvs_uuid and size is
+ equal to size malloc bdev
+- construct first nested lvol store on created lvol_bdev
+- check correct uuid values in response get_lvol_stores command
+- construct first nested lvol bdev on correct lvs_uuid and size
+- construct second nested lvol store on created first nested lvol bdev
+- check correct uuid values in response get_lvol_stores command
+- construct second nested lvol bdev on correct first nested lvs uuid and size
+- delete nested lvol bdev and lvol store
+- delete base lvol bdev and lvol store
+- delete malloc bdev
+
+Expected result:
+- calls successful, return code = 0
+- get_lvol_stores: backend used for construct_lvol_store has UUID
+ field set with the same UUID as returned from RPC call
+ backend used for construct_lvol_bdev has UUID
+ field set with the same UUID as returned from RPC call
+- no other operation fails
+
+### construct_lvol_store - negative tests
+
+#### TEST CASE 450 - Name: construct_lvs_nonexistent_bdev
+Negative test for constructing a new lvol store.
+Call construct_lvol_store with base bdev name which does not
+exist in configuration.
+Steps:
+- try construct_lvol_store on bdev which does not exist
+
+Expected result:
+- return code != 0
+- Error code: ENODEV ("No such device") response printed to stdout
+
+#### TEST CASE 451 - Name: construct_lvs_on_bdev_twice
+Negative test for constructing a new lvol store.
+Call construct_lvol_store with base bdev name twice.
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- try construct_lvol_store on the same bdev as in last step;
+ this call should fail as base bdev is already claimed by lvol store
+- destroy lvs
+- delete malloc bdev
+
+Expected result:
+- first call successful
+- second construct_lvol_store call return code != 0
+- EEXIST response printed to stdout
+- no other operation fails
+
+#### TEST CASE 452 - Name: construct_lvs_name_twice
+Negative test for constructing a new lvol store using the same
+friendly name twice.
+Steps:
+- create two malloc bdevs
+- create logical volume store on first malloc bdev
+- using get_lvol_stores verify that logical volume store was correctly created
+ and has arguments as provided in step earlier (cluster size, friendly name, base bdev)
+- try to create another logical volume store on second malloc bdev using the
+ same friendly name as before; this step is expected to fail as lvol stores
+ cannot have the same name
+- delete existing lvol store
+- delete malloc bdevs
+
+Expected results:
+- creating two logical volume stores with the same friendly name should
+not be possible
+- no other operation fails
+
+### logical volume rename tests
+
+#### TEST CASE 800 - Name: rename_positive
+Positive test for lvol store and lvol bdev rename.
+Steps:
+- create malloc bdev
+- construct lvol store on malloc bdev
+- create 4 lvol bdevs on top of previously created lvol store
+- rename lvol store; verify that lvol store friendly name was
+ updated in get_lvol_stores output; verify that prefix in lvol bdevs
+ friendly names were also updated
+- rename lvol bdevs; use lvols UUID's to point which lvol bdev name to change;
+ verify that all bdev names were successfully updated
+- rename lvol bdevs; use lvols alias name to point which lvol bdev
+ name to change; verify that all bdev names were successfully updated
+- clean running configuration: delete lvol bdevs, destroy lvol store,
+ delete malloc bdev; use lvol store and lvol bdev friendly names for delete
+ and destroy commands to check if new names can be correctly used for performing
+ other RPC operations;
+
+Expected results:
+- lvol store and lvol bdevs correctly created
+- lvol store and lvol bdevs names updated after renaming operation
+- lvol store and lvol bdevs possible to delete using new names
+- no other operation fails
+
+#### TEST CASE 801 - Name: rename_lvs_nonexistent
+Negative test case for lvol store rename.
+Check that error is returned when trying to rename not existing lvol store.
+
+Steps:
+- call rename_lvol_store with name pointing to not existing lvol store
+
+Expected results:
+- rename_lvol_store return code != 0
+- no other operation fails
+
+#### TEST CASE 802 - Name: rename_lvs_EEXIST
+Negative test case for lvol store rename.
+Check that error is returned when trying to rename to a name which is already
+used by another lvol store.
+
+Steps:
+- create 2 malloc bdevs
+- construct lvol store on each malloc bdev
+- on each lvol store create 4 lvol bdevs
+- call rename_lvol_store on first lvol store and try to change its name to
+ the same name as used by second lvol store
+- verify that both lvol stores still have the same names as before
+- verify that lvol bdev have the same aliases as before
+
+Expected results:
+- rename_lvol_store return code != 0; not possible to rename to already
+ used name
+- no other operation fails
+
+#### TEST CASE 803 - Name: rename_lvol_bdev_nonexistent
+Negative test case for lvol bdev rename.
+Check that error is returned when trying to rename not existing lvol bdev.
+
+Steps:
+- call rename_lvol_bdev with name pointing to not existing lvol bdev
+
+Expected results:
+- rename_lvol_bdev return code != 0
+- no other operation fails
+
+#### TEST CASE 804 - Name: rename_lvol_bdev_EEXIST
+Negative test case for lvol bdev rename.
+Check that error is returned when trying to rename to a name which is already
+used by another lvol bdev.
+
+Steps:
+- create malloc bdev
+- construct lvol store on malloc bdev
+- construct 2 lvol bdevs on lvol store
+- call rename_lvol_bdev on first lvol bdev and try to change its name to
+ the same name as used by second lvol bdev
+- verify that both lvol bdev still have the same names as before
+
+Expected results:
+- rename_lvol_bdev return code != 0; not possible to rename to already
+ used name
+- no other operation fails
+
+### SIGTERM
+
+#### TEST CASE 10000 - Name: SIGTERM
+Call CTRL+C (SIGTERM) occurs after creating lvol store
+Steps:
+- create a malloc bdev
+- construct_lvol_store on created malloc bdev
+- check correct uuid values in response get_lvol_stores command
+- Send SIGTERM signal to the application
+
+Expected result:
+- calls successful, return code = 0
+- get_bdevs: no change
+- no other operation fails
diff --git a/src/spdk/test/nvme/Makefile b/src/spdk/test/nvme/Makefile
new file mode 100644
index 00000000..9fae60f6
--- /dev/null
+++ b/src/spdk/test/nvme/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 = aer reset sgl e2edp overhead deallocated_value err_injection
+
+.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/nvme/aer/.gitignore b/src/spdk/test/nvme/aer/.gitignore
new file mode 100644
index 00000000..31379617
--- /dev/null
+++ b/src/spdk/test/nvme/aer/.gitignore
@@ -0,0 +1 @@
+aer
diff --git a/src/spdk/test/nvme/aer/Makefile b/src/spdk/test/nvme/aer/Makefile
new file mode 100644
index 00000000..77acabd0
--- /dev/null
+++ b/src/spdk/test/nvme/aer/Makefile
@@ -0,0 +1,39 @@
+#
+# 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 = aer
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/aer/aer.c b/src/spdk/test/nvme/aer/aer.c
new file mode 100644
index 00000000..e102813f
--- /dev/null
+++ b/src/spdk/test/nvme/aer/aer.c
@@ -0,0 +1,580 @@
+/*-
+ * 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/log.h"
+#include "spdk/nvme.h"
+#include "spdk/env.h"
+
+#define MAX_DEVS 64
+
+struct dev {
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct spdk_nvme_health_information_page *health_page;
+ struct spdk_nvme_ns_list *changed_ns_list;
+ uint32_t orig_temp_threshold;
+ char name[SPDK_NVMF_TRADDR_MAX_LEN + 1];
+};
+
+static void get_feature_test(struct dev *dev);
+
+static struct dev devs[MAX_DEVS];
+static int num_devs = 0;
+
+#define foreach_dev(iter) \
+ for (iter = devs; iter - devs < num_devs; iter++)
+
+static int outstanding_commands = 0;
+static int aer_done = 0;
+static int temperature_done = 0;
+static int failed = 0;
+static struct spdk_nvme_transport_id g_trid;
+
+/* Enable AER temperature test */
+static int enable_temp_test = 0;
+/* Enable AER namespace attribute notice test, this variable holds
+ * the NSID that is expected to be in the Changed NS List.
+ */
+static uint32_t expected_ns_test = 0;
+
+static void
+set_temp_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands--;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: set feature (temp threshold) failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ /* Admin command completions are synchronized by the NVMe driver,
+ * so we don't need to do any special locking here. */
+ temperature_done++;
+}
+
+static int
+set_temp_threshold(struct dev *dev, uint32_t temp)
+{
+ struct spdk_nvme_cmd cmd = {};
+ int rc;
+
+ cmd.opc = SPDK_NVME_OPC_SET_FEATURES;
+ cmd.cdw10 = SPDK_NVME_FEAT_TEMPERATURE_THRESHOLD;
+ cmd.cdw11 = temp;
+
+ rc = spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0, set_temp_completion, dev);
+ if (rc == 0) {
+ outstanding_commands++;
+ }
+
+ return rc;
+}
+
+static void
+get_temp_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands--;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: get feature (temp threshold) failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ dev->orig_temp_threshold = cpl->cdw0;
+ printf("%s: original temperature threshold: %u Kelvin (%d Celsius)\n",
+ dev->name, dev->orig_temp_threshold, dev->orig_temp_threshold - 273);
+
+ temperature_done++;
+}
+
+static int
+get_temp_threshold(struct dev *dev)
+{
+ struct spdk_nvme_cmd cmd = {};
+ int rc;
+
+ cmd.opc = SPDK_NVME_OPC_GET_FEATURES;
+ cmd.cdw10 = SPDK_NVME_FEAT_TEMPERATURE_THRESHOLD;
+
+ rc = spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0, get_temp_completion, dev);
+ if (rc == 0) {
+ outstanding_commands++;
+ }
+
+ return rc;
+}
+
+static void
+print_health_page(struct dev *dev, struct spdk_nvme_health_information_page *hip)
+{
+ printf("%s: Current Temperature: %u Kelvin (%d Celsius)\n",
+ dev->name, hip->temperature, hip->temperature - 273);
+}
+
+static void
+get_health_log_page_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands --;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: get log page failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ print_health_page(dev, dev->health_page);
+ aer_done++;
+}
+
+static void
+get_changed_ns_log_page_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+ bool found = false;
+ uint32_t i;
+
+ outstanding_commands --;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: get log page failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ /* Let's compare the expected namespce ID is
+ * in changed namespace list
+ */
+ if (dev->changed_ns_list->ns_list[0] != 0xffffffffu) {
+ for (i = 0; i < sizeof(*dev->changed_ns_list) / sizeof(uint32_t); i++) {
+ if (expected_ns_test == dev->changed_ns_list->ns_list[i]) {
+ printf("%s: changed NS list contains expected NSID: %u\n",
+ dev->name, expected_ns_test);
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ printf("%s: Error: Can't find expected NSID %u\n", dev->name, expected_ns_test);
+ failed = 1;
+ }
+
+ aer_done++;
+}
+
+static int
+get_health_log_page(struct dev *dev)
+{
+ int rc;
+
+ rc = spdk_nvme_ctrlr_cmd_get_log_page(dev->ctrlr, SPDK_NVME_LOG_HEALTH_INFORMATION,
+ SPDK_NVME_GLOBAL_NS_TAG, dev->health_page, sizeof(*dev->health_page), 0,
+ get_health_log_page_completion, dev);
+
+ if (rc == 0) {
+ outstanding_commands++;
+ }
+
+ return rc;
+}
+
+static int
+get_changed_ns_log_page(struct dev *dev)
+{
+ int rc;
+
+ rc = spdk_nvme_ctrlr_cmd_get_log_page(dev->ctrlr, SPDK_NVME_LOG_CHANGED_NS_LIST,
+ SPDK_NVME_GLOBAL_NS_TAG, dev->changed_ns_list,
+ sizeof(*dev->changed_ns_list), 0,
+ get_changed_ns_log_page_completion, dev);
+
+ if (rc == 0) {
+ outstanding_commands++;
+ }
+
+ return rc;
+}
+
+static void
+cleanup(void)
+{
+ struct dev *dev;
+
+ foreach_dev(dev) {
+ if (dev->health_page) {
+ spdk_dma_free(dev->health_page);
+ }
+ if (dev->changed_ns_list) {
+ spdk_dma_free(dev->changed_ns_list);
+ }
+ }
+}
+
+static void
+aer_cb(void *arg, const struct spdk_nvme_cpl *cpl)
+{
+ uint32_t log_page_id = (cpl->cdw0 & 0xFF0000) >> 16;
+ struct dev *dev = arg;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: AER failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ printf("%s: aer_cb for log page %d\n", dev->name, log_page_id);
+
+ if (log_page_id == SPDK_NVME_LOG_HEALTH_INFORMATION) {
+ /* Set the temperature threshold back to the original value
+ * so the AER doesn't trigger again.
+ */
+ set_temp_threshold(dev, dev->orig_temp_threshold);
+ get_health_log_page(dev);
+ } else if (log_page_id == SPDK_NVME_LOG_CHANGED_NS_LIST) {
+ get_changed_ns_log_page(dev);
+ }
+}
+
+static void
+usage(const char *program_name)
+{
+ printf("%s [options]", program_name);
+ printf("\n");
+ printf("options:\n");
+ printf(" -T enable temperature tests\n");
+ printf(" -n expected Namespace attribute notice ID\n");
+ printf(" -r trid remote NVMe over Fabrics target address\n");
+ printf(" Format: 'key:value [key:value] ...'\n");
+ printf(" Keys:\n");
+ printf(" trtype Transport type (e.g. RDMA)\n");
+ printf(" adrfam Address family (e.g. IPv4, IPv6)\n");
+ printf(" traddr Transport address (e.g. 192.168.100.8)\n");
+ printf(" trsvcid Transport service identifier (e.g. 4420)\n");
+ printf(" subnqn Subsystem NQN (default: %s)\n", SPDK_NVMF_DISCOVERY_NQN);
+ printf(" Example: -r 'trtype:RDMA adrfam:IPv4 traddr:192.168.100.8 trsvcid:4420'\n");
+
+ spdk_tracelog_usage(stdout, "-L");
+
+ printf(" -v verbose (enable warnings)\n");
+ printf(" -H show this usage\n");
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ int op, rc;
+
+ g_trid.trtype = SPDK_NVME_TRANSPORT_PCIE;
+ snprintf(g_trid.subnqn, sizeof(g_trid.subnqn), "%s", SPDK_NVMF_DISCOVERY_NQN);
+
+ while ((op = getopt(argc, argv, "n:r:HL:T")) != -1) {
+ switch (op) {
+ case 'n':
+ expected_ns_test = atoi(optarg);
+ break;
+ case 'r':
+ if (spdk_nvme_transport_id_parse(&g_trid, optarg) != 0) {
+ fprintf(stderr, "Error parsing transport address\n");
+ return 1;
+ }
+ break;
+ case 'L':
+ rc = spdk_log_set_trace_flag(optarg);
+ if (rc < 0) {
+ fprintf(stderr, "unknown flag\n");
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ spdk_log_set_print_level(SPDK_LOG_DEBUG);
+#ifndef DEBUG
+ fprintf(stderr, "%s must be rebuilt with CONFIG_DEBUG=y for -L flag.\n",
+ argv[0]);
+ usage(argv[0]);
+ return 0;
+#endif
+ break;
+ case 'T':
+ enable_temp_test = 1;
+ break;
+ case 'H':
+ default:
+ usage(argv[0]);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static bool
+probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
+ struct spdk_nvme_ctrlr_opts *opts)
+{
+ printf("Attaching to %s\n", trid->traddr);
+
+ 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 dev *dev;
+
+ /* add to dev list */
+ dev = &devs[num_devs++];
+
+ dev->ctrlr = ctrlr;
+
+ snprintf(dev->name, sizeof(dev->name), "%s",
+ trid->traddr);
+
+ printf("Attached to %s\n", dev->name);
+
+ dev->health_page = spdk_dma_zmalloc(sizeof(*dev->health_page), 4096, NULL);
+ if (dev->health_page == NULL) {
+ printf("Allocation error (health page)\n");
+ failed = 1;
+ }
+ dev->changed_ns_list = spdk_dma_zmalloc(sizeof(*dev->changed_ns_list), 4096, NULL);
+ if (dev->changed_ns_list == NULL) {
+ printf("Allocation error (changed namespace list page)\n");
+ failed = 1;
+ }
+}
+
+static void
+get_feature_test_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands--;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: get number of queues failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ if (aer_done < num_devs) {
+ /*
+ * Resubmit Get Features command to continue filling admin queue
+ * while the test is running.
+ */
+ get_feature_test(dev);
+ }
+}
+
+static void
+get_feature_test(struct dev *dev)
+{
+ struct spdk_nvme_cmd cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opc = SPDK_NVME_OPC_GET_FEATURES;
+ cmd.cdw10 = SPDK_NVME_FEAT_NUMBER_OF_QUEUES;
+ if (spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0,
+ get_feature_test_cb, dev) != 0) {
+ printf("Failed to send Get Features command for dev=%p\n", dev);
+ failed = 1;
+ return;
+ }
+
+ outstanding_commands++;
+}
+
+static int
+spdk_aer_temperature_test(void)
+{
+ struct dev *dev;
+
+ printf("Getting temperature thresholds of all controllers...\n");
+ foreach_dev(dev) {
+ /* Get the original temperature threshold */
+ get_temp_threshold(dev);
+ }
+
+ while (!failed && temperature_done < num_devs) {
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr);
+ }
+ }
+
+ if (failed) {
+ return failed;
+ }
+ temperature_done = 0;
+ aer_done = 0;
+
+ /* Send admin commands to test admin queue wraparound while waiting for the AER */
+ foreach_dev(dev) {
+ get_feature_test(dev);
+ }
+
+ if (failed) {
+ return failed;
+ }
+
+ printf("Waiting for all controllers to trigger AER...\n");
+ foreach_dev(dev) {
+ /* Set the temperature threshold to a low value */
+ set_temp_threshold(dev, 200);
+ }
+
+ if (failed) {
+ return failed;
+ }
+
+ while (!failed && (aer_done < num_devs || temperature_done < num_devs)) {
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr);
+ }
+ }
+
+ if (failed) {
+ return failed;
+ }
+
+ return 0;
+}
+
+static int
+spdk_aer_changed_ns_test(void)
+{
+ struct dev *dev;
+
+ aer_done = 0;
+
+ printf("Starting namespce attribute notice tests for all controllers...\n");
+
+ foreach_dev(dev) {
+ get_feature_test(dev);
+ }
+
+ if (failed) {
+ return failed;
+ }
+
+ while (!failed && (aer_done < num_devs)) {
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr);
+ }
+ }
+
+ if (failed) {
+ return failed;
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct dev *dev;
+ int i;
+ struct spdk_env_opts opts;
+ int rc;
+
+ rc = parse_args(argc, argv);
+ if (rc != 0) {
+ return rc;
+ }
+
+ spdk_env_opts_init(&opts);
+ opts.name = "aer";
+ opts.core_mask = "0x1";
+ opts.mem_size = 64;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ printf("Asynchronous Event Request test\n");
+
+ if (spdk_nvme_probe(&g_trid, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "spdk_nvme_probe() failed\n");
+ return 1;
+ }
+
+ if (failed) {
+ goto done;
+ }
+
+ printf("Registering asynchronous event callbacks...\n");
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_register_aer_callback(dev->ctrlr, aer_cb, dev);
+ }
+
+ /* AER temperature test */
+ if (enable_temp_test) {
+ if (spdk_aer_temperature_test()) {
+ goto done;
+ }
+ }
+
+ /* AER changed namespace list test */
+ if (expected_ns_test) {
+ if (spdk_aer_changed_ns_test()) {
+ goto done;
+ }
+ }
+
+ printf("Cleaning up...\n");
+
+ while (outstanding_commands) {
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr);
+ }
+ }
+
+ for (i = 0; i < num_devs; i++) {
+ struct dev *dev = &devs[i];
+
+ spdk_nvme_detach(dev->ctrlr);
+ }
+
+done:
+ cleanup();
+
+ return failed;
+}
diff --git a/src/spdk/test/nvme/deallocated_value/.gitignore b/src/spdk/test/nvme/deallocated_value/.gitignore
new file mode 100644
index 00000000..8460e82e
--- /dev/null
+++ b/src/spdk/test/nvme/deallocated_value/.gitignore
@@ -0,0 +1 @@
+deallocated_value
diff --git a/src/spdk/test/nvme/deallocated_value/Makefile b/src/spdk/test/nvme/deallocated_value/Makefile
new file mode 100644
index 00000000..bff11cad
--- /dev/null
+++ b/src/spdk/test/nvme/deallocated_value/Makefile
@@ -0,0 +1,39 @@
+#
+# 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 = deallocated_value
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/deallocated_value/deallocated_value.c b/src/spdk/test/nvme/deallocated_value/deallocated_value.c
new file mode 100644
index 00000000..e1d1c9f6
--- /dev/null
+++ b/src/spdk/test/nvme/deallocated_value/deallocated_value.c
@@ -0,0 +1,445 @@
+/*-
+ * 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/nvme.h"
+#include "spdk/env.h"
+
+#define NUM_BLOCKS 100
+
+/*
+ * The purpose of this sample app is to determine the read value of deallocated logical blocks
+ * from a given NVMe Controller. The NVMe 1.3 spec requires the controller to list this value,
+ * but controllers adhering to the NVMe 1.2 spec may not report this value. According to the spec,
+ * "The values read from a deallocated logical block and its metadata (excluding protection information) shall
+ * be all bytes set to 00h, all bytes set to FFh, or the last data written to the associated logical block".
+ */
+
+struct ns_entry {
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct spdk_nvme_ns *ns;
+ struct ns_entry *next;
+ struct spdk_nvme_qpair *qpair;
+};
+
+struct deallocate_context {
+ struct ns_entry *ns_entry;
+ char **write_buf;
+ char **read_buf;
+ char *zero_buf;
+ char *FFh_buf;
+ int writes_completed;
+ int reads_completed;
+ int deallocate_completed;
+ int flush_complete;
+ int matches_zeroes;
+ int matches_previous_data;
+ int matches_FFh;
+};
+
+static struct ns_entry *g_namespaces = NULL;
+
+static void cleanup(struct deallocate_context *context);
+
+static void
+fill_random(char *buf, size_t num_bytes)
+{
+ size_t i;
+
+ srand((unsigned) time(NULL));
+ for (i = 0; i < num_bytes; i++) {
+ buf[i] = rand() % 0x100;
+ }
+}
+
+static void
+register_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns *ns)
+{
+ struct ns_entry *entry;
+ const struct spdk_nvme_ctrlr_data *cdata;
+
+ cdata = spdk_nvme_ctrlr_get_data(ctrlr);
+
+ if (!spdk_nvme_ns_is_active(ns)) {
+ printf("Controller %-20.20s (%-20.20s): Skipping inactive NS %u\n",
+ cdata->mn, cdata->sn,
+ spdk_nvme_ns_get_id(ns));
+ return;
+ }
+
+ entry = malloc(sizeof(struct ns_entry));
+ if (entry == NULL) {
+ perror("ns_entry malloc");
+ exit(1);
+ }
+
+ entry->ctrlr = ctrlr;
+ entry->ns = ns;
+ entry->next = g_namespaces;
+ g_namespaces = entry;
+
+ printf(" Namespace ID: %d size: %juGB\n", spdk_nvme_ns_get_id(ns),
+ spdk_nvme_ns_get_size(ns) / 1000000000);
+}
+
+static uint32_t
+get_max_block_size(void)
+{
+ struct ns_entry *ns;
+ uint32_t max_block_size, temp_block_size;
+
+ ns = g_namespaces;
+ max_block_size = 0;
+
+ while (ns != NULL) {
+ temp_block_size = spdk_nvme_ns_get_sector_size(ns->ns);
+ max_block_size = temp_block_size > max_block_size ? temp_block_size : max_block_size;
+ ns = ns->next;
+ }
+
+ return max_block_size;
+}
+
+static void
+write_complete(void *arg, const struct spdk_nvme_cpl *completion)
+{
+ struct deallocate_context *context = arg;
+
+ context->writes_completed++;
+}
+
+static void
+read_complete(void *arg, const struct spdk_nvme_cpl *completion)
+{
+ struct deallocate_context *context = arg;
+ struct ns_entry *ns_entry = context->ns_entry;
+ int rc;
+
+ rc = memcmp(context->write_buf[context->reads_completed],
+ context->read_buf[context->reads_completed], spdk_nvme_ns_get_sector_size(ns_entry->ns));
+ if (rc == 0) {
+ context->matches_previous_data++;
+ }
+
+ rc = memcmp(context->zero_buf, context->read_buf[context->reads_completed],
+ spdk_nvme_ns_get_sector_size(ns_entry->ns));
+ if (rc == 0) {
+ context->matches_zeroes++;
+ }
+
+ rc = memcmp(context->FFh_buf, context->read_buf[context->reads_completed],
+ spdk_nvme_ns_get_sector_size(ns_entry->ns));
+ if (rc == 0) {
+ context->matches_FFh++;
+ }
+ context->reads_completed++;
+}
+
+static void
+deallocate_complete(void *arg, const struct spdk_nvme_cpl *completion)
+{
+ struct deallocate_context *context = arg;
+
+ printf("blocks matching previous data: %d\n", context->matches_previous_data);
+ printf("blocks matching zeroes: %d\n", context->matches_zeroes);
+ printf("blocks matching 0xFF: %d\n", context->matches_FFh);
+ printf("Deallocating Blocks 0 to %d with random data.\n", NUM_BLOCKS - 1);
+ printf("On next read, read value will match deallocated block read value.\n");
+ context->deallocate_completed = 1;
+ context->reads_completed = 0;
+ context->matches_previous_data = 0;
+ context->matches_zeroes = 0;
+ context->matches_FFh = 0;
+}
+
+static void
+flush_complete(void *arg, const struct spdk_nvme_cpl *completion)
+{
+ struct deallocate_context *context = arg;
+
+ context->flush_complete = 1;
+}
+
+static void
+deallocate_test(void)
+{
+ struct ns_entry *ns_entry;
+ struct spdk_nvme_ctrlr *ctrlr;
+ const struct spdk_nvme_ctrlr_data *data;
+ struct deallocate_context context;
+ struct spdk_nvme_dsm_range range;
+ uint32_t max_block_size;
+ int rc, i;
+
+ memset(&context, 0, sizeof(struct deallocate_context));
+ max_block_size = get_max_block_size();
+ ns_entry = g_namespaces;
+
+ if (max_block_size > 0) {
+ context.zero_buf = malloc(max_block_size);
+ } else {
+ printf("Unable to determine max block size.\n");
+ return;
+ }
+
+ if (context.zero_buf == NULL) {
+ printf("could not allocate buffer for test.\n");
+ return;
+ }
+
+ context.FFh_buf = malloc(max_block_size);
+ if (context.FFh_buf == NULL) {
+ cleanup(&context);
+ printf("could not allocate buffer for test.\n");
+ return;
+ }
+
+ context.write_buf = calloc(NUM_BLOCKS, sizeof(char *));
+ if (context.write_buf == NULL) {
+ cleanup(&context);
+ return;
+ }
+
+ context.read_buf = calloc(NUM_BLOCKS, sizeof(char *));
+ if (context.read_buf == NULL) {
+ printf("could not allocate buffer for test.\n");
+ cleanup(&context);
+ return;
+ }
+
+ memset(context.zero_buf, 0x00, max_block_size);
+ memset(context.FFh_buf, 0xFF, max_block_size);
+
+ for (i = 0; i < NUM_BLOCKS; i++) {
+ context.write_buf[i] = spdk_dma_zmalloc(0x1000, max_block_size, NULL);
+ if (context.write_buf[i] == NULL) {
+ printf("could not allocate buffer for test.\n");
+ cleanup(&context);
+ return;
+ }
+
+ fill_random(context.write_buf[i], 0x1000);
+ context.read_buf[i] = spdk_dma_zmalloc(0x1000, max_block_size, NULL);
+ if (context.read_buf[i] == NULL) {
+ printf("could not allocate buffer for test.\n");
+ cleanup(&context);
+ return;
+ }
+ }
+
+ while (ns_entry != NULL) {
+
+ ns_entry->qpair = spdk_nvme_ctrlr_alloc_io_qpair(ns_entry->ctrlr, NULL, 0);
+ if (ns_entry->qpair == NULL) {
+ printf("ERROR: spdk_nvme_ctrlr_alloc_io_qpair() failed.\n");
+ return;
+ }
+
+ ctrlr = spdk_nvme_ns_get_ctrlr(ns_entry->ns);
+ data = spdk_nvme_ctrlr_get_data(ctrlr);
+
+ printf("\nController %-20.20s (%-20.20s)\n", data->mn, data->sn);
+ printf("Controller PCI vendor:%u PCI subsystem vendor:%u\n", data->vid, data->ssvid);
+ printf("Namespace Block Size:%u\n", spdk_nvme_ns_get_sector_size(ns_entry->ns));
+ printf("Writing Blocks 0 to %d with random data.\n", NUM_BLOCKS);
+ printf("On next read, read value will match random data.\n");
+
+ context.ns_entry = ns_entry;
+
+ for (i = 0; i < NUM_BLOCKS; i++) {
+ rc = spdk_nvme_ns_cmd_write(ns_entry->ns, ns_entry->qpair, context.write_buf[i],
+ i,
+ 1,
+ write_complete, &context, 0);
+ if (rc) {
+ printf("Error in nvme command completion, values may be inaccurate.\n");
+ }
+ }
+ while (context.writes_completed < NUM_BLOCKS) {
+ spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
+ }
+
+ spdk_nvme_ns_cmd_flush(ns_entry->ns, ns_entry->qpair, flush_complete, &context);
+ while (!context.flush_complete) {
+ spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
+ }
+
+ for (i = 0; i < NUM_BLOCKS; i++) {
+ rc = spdk_nvme_ns_cmd_read(ns_entry->ns, ns_entry->qpair, context.read_buf[i],
+ i, /* LBA start */
+ 1, /* number of LBAs */
+ read_complete, &context, 0);
+ if (rc) {
+ printf("Error in nvme command completion, values may be inaccurate.\n");
+ }
+
+ /* block after each read command so that we can match the block to the write buffer. */
+ while (context.reads_completed <= i) {
+ spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
+ }
+ }
+
+ context.flush_complete = 0;
+ range.length = NUM_BLOCKS;
+ range.starting_lba = 0;
+ rc = spdk_nvme_ns_cmd_dataset_management(ns_entry->ns, ns_entry->qpair,
+ SPDK_NVME_DSM_ATTR_DEALLOCATE, &range, 1, deallocate_complete, &context);
+ if (rc) {
+ printf("Error in nvme command completion, values may be inaccurate.\n");
+ }
+
+ while (!context.deallocate_completed) {
+ spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
+ }
+
+ for (i = 0; i < NUM_BLOCKS; i++) {
+ rc = spdk_nvme_ns_cmd_read(ns_entry->ns, ns_entry->qpair, context.read_buf[i],
+ i, /* LBA start */
+ 1, /* number of LBAs */
+ read_complete, &context, 0);
+ if (rc) {
+ printf("Error in nvme command completion, values may be inaccurate.\n");
+ }
+ while (context.reads_completed <= i) {
+ spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
+ }
+ }
+
+ printf("blocks matching previous data: %d\n", context.matches_previous_data);
+ printf("blocks matching zeroes: %d\n", context.matches_zeroes);
+ printf("blocks matching FFh: %d\n", context.matches_FFh);
+
+ /* reset counters in between each namespace. */
+ context.matches_previous_data = 0;
+ context.matches_zeroes = 0;
+ context.matches_FFh = 0;
+ context.writes_completed = 0;
+ context.reads_completed = 0;
+ context.deallocate_completed = 0;
+
+ spdk_nvme_ctrlr_free_io_qpair(ns_entry->qpair);
+ ns_entry = ns_entry->next;
+ }
+ cleanup(&context);
+}
+
+static bool
+probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
+ struct spdk_nvme_ctrlr_opts *opts)
+{
+ printf("Attaching to %s\n", trid->traddr);
+
+ 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)
+{
+ int num_ns;
+ struct spdk_nvme_ns *ns;
+
+ printf("Attached to %s\n", trid->traddr);
+ /*
+ * Use only the first namespace from each controller since we are testing controller level functionality.
+ */
+ num_ns = spdk_nvme_ctrlr_get_num_ns(ctrlr);
+ if (num_ns < 1) {
+ printf("No valid namespaces in controller\n");
+ } else {
+ ns = spdk_nvme_ctrlr_get_ns(ctrlr, 1);
+ register_ns(ctrlr, ns);
+ }
+}
+
+static void
+cleanup(struct deallocate_context *context)
+{
+ struct ns_entry *ns_entry = g_namespaces;
+ int i;
+
+ while (ns_entry) {
+ struct ns_entry *next = ns_entry->next;
+ free(ns_entry);
+ ns_entry = next;
+ }
+ for (i = 0; i < NUM_BLOCKS; i++) {
+ if (context->write_buf[i]) {
+ spdk_dma_free(context->write_buf[i]);
+ } else {
+ break;
+ }
+ if (context->read_buf[i]) {
+ spdk_dma_free(context->read_buf[i]);
+ } else {
+ break;
+ }
+ }
+
+ free(context->write_buf);
+ free(context->read_buf);
+ free(context->zero_buf);
+ free(context->FFh_buf);
+}
+
+int main(int argc, char **argv)
+{
+ int rc;
+ struct spdk_env_opts opts;
+
+ spdk_env_opts_init(&opts);
+ opts.name = "deallocate_test";
+ opts.shm_id = 0;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ printf("Initializing NVMe Controllers\n");
+
+ rc = spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL);
+ if (rc != 0) {
+ fprintf(stderr, "spdk_nvme_probe() failed\n");
+ return 1;
+ }
+
+ if (g_namespaces == NULL) {
+ fprintf(stderr, "no NVMe controllers found\n");
+ return 1;
+ }
+
+ printf("Initialization complete.\n");
+ deallocate_test();
+ return 0;
+}
diff --git a/src/spdk/test/nvme/e2edp/.gitignore b/src/spdk/test/nvme/e2edp/.gitignore
new file mode 100644
index 00000000..df095820
--- /dev/null
+++ b/src/spdk/test/nvme/e2edp/.gitignore
@@ -0,0 +1 @@
+nvme_dp
diff --git a/src/spdk/test/nvme/e2edp/Makefile b/src/spdk/test/nvme/e2edp/Makefile
new file mode 100644
index 00000000..226a50e0
--- /dev/null
+++ b/src/spdk/test/nvme/e2edp/Makefile
@@ -0,0 +1,39 @@
+#
+# 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 = nvme_dp
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/e2edp/nvme_dp.c b/src/spdk/test/nvme/e2edp/nvme_dp.c
new file mode 100644
index 00000000..eaf2bd32
--- /dev/null
+++ b/src/spdk/test/nvme/e2edp/nvme_dp.c
@@ -0,0 +1,659 @@
+/*-
+ * 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.
+ */
+
+/*
+ * NVMe end-to-end data protection test
+ */
+
+#include "spdk/stdinc.h"
+
+#include "spdk/nvme.h"
+#include "spdk/env.h"
+#include "spdk/crc16.h"
+#include "spdk/endian.h"
+
+#define MAX_DEVS 64
+
+#define DATA_PATTERN 0x5A
+
+struct dev {
+ struct spdk_nvme_ctrlr *ctrlr;
+ char name[SPDK_NVMF_TRADDR_MAX_LEN + 1];
+};
+
+static struct dev devs[MAX_DEVS];
+static int num_devs = 0;
+
+#define foreach_dev(iter) \
+ for (iter = devs; iter - devs < num_devs; iter++)
+
+static int io_complete_flag = 0;
+
+struct io_request {
+ void *contig;
+ void *metadata;
+ bool use_extended_lba;
+ bool use_sgl;
+ uint32_t sgl_offset;
+ uint32_t buf_size;
+ uint64_t lba;
+ uint32_t lba_count;
+ uint16_t apptag_mask;
+ uint16_t apptag;
+};
+
+static void
+io_complete(void *ctx, const struct spdk_nvme_cpl *cpl)
+{
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ io_complete_flag = 2;
+ } else {
+ io_complete_flag = 1;
+ }
+}
+
+static void
+ns_data_buffer_reset(struct spdk_nvme_ns *ns, struct io_request *req, uint8_t data_pattern)
+{
+ uint32_t md_size, sector_size;
+ uint32_t i, offset = 0;
+ uint8_t *buf;
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+
+ for (i = 0; i < req->lba_count; i++) {
+ if (req->use_extended_lba) {
+ offset = (sector_size + md_size) * i;
+ } else {
+ offset = sector_size * i;
+ }
+
+ buf = (uint8_t *)req->contig + offset;
+ memset(buf, data_pattern, sector_size);
+ }
+}
+
+static void nvme_req_reset_sgl(void *cb_arg, uint32_t sgl_offset)
+{
+ struct io_request *req = (struct io_request *)cb_arg;
+
+ req->sgl_offset = sgl_offset;
+ return;
+}
+
+static int nvme_req_next_sge(void *cb_arg, void **address, uint32_t *length)
+{
+ struct io_request *req = (struct io_request *)cb_arg;
+ void *payload;
+
+ payload = req->contig + req->sgl_offset;
+ *address = payload;
+
+ *length = req->buf_size - req->sgl_offset;
+
+ return 0;
+}
+
+/* CRC-16 Guard checked for extended lba format */
+static uint32_t dp_guard_check_extended_lba_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ struct spdk_nvme_protection_info *pi;
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 2;
+
+ /* extended LBA only for the test case */
+ if (!(spdk_nvme_ns_supports_extended_lba(ns))) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc((sector_size + md_size) * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->lba = 0x200000;
+ req->use_extended_lba = true;
+ req->use_sgl = true;
+ req->buf_size = (sector_size + md_size) * req->lba_count;
+ req->metadata = NULL;
+ ns_data_buffer_reset(ns, req, DATA_PATTERN);
+ pi = (struct spdk_nvme_protection_info *)(req->contig + sector_size + md_size - 8);
+ /* big-endian for guard */
+ to_be16(&pi->guard, spdk_crc16_t10dif(req->contig, sector_size));
+
+ pi = (struct spdk_nvme_protection_info *)(req->contig + (sector_size + md_size) * 2 - 8);
+ to_be16(&pi->guard, spdk_crc16_t10dif(req->contig + sector_size + md_size, sector_size));
+
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_GUARD;
+
+ return req->lba_count;
+}
+
+/*
+ * No protection information with PRACT setting to 1,
+ * both extended LBA format and separate metadata can
+ * run the test case.
+ */
+static uint32_t dp_with_pract_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ uint32_t sector_size;
+
+ req->lba_count = 8;
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ /* No additional metadata buffer provided */
+ req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ switch (spdk_nvme_ns_get_pi_type(ns)) {
+ case SPDK_NVME_FMT_NVM_PROTECTION_TYPE3:
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_GUARD | SPDK_NVME_IO_FLAGS_PRACT;
+ break;
+ case SPDK_NVME_FMT_NVM_PROTECTION_TYPE1:
+ case SPDK_NVME_FMT_NVM_PROTECTION_TYPE2:
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_GUARD | SPDK_NVME_IO_FLAGS_PRCHK_REFTAG |
+ SPDK_NVME_IO_FLAGS_PRACT;
+ break;
+ default:
+ *io_flags = 0;
+ break;
+ }
+
+ req->lba = 0x100000;
+ req->use_extended_lba = false;
+ req->metadata = NULL;
+
+ return req->lba_count;
+}
+
+/* Block Reference Tag checked for TYPE1 and TYPE2 with PRACT setting to 0 */
+static uint32_t dp_without_pract_extended_lba_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ struct spdk_nvme_protection_info *pi;
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 2;
+
+ switch (spdk_nvme_ns_get_pi_type(ns)) {
+ case SPDK_NVME_FMT_NVM_PROTECTION_TYPE3:
+ return 0;
+ default:
+ break;
+ }
+
+ /* extended LBA only for the test case */
+ if (!(spdk_nvme_ns_supports_extended_lba(ns))) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc((sector_size + md_size) * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->lba = 0x200000;
+ req->use_extended_lba = true;
+ req->metadata = NULL;
+ pi = (struct spdk_nvme_protection_info *)(req->contig + sector_size + md_size - 8);
+ /* big-endian for reference tag */
+ to_be32(&pi->ref_tag, (uint32_t)req->lba);
+
+ pi = (struct spdk_nvme_protection_info *)(req->contig + (sector_size + md_size) * 2 - 8);
+ /* is incremented for each subsequent logical block */
+ to_be32(&pi->ref_tag, (uint32_t)(req->lba + 1));
+
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_REFTAG;
+
+ return req->lba_count;
+}
+
+/* LBA + Metadata without data protection bits setting */
+static uint32_t dp_without_flags_extended_lba_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 16;
+
+ /* extended LBA only for the test case */
+ if (!(spdk_nvme_ns_supports_extended_lba(ns))) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc((sector_size + md_size) * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->lba = 0x400000;
+ req->use_extended_lba = true;
+ req->metadata = NULL;
+ *io_flags = 0;
+
+ return req->lba_count;
+}
+
+/* Block Reference Tag checked for TYPE1 and TYPE2 with PRACT setting to 0 */
+static uint32_t dp_without_pract_separate_meta_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ struct spdk_nvme_protection_info *pi;
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 2;
+
+ switch (spdk_nvme_ns_get_pi_type(ns)) {
+ case SPDK_NVME_FMT_NVM_PROTECTION_TYPE3:
+ return 0;
+ default:
+ break;
+ }
+
+ /* separate metadata payload for the test case */
+ if (spdk_nvme_ns_supports_extended_lba(ns)) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->metadata = spdk_dma_zmalloc(md_size * req->lba_count, 0x1000, NULL);
+ if (!req->metadata) {
+ spdk_dma_free(req->contig);
+ return 0;
+ }
+
+ req->lba = 0x400000;
+ req->use_extended_lba = false;
+
+ /* last 8 bytes if the metadata size bigger than 8 */
+ pi = (struct spdk_nvme_protection_info *)(req->metadata + md_size - 8);
+ /* big-endian for reference tag */
+ to_be32(&pi->ref_tag, (uint32_t)req->lba);
+
+ pi = (struct spdk_nvme_protection_info *)(req->metadata + md_size * 2 - 8);
+ /* is incremented for each subsequent logical block */
+ to_be32(&pi->ref_tag, (uint32_t)(req->lba + 1));
+
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_REFTAG;
+
+ return req->lba_count;
+}
+
+/* Application Tag checked with PRACT setting to 0 */
+static uint32_t dp_without_pract_separate_meta_apptag_test(struct spdk_nvme_ns *ns,
+ struct io_request *req,
+ uint32_t *io_flags)
+{
+ struct spdk_nvme_protection_info *pi;
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 1;
+
+ /* separate metadata payload for the test case */
+ if (spdk_nvme_ns_supports_extended_lba(ns)) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->metadata = spdk_dma_zmalloc(md_size * req->lba_count, 0x1000, NULL);
+ if (!req->metadata) {
+ spdk_dma_free(req->contig);
+ return 0;
+ }
+
+ req->lba = 0x500000;
+ req->use_extended_lba = false;
+ req->apptag_mask = 0xFFFF;
+ req->apptag = req->lba_count;
+
+ /* last 8 bytes if the metadata size bigger than 8 */
+ pi = (struct spdk_nvme_protection_info *)(req->metadata + md_size - 8);
+ to_be16(&pi->app_tag, req->lba_count);
+
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_APPTAG;
+
+ return req->lba_count;
+}
+
+/*
+ * LBA + Metadata without data protection bits setting,
+ * separate metadata payload for the test case.
+ */
+static uint32_t dp_without_flags_separate_meta_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 16;
+
+ /* separate metadata payload for the test case */
+ if (spdk_nvme_ns_supports_extended_lba(ns)) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->metadata = spdk_dma_zmalloc(md_size * req->lba_count, 0x1000, NULL);
+ if (!req->metadata) {
+ spdk_dma_free(req->contig);
+ return 0;
+ }
+
+ req->lba = 0x600000;
+ req->use_extended_lba = false;
+ *io_flags = 0;
+
+ return req->lba_count;
+}
+
+typedef uint32_t (*nvme_build_io_req_fn_t)(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *lba_count);
+
+static void
+free_req(struct io_request *req)
+{
+ if (req == NULL) {
+ return;
+ }
+
+ if (req->contig) {
+ spdk_dma_free(req->contig);
+ }
+
+ if (req->metadata) {
+ spdk_dma_free(req->metadata);
+ }
+
+ spdk_dma_free(req);
+}
+
+static int
+ns_data_buffer_compare(struct spdk_nvme_ns *ns, struct io_request *req, uint8_t data_pattern)
+{
+ uint32_t md_size, sector_size;
+ uint32_t i, j, offset = 0;
+ uint8_t *buf;
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+
+ for (i = 0; i < req->lba_count; i++) {
+ if (req->use_extended_lba) {
+ offset = (sector_size + md_size) * i;
+ } else {
+ offset = sector_size * i;
+ }
+
+ buf = (uint8_t *)req->contig + offset;
+ for (j = 0; j < sector_size; j++) {
+ if (buf[j] != data_pattern) {
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int
+write_read_e2e_dp_tests(struct dev *dev, nvme_build_io_req_fn_t build_io_fn, const char *test_name)
+{
+ int rc = 0;
+ uint32_t lba_count;
+ uint32_t io_flags = 0;
+
+ struct io_request *req;
+ struct spdk_nvme_ns *ns;
+ struct spdk_nvme_qpair *qpair;
+ const struct spdk_nvme_ns_data *nsdata;
+
+ ns = spdk_nvme_ctrlr_get_ns(dev->ctrlr, 1);
+ if (!ns) {
+ fprintf(stderr, "Null namespace\n");
+ return 0;
+ }
+
+ if (!(spdk_nvme_ns_get_flags(ns) & SPDK_NVME_NS_DPS_PI_SUPPORTED)) {
+ return 0;
+ }
+
+ nsdata = spdk_nvme_ns_get_data(ns);
+ if (!nsdata || !spdk_nvme_ns_get_sector_size(ns)) {
+ fprintf(stderr, "Empty nsdata or wrong sector size\n");
+ return 0;
+ }
+
+ req = spdk_dma_zmalloc(sizeof(*req), 0, NULL);
+ if (!req) {
+ fprintf(stderr, "Allocate request failed\n");
+ return 0;
+ }
+
+ /* IO parameters setting */
+ lba_count = build_io_fn(ns, req, &io_flags);
+
+ if (!lba_count) {
+ fprintf(stderr, "%s: %s bypass the test case\n", dev->name, test_name);
+ free_req(req);
+ return 0;
+ }
+
+ qpair = spdk_nvme_ctrlr_alloc_io_qpair(dev->ctrlr, NULL, 0);
+ if (!qpair) {
+ free_req(req);
+ return -1;
+ }
+
+ ns_data_buffer_reset(ns, req, DATA_PATTERN);
+ if (req->use_extended_lba && req->use_sgl) {
+ rc = spdk_nvme_ns_cmd_writev(ns, qpair, req->lba, lba_count, io_complete, req, io_flags,
+ nvme_req_reset_sgl, nvme_req_next_sge);
+ } else if (req->use_extended_lba) {
+ rc = spdk_nvme_ns_cmd_write(ns, qpair, req->contig, req->lba, lba_count,
+ io_complete, req, io_flags);
+ } else {
+ rc = spdk_nvme_ns_cmd_write_with_md(ns, qpair, req->contig, req->metadata, req->lba, lba_count,
+ io_complete, req, io_flags, req->apptag_mask, req->apptag);
+ }
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: %s write submit failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ io_complete_flag = 0;
+
+ while (!io_complete_flag) {
+ spdk_nvme_qpair_process_completions(qpair, 1);
+ }
+
+ if (io_complete_flag != 1) {
+ fprintf(stderr, "%s: %s write exec failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ /* reset completion flag */
+ io_complete_flag = 0;
+
+ ns_data_buffer_reset(ns, req, 0);
+ if (req->use_extended_lba && req->use_sgl) {
+ rc = spdk_nvme_ns_cmd_readv(ns, qpair, req->lba, lba_count, io_complete, req, io_flags,
+ nvme_req_reset_sgl, nvme_req_next_sge);
+
+ } else if (req->use_extended_lba) {
+ rc = spdk_nvme_ns_cmd_read(ns, qpair, req->contig, req->lba, lba_count,
+ io_complete, req, io_flags);
+ } else {
+ rc = spdk_nvme_ns_cmd_read_with_md(ns, qpair, req->contig, req->metadata, req->lba, lba_count,
+ io_complete, req, io_flags, req->apptag_mask, req->apptag);
+ }
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: %s read failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ while (!io_complete_flag) {
+ spdk_nvme_qpair_process_completions(qpair, 1);
+ }
+
+ if (io_complete_flag != 1) {
+ fprintf(stderr, "%s: %s read failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ rc = ns_data_buffer_compare(ns, req, DATA_PATTERN);
+ if (rc < 0) {
+ fprintf(stderr, "%s: %s write/read success, but memcmp Failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ fprintf(stdout, "%s: %s test passed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return rc;
+}
+
+static bool
+probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
+ struct spdk_nvme_ctrlr_opts *opts)
+{
+ printf("Attaching to %s\n", trid->traddr);
+
+ 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 dev *dev;
+
+ /* add to dev list */
+ dev = &devs[num_devs++];
+
+ dev->ctrlr = ctrlr;
+
+ snprintf(dev->name, sizeof(dev->name), "%s",
+ trid->traddr);
+
+ printf("Attached to %s\n", dev->name);
+}
+
+int main(int argc, char **argv)
+{
+ struct dev *iter;
+ int rc, i;
+ struct spdk_env_opts opts;
+
+ spdk_env_opts_init(&opts);
+ opts.name = "nvme_dp";
+ opts.core_mask = "0x1";
+ opts.shm_id = 0;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ printf("NVMe Write/Read with End-to-End data protection test\n");
+
+ if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "nvme_probe() failed\n");
+ exit(1);
+ }
+
+ rc = 0;
+ foreach_dev(iter) {
+#define TEST(x) write_read_e2e_dp_tests(iter, x, #x)
+ if (TEST(dp_with_pract_test)
+ || TEST(dp_guard_check_extended_lba_test)
+ || TEST(dp_without_pract_extended_lba_test)
+ || TEST(dp_without_flags_extended_lba_test)
+ || TEST(dp_without_pract_separate_meta_test)
+ || TEST(dp_without_pract_separate_meta_apptag_test)
+ || TEST(dp_without_flags_separate_meta_test)) {
+#undef TEST
+ rc = 1;
+ printf("%s: failed End-to-End data protection tests\n", iter->name);
+ }
+ }
+
+ printf("Cleaning up...\n");
+
+ for (i = 0; i < num_devs; i++) {
+ struct dev *dev = &devs[i];
+
+ spdk_nvme_detach(dev->ctrlr);
+ }
+
+ return rc;
+}
diff --git a/src/spdk/test/nvme/err_injection/.gitignore b/src/spdk/test/nvme/err_injection/.gitignore
new file mode 100644
index 00000000..3572a8e7
--- /dev/null
+++ b/src/spdk/test/nvme/err_injection/.gitignore
@@ -0,0 +1 @@
+err_injection
diff --git a/src/spdk/test/nvme/err_injection/Makefile b/src/spdk/test/nvme/err_injection/Makefile
new file mode 100644
index 00000000..4f5f1851
--- /dev/null
+++ b/src/spdk/test/nvme/err_injection/Makefile
@@ -0,0 +1,39 @@
+#
+# 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 = err_injection
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/err_injection/err_injection.c b/src/spdk/test/nvme/err_injection/err_injection.c
new file mode 100644
index 00000000..50d67092
--- /dev/null
+++ b/src/spdk/test/nvme/err_injection/err_injection.c
@@ -0,0 +1,279 @@
+/*-
+ * 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/log.h"
+#include "spdk/nvme.h"
+#include "spdk/env.h"
+
+#define MAX_DEVS 64
+
+struct dev {
+ bool error_expected;
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct spdk_nvme_ns *ns;
+ struct spdk_nvme_qpair *qpair;
+ void *data;
+ char name[SPDK_NVMF_TRADDR_MAX_LEN + 1];
+};
+
+static struct dev devs[MAX_DEVS];
+static int num_devs = 0;
+
+#define foreach_dev(iter) \
+ for (iter = devs; iter - devs < num_devs; iter++)
+
+static int outstanding_commands = 0;
+static int failed = 0;
+
+static bool
+probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
+ struct spdk_nvme_ctrlr_opts *opts)
+{
+ printf("Attaching to %s\n", trid->traddr);
+
+ 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 dev *dev;
+ uint32_t nsid;
+
+ /* add to dev list */
+ dev = &devs[num_devs++];
+ if (num_devs >= MAX_DEVS) {
+ return;
+ }
+
+ dev->ctrlr = ctrlr;
+ nsid = spdk_nvme_ctrlr_get_first_active_ns(ctrlr);
+ dev->ns = spdk_nvme_ctrlr_get_ns(ctrlr, nsid);
+ if (dev->ns == NULL) {
+ failed = 1;
+ return;
+ }
+ dev->qpair = spdk_nvme_ctrlr_alloc_io_qpair(ctrlr, NULL, 0);
+ if (dev->qpair == NULL) {
+ failed = 1;
+ return;
+ }
+
+ snprintf(dev->name, sizeof(dev->name), "%s",
+ trid->traddr);
+
+ printf("Attached to %s\n", dev->name);
+}
+
+static void
+get_feature_test_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands--;
+
+ if (spdk_nvme_cpl_is_error(cpl) && dev->error_expected) {
+ if (cpl->status.sct != SPDK_NVME_SCT_GENERIC ||
+ cpl->status.sc != SPDK_NVME_SC_INVALID_FIELD) {
+ failed = 1;
+ }
+ printf("%s: get features failed as expected\n", dev->name);
+ return;
+ }
+
+ if (!spdk_nvme_cpl_is_error(cpl) && !dev->error_expected) {
+ printf("%s: get features successfully as expected\n", dev->name);
+ return;
+ }
+
+ failed = 1;
+}
+
+static void
+get_feature_test(bool error_expected)
+{
+ struct dev *dev;
+ struct spdk_nvme_cmd cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opc = SPDK_NVME_OPC_GET_FEATURES;
+ cmd.cdw10 = SPDK_NVME_FEAT_NUMBER_OF_QUEUES;
+
+ foreach_dev(dev) {
+ dev->error_expected = error_expected;
+ if (spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0,
+ get_feature_test_cb, dev) != 0) {
+ printf("Error: failed to send Get Features command for dev=%p\n", dev);
+ failed = 1;
+ goto cleanup;
+ }
+ outstanding_commands++;
+ }
+
+cleanup:
+
+ while (outstanding_commands) {
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr);
+ }
+ }
+}
+
+static void
+read_test_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands--;
+ spdk_dma_free(dev->data);
+
+ if (spdk_nvme_cpl_is_error(cpl) && dev->error_expected) {
+ if (cpl->status.sct != SPDK_NVME_SCT_MEDIA_ERROR ||
+ cpl->status.sc != SPDK_NVME_SC_UNRECOVERED_READ_ERROR) {
+ failed = 1;
+ }
+ printf("%s: read failed as expected\n", dev->name);
+ return;
+ }
+
+ if (!spdk_nvme_cpl_is_error(cpl) && !dev->error_expected) {
+ printf("%s: read successfully as expected\n", dev->name);
+ return;
+ }
+
+ failed = 1;
+}
+
+static void
+read_test(bool error_expected)
+{
+ struct dev *dev;
+
+ foreach_dev(dev) {
+ dev->error_expected = error_expected;
+ dev->data = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ if (!dev->data) {
+ failed = 1;
+ goto cleanup;
+ }
+
+ if (spdk_nvme_ns_cmd_read(dev->ns, dev->qpair, dev->data,
+ 0, 1, read_test_cb, dev, 0) != 0) {
+ printf("Error: failed to send Read command for dev=%p\n", dev);
+ failed = 1;
+ goto cleanup;
+ }
+
+ outstanding_commands++;
+ }
+
+cleanup:
+
+ while (outstanding_commands) {
+ foreach_dev(dev) {
+ spdk_nvme_qpair_process_completions(dev->qpair, 0);
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct dev *dev;
+ int i;
+ struct spdk_env_opts opts;
+ int rc;
+
+ spdk_env_opts_init(&opts);
+ opts.name = "err_injection";
+ opts.core_mask = "0x1";
+ opts.mem_size = 64;
+ opts.shm_id = 0;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ printf("NVMe Error Injection test\n");
+
+ if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "spdk_nvme_probe() failed\n");
+ return 1;
+ }
+
+ if (failed) {
+ goto exit;
+ }
+
+ if (!num_devs) {
+ printf("No NVMe controller found, %s exiting\n", argv[0]);
+ return 1;
+ }
+
+ foreach_dev(dev) {
+ /* Admin error injection at submission path */
+ rc = spdk_nvme_qpair_add_cmd_error_injection(dev->ctrlr, NULL,
+ SPDK_NVME_OPC_GET_FEATURES, true, 5000, 1,
+ SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_INVALID_FIELD);
+ failed += rc;
+ /* IO error injection at completion path */
+ rc = spdk_nvme_qpair_add_cmd_error_injection(dev->ctrlr, dev->qpair,
+ SPDK_NVME_OPC_READ, false, 0, 1,
+ SPDK_NVME_SCT_MEDIA_ERROR, SPDK_NVME_SC_UNRECOVERED_READ_ERROR);
+ failed += rc;
+ }
+
+ if (failed) {
+ goto exit;
+ }
+
+ /* Admin Get Feature, expect error return */
+ get_feature_test(true);
+ /* Admin Get Feature, expect successful return */
+ get_feature_test(false);
+ /* Read, expect error return */
+ read_test(true);
+ /* Read, expect successful return */
+ read_test(false);
+
+exit:
+ printf("Cleaning up...\n");
+ for (i = 0; i < num_devs; i++) {
+ struct dev *dev = &devs[i];
+ spdk_nvme_detach(dev->ctrlr);
+ }
+
+ return failed;
+}
diff --git a/src/spdk/test/nvme/hotplug.sh b/src/spdk/test/nvme/hotplug.sh
new file mode 100755
index 00000000..ca661b6f
--- /dev/null
+++ b/src/spdk/test/nvme/hotplug.sh
@@ -0,0 +1,147 @@
+#!/usr/bin/env bash
+
+set -xe
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/test/common/autotest_common.sh
+
+if [ -z "${DEPENDENCY_DIR}" ]; then
+ echo DEPENDENCY_DIR not defined!
+ exit 1
+fi
+
+function ssh_vm() {
+ sshpass -p "$password" ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -p 10022 root@localhost "$@"
+}
+
+function monitor_cmd() {
+ rc=0
+ if ! (echo "$@" | nc localhost 4444 > mon.log); then
+ rc=1
+ cat mon.log
+ fi
+ rm mon.log
+ return $rc
+}
+
+function get_online_devices_count() {
+ ssh_vm "lspci | grep -c NVM"
+}
+
+function wait_for_devices_ready() {
+ count=$(get_online_devices_count)
+
+ while [ $count -ne 4 ]; do
+ echo "waitting for all devices online"
+ count=$(get_online_devices_count)
+ done
+}
+
+function devices_initialization() {
+ timing_enter devices_initialization
+ dd if=/dev/zero of=/root/test0 bs=1M count=1024
+ dd if=/dev/zero of=/root/test1 bs=1M count=1024
+ dd if=/dev/zero of=/root/test2 bs=1M count=1024
+ dd if=/dev/zero of=/root/test3 bs=1M count=1024
+ monitor_cmd "drive_add 0 file=/root/test0,format=raw,id=drive0,if=none"
+ monitor_cmd "drive_add 1 file=/root/test1,format=raw,id=drive1,if=none"
+ monitor_cmd "drive_add 2 file=/root/test2,format=raw,id=drive2,if=none"
+ monitor_cmd "drive_add 3 file=/root/test3,format=raw,id=drive3,if=none"
+ timing_exit devices_initialization
+}
+
+function insert_devices() {
+ monitor_cmd "device_add nvme,drive=drive0,id=nvme0,serial=nvme0"
+ monitor_cmd "device_add nvme,drive=drive1,id=nvme1,serial=nvme1"
+ monitor_cmd "device_add nvme,drive=drive2,id=nvme2,serial=nvme2"
+ monitor_cmd "device_add nvme,drive=drive3,id=nvme3,serial=nvme3"
+ wait_for_devices_ready
+ ssh_vm "scripts/setup.sh"
+}
+
+function remove_devices() {
+ monitor_cmd "device_del nvme0"
+ monitor_cmd "device_del nvme1"
+ monitor_cmd "device_del nvme2"
+ monitor_cmd "device_del nvme3"
+}
+
+function devices_delete() {
+ timing_enter devices_delete
+ rm /root/test0
+ rm /root/test1
+ rm /root/test2
+ rm /root/test3
+ timing_exit devices_delete
+}
+
+password=$1
+base_img=${DEPENDENCY_DIR}/fedora24.img
+test_img=${DEPENDENCY_DIR}/fedora24_test.img
+qemu_pidfile=${DEPENDENCY_DIR}/qemupid
+
+if [ ! -e "$base_img" ]; then
+ echo "Hotplug VM image not found; skipping test"
+ exit 0
+fi
+
+timing_enter hotplug
+
+timing_enter start_qemu
+
+qemu-img create -b "$base_img" -f qcow2 "$test_img"
+
+qemu-system-x86_64 \
+ -daemonize -display none -m 8192 \
+ -pidfile "$qemu_pidfile" \
+ -hda "$test_img" \
+ -net user,hostfwd=tcp::10022-:22 \
+ -net nic \
+ -cpu host \
+ -smp cores=16,sockets=1 \
+ --enable-kvm \
+ -chardev socket,id=mon0,host=localhost,port=4444,server,nowait \
+ -mon chardev=mon0,mode=readline
+
+timing_exit start_qemu
+
+timing_enter wait_for_vm
+ssh_vm 'echo ready'
+timing_exit wait_for_vm
+
+timing_enter copy_repo
+(cd "$rootdir"; tar -cf - .) | (ssh_vm 'tar -xf -')
+timing_exit copy_repo
+
+devices_initialization
+insert_devices
+
+timing_enter hotplug_test
+
+ssh_vm "examples/nvme/hotplug/hotplug -i 0 -t 25 -n 4 -r 8" &
+example_pid=$!
+
+sleep 4
+remove_devices
+sleep 4
+insert_devices
+sleep 4
+remove_devices
+devices_delete
+
+timing_enter wait_for_example
+wait $example_pid
+timing_exit wait_for_example
+
+trap - SIGINT SIGTERM EXIT
+
+qemupid=`cat "$qemu_pidfile" | awk '{printf $0}'`
+kill -9 $qemupid
+rm "$qemu_pidfile"
+rm "$test_img"
+
+report_test_completion "nvme_hotplug"
+timing_exit hotplug_test
+
+timing_exit hotplug
diff --git a/src/spdk/test/nvme/nvme.sh b/src/spdk/test/nvme/nvme.sh
new file mode 100755
index 00000000..c52b926e
--- /dev/null
+++ b/src/spdk/test/nvme/nvme.sh
@@ -0,0 +1,187 @@
+#!/usr/bin/env bash
+
+set -e
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/scripts/common.sh
+source $rootdir/test/common/autotest_common.sh
+
+function get_nvme_name_from_bdf {
+ lsblk -d --output NAME
+ nvme_devs=$(lsblk -d --output NAME | grep "^nvme") || true
+ if [ -z "$nvme_devs" ]; then
+ return
+ fi
+ for dev in $nvme_devs; do
+ link_name=$(readlink /sys/block/$dev/device/device) || true
+ if [ -z "$link_name" ]; then
+ link_name=$(readlink /sys/block/$dev/device)
+ fi
+ bdf=$(basename "$link_name")
+ if [ "$bdf" = "$1" ]; then
+ eval "$2=$dev"
+ return
+ fi
+ done
+}
+
+timing_enter nvme
+
+if [ `uname` = Linux ]; then
+ # check that our setup.sh script does not bind NVMe devices to uio/vfio if they
+ # have an active mountpoint
+ $rootdir/scripts/setup.sh reset
+ # give kernel nvme driver some time to create the block devices before we start looking for them
+ sleep 1
+ blkname=''
+ # first, find an NVMe device that does not have an active mountpoint already;
+ # this covers rare case where someone is running this test script on a system
+ # that has a mounted NVMe filesystem
+ #
+ # note: more work probably needs to be done to properly handle devices with multiple
+ # namespaces
+ for bdf in $(iter_pci_class_code 01 08 02); do
+ get_nvme_name_from_bdf "$bdf" blkname
+ if [ "$blkname" != "" ]; then
+ mountpoints=$(lsblk /dev/$blkname --output MOUNTPOINT -n | wc -w)
+ if [ "$mountpoints" = "0" ]; then
+ break
+ else
+ blkname=''
+ fi
+ fi
+ done
+
+ # if we found an NVMe block device without an active mountpoint, create and mount
+ # a filesystem on it for purposes of testing the setup.sh script
+ if [ "$blkname" != "" ]; then
+ parted -s /dev/$blkname mklabel gpt
+ # just create a 100MB partition - this tests our ability to detect mountpoints
+ # on partitions of the device, not just the device itself; it also is faster
+ # since we don't trim and initialize the whole namespace
+ parted -s /dev/$blkname mkpart primary 1 100
+ sleep 1
+ mkfs.ext4 -F /dev/${blkname}p1
+ mkdir -p /tmp/nvmetest
+ mount /dev/${blkname}p1 /tmp/nvmetest
+ $rootdir/scripts/setup.sh
+ driver=$(basename $(readlink /sys/bus/pci/devices/$bdf/driver))
+ # check that the nvme driver is still loaded against the device
+ if [ "$driver" != "nvme" ]; then
+ exit 1
+ fi
+ umount /tmp/nvmetest
+ rmdir /tmp/nvmetest
+ # write zeroes to the device to blow away the partition table and filesystem
+ dd if=/dev/zero of=/dev/$blkname oflag=direct bs=1M count=1
+ $rootdir/scripts/setup.sh
+ driver=$(basename $(readlink /sys/bus/pci/devices/$bdf/driver))
+ # check that the nvme driver is not loaded against the device
+ if [ "$driver" = "nvme" ]; then
+ exit 1
+ fi
+ else
+ $rootdir/scripts/setup.sh
+ fi
+fi
+
+if [ `uname` = Linux ]; then
+ start_stub "-s 4096 -i 0 -m 0xF"
+ trap "kill_stub; exit 1" SIGINT SIGTERM EXIT
+fi
+
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ # TODO: temporarily disabled - temperature AER doesn't fire on emulated controllers
+ #timing_enter aer
+ #$testdir/aer/aer
+ #timing_exit aer
+
+ timing_enter reset
+ $testdir/reset/reset -q 64 -w write -s 4096 -t 2
+ report_test_completion "nightly_nvme_reset"
+ timing_exit reset
+fi
+
+timing_enter identify
+$rootdir/examples/nvme/identify/identify -i 0
+for bdf in $(iter_pci_class_code 01 08 02); do
+ $rootdir/examples/nvme/identify/identify -r "trtype:PCIe traddr:${bdf}" -i 0
+done
+timing_exit identify
+
+timing_enter perf
+$rootdir/examples/nvme/perf/perf -q 128 -w read -o 12288 -t 1 -LL -i 0
+if [ -b /dev/ram0 ]; then
+ # Test perf with AIO device
+ $rootdir/examples/nvme/perf/perf /dev/ram0 -q 128 -w read -o 12288 -t 1 -LL -i 0
+ report_test_completion "nvme_perf"
+fi
+timing_exit perf
+
+timing_enter reserve
+$rootdir/examples/nvme/reserve/reserve
+timing_exit reserve
+
+timing_enter hello_world
+$rootdir/examples/nvme/hello_world/hello_world
+timing_exit
+
+timing_enter deallocated_value
+$testdir/deallocated_value/deallocated_value
+timing_exit deallocated_value
+
+timing_enter sgl
+$testdir/sgl/sgl
+timing_exit sgl
+
+timing_enter e2edp
+$testdir/e2edp/nvme_dp
+timing_exit e2edp
+
+timing_enter err_injection
+$testdir/err_injection/err_injection
+timing_exit err_injection
+
+timing_enter overhead
+$testdir/overhead/overhead -s 4096 -t 1 -H
+timing_exit overhead
+
+timing_enter arbitration
+$rootdir/examples/nvme/arbitration/arbitration -t 3 -i 0
+timing_exit arbitration
+
+if [ `uname` = Linux ]; then
+ timing_enter multi_secondary
+ $rootdir/examples/nvme/perf/perf -i 0 -q 16 -w read -o 4096 -t 3 -c 0x1 &
+ pid0=$!
+ $rootdir/examples/nvme/perf/perf -i 0 -q 16 -w read -o 4096 -t 3 -c 0x2 &
+ pid1=$!
+ $rootdir/examples/nvme/perf/perf -i 0 -q 16 -w read -o 4096 -t 3 -c 0x4
+ wait $pid0
+ wait $pid1
+ report_test_completion "nvme_multi_secondary"
+ timing_exit multi_secondary
+fi
+
+if [ `uname` = Linux ]; then
+ trap - SIGINT SIGTERM EXIT
+ kill_stub
+fi
+PLUGIN_DIR=$rootdir/examples/nvme/fio_plugin
+
+if [ -d /usr/src/fio ]; then
+ timing_enter fio_plugin
+ for bdf in $(iter_pci_class_code 01 08 02); do
+ # Only test when ASAN is not enabled. If ASAN is enabled, we cannot test.
+ if [ $SPDK_RUN_ASAN -eq 0 ]; then
+ LD_PRELOAD=$PLUGIN_DIR/fio_plugin /usr/src/fio/fio $PLUGIN_DIR/example_config.fio --filename="trtype=PCIe traddr=${bdf//:/.} ns=1"
+ report_test_completion "bdev_fio"
+ fi
+ break
+ done
+
+ timing_exit fio_plugin
+fi
+
+timing_exit nvme
diff --git a/src/spdk/test/nvme/overhead/.gitignore b/src/spdk/test/nvme/overhead/.gitignore
new file mode 100644
index 00000000..d5a7d6f4
--- /dev/null
+++ b/src/spdk/test/nvme/overhead/.gitignore
@@ -0,0 +1 @@
+overhead
diff --git a/src/spdk/test/nvme/overhead/Makefile b/src/spdk/test/nvme/overhead/Makefile
new file mode 100644
index 00000000..bcb7f38d
--- /dev/null
+++ b/src/spdk/test/nvme/overhead/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
+
+APP = overhead
+
+ifeq ($(OS),Linux)
+SYS_LIBS += -laio
+CFLAGS += -DHAVE_LIBAIO
+endif
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/overhead/README b/src/spdk/test/nvme/overhead/README
new file mode 100644
index 00000000..b88c4217
--- /dev/null
+++ b/src/spdk/test/nvme/overhead/README
@@ -0,0 +1,24 @@
+This application measures the software overhead of I/O submission
+and completion for both the SPDK NVMe driver and an AIO file handle.
+It runs a random read, queue depth = 1 workload to a single device,
+and captures TSC as follows:
+
+* Submission: capture TSC before and after the I/O submission
+ call (SPDK or AIO).
+* Completion: capture TSC before and after the I/O completion
+ check. Only record the TSC delta if the I/O completion check
+ resulted in a completed I/O. Also use heuristics in the AIO
+ case to account for time spent in interrupt handling outside
+ of the actual I/O completion check.
+
+Usage:
+
+To test software overhead for a 4KB I/O over a 10 second period:
+
+SPDK: overhead -s 4096 -t 10
+AIO: overhead -s 4096 -t 10 /dev/nvme0n1
+
+Note that for the SPDK case, it will only use the first namespace
+on the first controller found by SPDK. If a different namespace is
+desired, attach controllers individually to the kernel NVMe driver
+to ensure they will not be enumerated by SPDK.
diff --git a/src/spdk/test/nvme/overhead/overhead.c b/src/spdk/test/nvme/overhead/overhead.c
new file mode 100644
index 00000000..f35247a2
--- /dev/null
+++ b/src/spdk/test/nvme/overhead/overhead.c
@@ -0,0 +1,720 @@
+/*-
+ * 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/barrier.h"
+#include "spdk/fd.h"
+#include "spdk/nvme.h"
+#include "spdk/env.h"
+#include "spdk/string.h"
+#include "spdk/nvme_intel.h"
+#include "spdk/histogram_data.h"
+
+#if HAVE_LIBAIO
+#include <libaio.h>
+#endif
+
+struct ctrlr_entry {
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct ctrlr_entry *next;
+ char name[1024];
+};
+
+enum entry_type {
+ ENTRY_TYPE_NVME_NS,
+ ENTRY_TYPE_AIO_FILE,
+};
+
+struct ns_entry {
+ enum entry_type type;
+
+ union {
+ struct {
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct spdk_nvme_ns *ns;
+ struct spdk_nvme_qpair *qpair;
+ } nvme;
+#if HAVE_LIBAIO
+ struct {
+ int fd;
+ struct io_event *events;
+ io_context_t ctx;
+ } aio;
+#endif
+ } u;
+
+ uint32_t io_size_blocks;
+ uint64_t size_in_ios;
+ bool is_draining;
+ uint32_t current_queue_depth;
+ char name[1024];
+ struct ns_entry *next;
+
+ struct spdk_histogram_data *submit_histogram;
+ struct spdk_histogram_data *complete_histogram;
+};
+
+struct perf_task {
+ void *buf;
+ uint64_t submit_tsc;
+#if HAVE_LIBAIO
+ struct iocb iocb;
+#endif
+};
+
+static bool g_enable_histogram = false;
+
+static struct ctrlr_entry *g_ctrlr = NULL;
+static struct ns_entry *g_ns = NULL;
+
+static uint64_t g_tsc_rate;
+
+static uint32_t g_io_size_bytes;
+static int g_time_in_sec;
+
+static int g_aio_optind; /* Index of first AIO filename in argv */
+
+struct perf_task *g_task;
+uint64_t g_tsc_submit = 0;
+uint64_t g_tsc_submit_min = UINT64_MAX;
+uint64_t g_tsc_submit_max = 0;
+uint64_t g_tsc_complete = 0;
+uint64_t g_tsc_complete_min = UINT64_MAX;
+uint64_t g_tsc_complete_max = 0;
+uint64_t g_io_completed = 0;
+
+static void
+register_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns *ns)
+{
+ struct ns_entry *entry;
+ const struct spdk_nvme_ctrlr_data *cdata;
+
+ cdata = spdk_nvme_ctrlr_get_data(ctrlr);
+
+ if (!spdk_nvme_ns_is_active(ns)) {
+ printf("Controller %-20.20s (%-20.20s): Skipping inactive NS %u\n",
+ cdata->mn, cdata->sn,
+ spdk_nvme_ns_get_id(ns));
+ return;
+ }
+
+ if (spdk_nvme_ns_get_size(ns) < g_io_size_bytes ||
+ spdk_nvme_ns_get_sector_size(ns) > g_io_size_bytes) {
+ printf("WARNING: controller %-20.20s (%-20.20s) ns %u has invalid "
+ "ns size %" PRIu64 " / block size %u for I/O size %u\n",
+ cdata->mn, cdata->sn, spdk_nvme_ns_get_id(ns),
+ spdk_nvme_ns_get_size(ns), spdk_nvme_ns_get_sector_size(ns), g_io_size_bytes);
+ return;
+ }
+
+ entry = calloc(1, sizeof(struct ns_entry));
+ if (entry == NULL) {
+ perror("ns_entry malloc");
+ exit(1);
+ }
+
+ entry->type = ENTRY_TYPE_NVME_NS;
+ entry->u.nvme.ctrlr = ctrlr;
+ entry->u.nvme.ns = ns;
+
+ entry->size_in_ios = spdk_nvme_ns_get_size(ns) /
+ g_io_size_bytes;
+ entry->io_size_blocks = g_io_size_bytes / spdk_nvme_ns_get_sector_size(ns);
+ entry->submit_histogram = spdk_histogram_data_alloc();
+ entry->complete_histogram = spdk_histogram_data_alloc();
+
+ snprintf(entry->name, 44, "%-20.20s (%-20.20s)", cdata->mn, cdata->sn);
+
+ entry->next = g_ns;
+ g_ns = entry;
+}
+
+static void
+register_ctrlr(struct spdk_nvme_ctrlr *ctrlr)
+{
+ int num_ns;
+ struct ctrlr_entry *entry = malloc(sizeof(struct ctrlr_entry));
+ const struct spdk_nvme_ctrlr_data *cdata = spdk_nvme_ctrlr_get_data(ctrlr);
+
+ if (entry == NULL) {
+ perror("ctrlr_entry malloc");
+ exit(1);
+ }
+
+ snprintf(entry->name, sizeof(entry->name), "%-20.20s (%-20.20s)", cdata->mn, cdata->sn);
+
+ entry->ctrlr = ctrlr;
+
+ entry->next = g_ctrlr;
+ g_ctrlr = entry;
+
+ num_ns = spdk_nvme_ctrlr_get_num_ns(ctrlr);
+ /* Only register the first namespace. */
+ if (num_ns < 1) {
+ fprintf(stderr, "controller found with no namespaces\n");
+ exit(1);
+ }
+
+ register_ns(ctrlr, spdk_nvme_ctrlr_get_ns(ctrlr, 1));
+}
+
+#if HAVE_LIBAIO
+static int
+register_aio_file(const char *path)
+{
+ struct ns_entry *entry;
+
+ int fd;
+ uint64_t size;
+ uint32_t blklen;
+
+ fd = open(path, O_RDWR | O_DIRECT);
+ if (fd < 0) {
+ fprintf(stderr, "Could not open AIO device %s: %s\n", path, strerror(errno));
+ return -1;
+ }
+
+ size = spdk_fd_get_size(fd);
+ if (size == 0) {
+ fprintf(stderr, "Could not determine size of AIO device %s\n", path);
+ close(fd);
+ return -1;
+ }
+
+ blklen = spdk_fd_get_blocklen(fd);
+ if (blklen == 0) {
+ fprintf(stderr, "Could not determine block size of AIO device %s\n", path);
+ close(fd);
+ return -1;
+ }
+
+ entry = calloc(1, sizeof(struct ns_entry));
+ if (entry == NULL) {
+ close(fd);
+ perror("aio ns_entry malloc");
+ return -1;
+ }
+
+ entry->type = ENTRY_TYPE_AIO_FILE;
+ entry->u.aio.fd = fd;
+ entry->size_in_ios = size / g_io_size_bytes;
+ entry->io_size_blocks = g_io_size_bytes / blklen;
+ entry->submit_histogram = spdk_histogram_data_alloc();
+ entry->complete_histogram = spdk_histogram_data_alloc();
+
+ snprintf(entry->name, sizeof(entry->name), "%s", path);
+
+ g_ns = entry;
+
+ return 0;
+}
+
+static int
+aio_submit(io_context_t aio_ctx, struct iocb *iocb, int fd, enum io_iocb_cmd cmd, void *buf,
+ unsigned long nbytes, uint64_t offset, void *cb_ctx)
+{
+ iocb->aio_fildes = fd;
+ iocb->aio_reqprio = 0;
+ iocb->aio_lio_opcode = cmd;
+ iocb->u.c.buf = buf;
+ iocb->u.c.nbytes = nbytes;
+ iocb->u.c.offset = offset;
+ iocb->data = cb_ctx;
+
+ if (io_submit(aio_ctx, 1, &iocb) < 0) {
+ printf("io_submit");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+aio_check_io(void)
+{
+ int count, i;
+ struct timespec timeout;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 0;
+
+ count = io_getevents(g_ns->u.aio.ctx, 1, 1, g_ns->u.aio.events, &timeout);
+ if (count < 0) {
+ fprintf(stderr, "io_getevents error\n");
+ exit(1);
+ }
+
+ for (i = 0; i < count; i++) {
+ g_ns->current_queue_depth--;
+ }
+}
+#endif /* HAVE_LIBAIO */
+
+static void io_complete(void *ctx, const struct spdk_nvme_cpl *completion);
+
+static __thread unsigned int seed = 0;
+
+static void
+submit_single_io(void)
+{
+ uint64_t offset_in_ios;
+ uint64_t start;
+ int rc;
+ struct ns_entry *entry = g_ns;
+ uint64_t tsc_submit;
+
+ offset_in_ios = rand_r(&seed) % entry->size_in_ios;
+
+ start = spdk_get_ticks();
+ spdk_rmb();
+#if HAVE_LIBAIO
+ if (entry->type == ENTRY_TYPE_AIO_FILE) {
+ rc = aio_submit(g_ns->u.aio.ctx, &g_task->iocb, entry->u.aio.fd, IO_CMD_PREAD, g_task->buf,
+ g_io_size_bytes, offset_in_ios * g_io_size_bytes, g_task);
+ } else
+#endif
+ {
+ rc = spdk_nvme_ns_cmd_read(entry->u.nvme.ns, g_ns->u.nvme.qpair, g_task->buf,
+ offset_in_ios * entry->io_size_blocks,
+ entry->io_size_blocks, io_complete, g_task, 0);
+ }
+
+ spdk_rmb();
+ tsc_submit = spdk_get_ticks() - start;
+ g_tsc_submit += tsc_submit;
+ if (tsc_submit < g_tsc_submit_min) {
+ g_tsc_submit_min = tsc_submit;
+ }
+ if (tsc_submit > g_tsc_submit_max) {
+ g_tsc_submit_max = tsc_submit;
+ }
+ if (g_enable_histogram) {
+ spdk_histogram_data_tally(entry->submit_histogram, tsc_submit);
+ }
+
+ if (rc != 0) {
+ fprintf(stderr, "starting I/O failed\n");
+ }
+
+ g_ns->current_queue_depth++;
+}
+
+static void
+io_complete(void *ctx, const struct spdk_nvme_cpl *completion)
+{
+ g_ns->current_queue_depth--;
+}
+
+uint64_t g_complete_tsc_start;
+
+static uint64_t
+check_io(void)
+{
+ uint64_t end, tsc_complete;
+
+ spdk_rmb();
+#if HAVE_LIBAIO
+ if (g_ns->type == ENTRY_TYPE_AIO_FILE) {
+ aio_check_io();
+ } else
+#endif
+ {
+ spdk_nvme_qpair_process_completions(g_ns->u.nvme.qpair, 0);
+ }
+ spdk_rmb();
+ end = spdk_get_ticks();
+ if (g_ns->current_queue_depth == 1) {
+ /*
+ * Account for race condition in AIO case where interrupt occurs
+ * after checking for queue depth. If the timestamp capture
+ * is too big compared to the last capture, assume that an
+ * interrupt fired, and do not bump the start tsc forward. This
+ * will ensure this extra time is accounted for next time through
+ * when we see current_queue_depth drop to 0.
+ */
+ if (g_ns->type == ENTRY_TYPE_NVME_NS || (end - g_complete_tsc_start) < 500) {
+ g_complete_tsc_start = end;
+ }
+ } else {
+ tsc_complete = end - g_complete_tsc_start;
+ g_tsc_complete += tsc_complete;
+ if (tsc_complete < g_tsc_complete_min) {
+ g_tsc_complete_min = tsc_complete;
+ }
+ if (tsc_complete > g_tsc_complete_max) {
+ g_tsc_complete_max = tsc_complete;
+ }
+ if (g_enable_histogram) {
+ spdk_histogram_data_tally(g_ns->complete_histogram, tsc_complete);
+ }
+ g_io_completed++;
+ if (!g_ns->is_draining) {
+ submit_single_io();
+ }
+ end = g_complete_tsc_start = spdk_get_ticks();
+ }
+
+ return end;
+}
+
+static void
+drain_io(void)
+{
+ g_ns->is_draining = true;
+ while (g_ns->current_queue_depth > 0) {
+ check_io();
+ }
+}
+
+static int
+init_ns_worker_ctx(void)
+{
+ if (g_ns->type == ENTRY_TYPE_AIO_FILE) {
+#ifdef HAVE_LIBAIO
+ g_ns->u.aio.events = calloc(1, sizeof(struct io_event));
+ if (!g_ns->u.aio.events) {
+ return -1;
+ }
+ g_ns->u.aio.ctx = 0;
+ if (io_setup(1, &g_ns->u.aio.ctx) < 0) {
+ free(g_ns->u.aio.events);
+ perror("io_setup");
+ return -1;
+ }
+#endif
+ } else {
+ /*
+ * TODO: If a controller has multiple namespaces, they could all use the same queue.
+ * For now, give each namespace/thread combination its own queue.
+ */
+ g_ns->u.nvme.qpair = spdk_nvme_ctrlr_alloc_io_qpair(g_ns->u.nvme.ctrlr, NULL, 0);
+ if (!g_ns->u.nvme.qpair) {
+ printf("ERROR: spdk_nvme_ctrlr_alloc_io_qpair failed\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void
+cleanup_ns_worker_ctx(void)
+{
+ if (g_ns->type == ENTRY_TYPE_AIO_FILE) {
+#ifdef HAVE_LIBAIO
+ io_destroy(g_ns->u.aio.ctx);
+ free(g_ns->u.aio.events);
+#endif
+ } else {
+ spdk_nvme_ctrlr_free_io_qpair(g_ns->u.nvme.qpair);
+ }
+}
+
+static int
+work_fn(void)
+{
+ uint64_t tsc_end, current;
+
+ /* Allocate a queue pair for each namespace. */
+ if (init_ns_worker_ctx() != 0) {
+ printf("ERROR: init_ns_worker_ctx() failed\n");
+ return 1;
+ }
+
+ tsc_end = spdk_get_ticks() + g_time_in_sec * g_tsc_rate;
+
+ /* Submit initial I/O for each namespace. */
+ submit_single_io();
+ g_complete_tsc_start = spdk_get_ticks();
+
+ while (1) {
+ /*
+ * Check for completed I/O for each controller. A new
+ * I/O will be submitted in the io_complete callback
+ * to replace each I/O that is completed.
+ */
+ current = check_io();
+
+ if (current > tsc_end) {
+ break;
+ }
+ }
+
+ drain_io();
+ cleanup_ns_worker_ctx();
+
+ return 0;
+}
+
+static void usage(char *program_name)
+{
+ printf("%s options", program_name);
+#if HAVE_LIBAIO
+ printf(" [AIO device(s)]...");
+#endif
+ printf("\n");
+ printf("\t[-s io size in bytes]\n");
+ printf("\t[-t time in seconds]\n");
+ printf("\t\t(default: 1)]\n");
+ printf("\t[-H enable histograms]\n");
+}
+
+static void
+print_bucket(void *ctx, uint64_t start, uint64_t end, uint64_t count,
+ uint64_t total, uint64_t so_far)
+{
+ double so_far_pct;
+
+ if (count == 0) {
+ return;
+ }
+
+ so_far_pct = (double)so_far * 100 / total;
+
+ printf("%9.3f - %9.3f: %9.4f%% (%9ju)\n",
+ (double)start * 1000 * 1000 / g_tsc_rate,
+ (double)end * 1000 * 1000 / g_tsc_rate,
+ so_far_pct, count);
+}
+
+static void
+print_stats(void)
+{
+ double divisor = (double)g_tsc_rate / (1000 * 1000 * 1000);
+
+ printf("submit (in ns) avg, min, max = %8.1f, %8.1f, %8.1f\n",
+ (double)g_tsc_submit / g_io_completed / divisor,
+ (double)g_tsc_submit_min / divisor,
+ (double)g_tsc_submit_max / divisor);
+ printf("complete (in ns) avg, min, max = %8.1f, %8.1f, %8.1f\n",
+ (double)g_tsc_complete / g_io_completed / divisor,
+ (double)g_tsc_complete_min / divisor,
+ (double)g_tsc_complete_max / divisor);
+
+ if (!g_enable_histogram) {
+ return;
+ }
+
+ printf("\n");
+ printf("Submit histogram\n");
+ printf("================\n");
+ printf(" Range in us Cumulative Count\n");
+ spdk_histogram_data_iterate(g_ns->submit_histogram, print_bucket, NULL);
+ printf("\n");
+
+ printf("Complete histogram\n");
+ printf("==================\n");
+ printf(" Range in us Cumulative Count\n");
+ spdk_histogram_data_iterate(g_ns->complete_histogram, print_bucket, NULL);
+ printf("\n");
+
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ int op;
+
+ /* default value */
+ g_io_size_bytes = 0;
+ g_time_in_sec = 0;
+
+ while ((op = getopt(argc, argv, "hs:t:H")) != -1) {
+ switch (op) {
+ case 'h':
+ usage(argv[0]);
+ exit(0);
+ break;
+ case 's':
+ g_io_size_bytes = atoi(optarg);
+ break;
+ case 't':
+ g_time_in_sec = atoi(optarg);
+ break;
+ case 'H':
+ g_enable_histogram = true;
+ break;
+ default:
+ usage(argv[0]);
+ return 1;
+ }
+ }
+
+ if (!g_io_size_bytes) {
+ usage(argv[0]);
+ return 1;
+ }
+ if (!g_time_in_sec) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ g_aio_optind = optind;
+
+ return 0;
+}
+
+static bool
+probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
+ struct spdk_nvme_ctrlr_opts *opts)
+{
+ static uint32_t ctrlr_found = 0;
+
+ if (ctrlr_found == 1) {
+ fprintf(stderr, "only attaching to one controller, so skipping\n");
+ fprintf(stderr, " controller at PCI address %s\n",
+ trid->traddr);
+ return false;
+ }
+ ctrlr_found = 1;
+
+ printf("Attaching to %s\n", trid->traddr);
+
+ 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)
+{
+ printf("Attached to %s\n", trid->traddr);
+
+ register_ctrlr(ctrlr);
+}
+
+static int
+register_controllers(void)
+{
+ printf("Initializing NVMe Controllers\n");
+
+ if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "spdk_nvme_probe() failed\n");
+ return 1;
+ }
+
+ if (g_ns == NULL) {
+ fprintf(stderr, "no NVMe controller found - check that device is bound to uio/vfio\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+cleanup(void)
+{
+ struct ns_entry *ns_entry = g_ns;
+ struct ctrlr_entry *ctrlr_entry = g_ctrlr;
+
+ while (ns_entry) {
+ struct ns_entry *next = ns_entry->next;
+
+ spdk_histogram_data_free(ns_entry->submit_histogram);
+ spdk_histogram_data_free(ns_entry->complete_histogram);
+ free(ns_entry);
+ ns_entry = next;
+ }
+
+ while (ctrlr_entry) {
+ struct ctrlr_entry *next = ctrlr_entry->next;
+
+ spdk_nvme_detach(ctrlr_entry->ctrlr);
+ free(ctrlr_entry);
+ ctrlr_entry = next;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int rc;
+ struct spdk_env_opts opts;
+
+ rc = parse_args(argc, argv);
+ if (rc != 0) {
+ return rc;
+ }
+
+ spdk_env_opts_init(&opts);
+ opts.name = "overhead";
+ opts.core_mask = "0x1";
+ opts.shm_id = 0;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ g_task = spdk_dma_zmalloc(sizeof(struct perf_task), 0, NULL);
+ if (g_task == NULL) {
+ fprintf(stderr, "g_task alloc failed\n");
+ exit(1);
+ }
+
+ g_task->buf = spdk_dma_zmalloc(g_io_size_bytes, 0x1000, NULL);
+ if (g_task->buf == NULL) {
+ fprintf(stderr, "g_task->buf spdk_dma_zmalloc failed\n");
+ exit(1);
+ }
+
+ g_tsc_rate = spdk_get_ticks_hz();
+
+#if HAVE_LIBAIO
+ if (g_aio_optind < argc) {
+ printf("Measuring overhead for AIO device %s.\n", argv[g_aio_optind]);
+ if (register_aio_file(argv[g_aio_optind]) != 0) {
+ cleanup();
+ return -1;
+ }
+ } else
+#endif
+ {
+ if (register_controllers() != 0) {
+ cleanup();
+ return -1;
+ }
+ }
+
+ printf("Initialization complete. Launching workers.\n");
+
+ rc = work_fn();
+
+ print_stats();
+
+ cleanup();
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: errors occured\n", argv[0]);
+ }
+
+ return rc;
+}
diff --git a/src/spdk/test/nvme/reset/.gitignore b/src/spdk/test/nvme/reset/.gitignore
new file mode 100644
index 00000000..a16781b1
--- /dev/null
+++ b/src/spdk/test/nvme/reset/.gitignore
@@ -0,0 +1 @@
+reset
diff --git a/src/spdk/test/nvme/reset/Makefile b/src/spdk/test/nvme/reset/Makefile
new file mode 100644
index 00000000..440f385c
--- /dev/null
+++ b/src/spdk/test/nvme/reset/Makefile
@@ -0,0 +1,39 @@
+#
+# 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 = reset
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/reset/reset.c b/src/spdk/test/nvme/reset/reset.c
new file mode 100644
index 00000000..fe2004e8
--- /dev/null
+++ b/src/spdk/test/nvme/reset/reset.c
@@ -0,0 +1,689 @@
+/*-
+ * 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/nvme.h"
+#include "spdk/env.h"
+#include "spdk/string.h"
+
+struct ctrlr_entry {
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct ctrlr_entry *next;
+ char name[1024];
+};
+
+struct ns_entry {
+ struct spdk_nvme_ns *ns;
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct ns_entry *next;
+ uint32_t io_size_blocks;
+ uint64_t size_in_ios;
+ char name[1024];
+};
+
+struct ns_worker_ctx {
+ struct ns_entry *entry;
+ struct spdk_nvme_qpair *qpair;
+ uint64_t io_completed;
+ uint64_t io_completed_error;
+ uint64_t io_submitted;
+ uint64_t current_queue_depth;
+ uint64_t offset_in_ios;
+ bool is_draining;
+
+ struct ns_worker_ctx *next;
+};
+
+struct reset_task {
+ struct ns_worker_ctx *ns_ctx;
+ void *buf;
+};
+
+struct worker_thread {
+ struct ns_worker_ctx *ns_ctx;
+ unsigned lcore;
+};
+
+static struct spdk_mempool *task_pool;
+
+static struct ctrlr_entry *g_controllers = NULL;
+static struct ns_entry *g_namespaces = NULL;
+static int g_num_namespaces = 0;
+static struct worker_thread *g_workers = NULL;
+
+static uint64_t g_tsc_rate;
+
+static int g_io_size_bytes;
+static int g_rw_percentage;
+static int g_is_random;
+static int g_queue_depth;
+static int g_time_in_sec;
+
+#define TASK_POOL_NUM 8192
+
+static void
+register_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns *ns)
+{
+ struct ns_entry *entry;
+ const struct spdk_nvme_ctrlr_data *cdata;
+
+ if (!spdk_nvme_ns_is_active(ns)) {
+ printf("Skipping inactive NS %u\n", spdk_nvme_ns_get_id(ns));
+ return;
+ }
+
+ entry = malloc(sizeof(struct ns_entry));
+ if (entry == NULL) {
+ perror("ns_entry malloc");
+ exit(1);
+ }
+
+ cdata = spdk_nvme_ctrlr_get_data(ctrlr);
+
+ entry->ns = ns;
+ entry->ctrlr = ctrlr;
+ entry->size_in_ios = spdk_nvme_ns_get_size(ns) /
+ g_io_size_bytes;
+ entry->io_size_blocks = g_io_size_bytes / spdk_nvme_ns_get_sector_size(ns);
+
+ snprintf(entry->name, 44, "%-20.20s (%-20.20s)", cdata->mn, cdata->sn);
+
+ g_num_namespaces++;
+ entry->next = g_namespaces;
+ g_namespaces = entry;
+}
+
+static void
+register_ctrlr(struct spdk_nvme_ctrlr *ctrlr)
+{
+ int nsid, num_ns;
+ struct spdk_nvme_ns *ns;
+ struct ctrlr_entry *entry = malloc(sizeof(struct ctrlr_entry));
+
+ if (entry == NULL) {
+ perror("ctrlr_entry malloc");
+ exit(1);
+ }
+
+ entry->ctrlr = ctrlr;
+ entry->next = g_controllers;
+ g_controllers = entry;
+
+ num_ns = spdk_nvme_ctrlr_get_num_ns(ctrlr);
+ for (nsid = 1; nsid <= num_ns; nsid++) {
+ ns = spdk_nvme_ctrlr_get_ns(ctrlr, nsid);
+ if (ns == NULL) {
+ continue;
+ }
+ register_ns(ctrlr, ns);
+ }
+}
+
+static void io_complete(void *ctx, const struct spdk_nvme_cpl *completion);
+
+static __thread unsigned int seed = 0;
+
+static void
+submit_single_io(struct ns_worker_ctx *ns_ctx)
+{
+ struct reset_task *task = NULL;
+ uint64_t offset_in_ios;
+ int rc;
+ struct ns_entry *entry = ns_ctx->entry;
+
+ task = spdk_mempool_get(task_pool);
+ if (!task) {
+ fprintf(stderr, "Failed to get task from task_pool\n");
+ exit(1);
+ }
+
+ task->buf = spdk_dma_zmalloc(g_io_size_bytes, 0x200, NULL);
+ if (!task->buf) {
+ spdk_dma_free(task->buf);
+ fprintf(stderr, "task->buf spdk_dma_zmalloc failed\n");
+ exit(1);
+ }
+
+ task->ns_ctx = ns_ctx;
+ task->ns_ctx->io_submitted++;
+
+ if (g_is_random) {
+ offset_in_ios = rand_r(&seed) % entry->size_in_ios;
+ } else {
+ offset_in_ios = ns_ctx->offset_in_ios++;
+ if (ns_ctx->offset_in_ios == entry->size_in_ios) {
+ ns_ctx->offset_in_ios = 0;
+ }
+ }
+
+ if ((g_rw_percentage == 100) ||
+ (g_rw_percentage != 0 && ((rand_r(&seed) % 100) < g_rw_percentage))) {
+ rc = spdk_nvme_ns_cmd_read(entry->ns, ns_ctx->qpair, task->buf,
+ offset_in_ios * entry->io_size_blocks,
+ entry->io_size_blocks, io_complete, task, 0);
+ } else {
+ rc = spdk_nvme_ns_cmd_write(entry->ns, ns_ctx->qpair, task->buf,
+ offset_in_ios * entry->io_size_blocks,
+ entry->io_size_blocks, io_complete, task, 0);
+ }
+
+ if (rc != 0) {
+ fprintf(stderr, "starting I/O failed\n");
+ }
+
+ ns_ctx->current_queue_depth++;
+}
+
+static void
+task_complete(struct reset_task *task, const struct spdk_nvme_cpl *completion)
+{
+ struct ns_worker_ctx *ns_ctx;
+
+ ns_ctx = task->ns_ctx;
+ ns_ctx->current_queue_depth--;
+
+ if (spdk_nvme_cpl_is_error(completion)) {
+ ns_ctx->io_completed_error++;
+ } else {
+ ns_ctx->io_completed++;
+ }
+
+ spdk_dma_free(task->buf);
+ spdk_mempool_put(task_pool, task);
+
+ /*
+ * 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 (!ns_ctx->is_draining) {
+ submit_single_io(ns_ctx);
+ }
+}
+
+static void
+io_complete(void *ctx, const struct spdk_nvme_cpl *completion)
+{
+ task_complete((struct reset_task *)ctx, completion);
+}
+
+static void
+check_io(struct ns_worker_ctx *ns_ctx)
+{
+ spdk_nvme_qpair_process_completions(ns_ctx->qpair, 0);
+}
+
+static void
+submit_io(struct ns_worker_ctx *ns_ctx, int queue_depth)
+{
+ while (queue_depth-- > 0) {
+ submit_single_io(ns_ctx);
+ }
+}
+
+static void
+drain_io(struct ns_worker_ctx *ns_ctx)
+{
+ ns_ctx->is_draining = true;
+ while (ns_ctx->current_queue_depth > 0) {
+ check_io(ns_ctx);
+ }
+}
+
+static int
+work_fn(void *arg)
+{
+ uint64_t tsc_end = spdk_get_ticks() + g_time_in_sec * g_tsc_rate;
+ struct worker_thread *worker = (struct worker_thread *)arg;
+ struct ns_worker_ctx *ns_ctx = NULL;
+ bool did_reset = false;
+
+ printf("Starting thread on core %u\n", worker->lcore);
+
+ /* Submit initial I/O for each namespace. */
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx != NULL) {
+ ns_ctx->qpair = spdk_nvme_ctrlr_alloc_io_qpair(ns_ctx->entry->ctrlr, NULL, 0);
+ if (ns_ctx->qpair == NULL) {
+ fprintf(stderr, "spdk_nvme_ctrlr_alloc_io_qpair() failed on core %u\n", worker->lcore);
+ return -1;
+ }
+ submit_io(ns_ctx, g_queue_depth);
+ ns_ctx = ns_ctx->next;
+ }
+
+ while (1) {
+ /*
+ * Check for completed I/O for each controller. A new
+ * I/O will be submitted in the io_complete callback
+ * to replace each I/O that is completed.
+ */
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx != NULL) {
+ check_io(ns_ctx);
+ ns_ctx = ns_ctx->next;
+ }
+
+ if (!did_reset && ((tsc_end - spdk_get_ticks()) / g_tsc_rate) > (uint64_t)g_time_in_sec / 2) {
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx != NULL) {
+ if (spdk_nvme_ctrlr_reset(ns_ctx->entry->ctrlr) < 0) {
+ fprintf(stderr, "nvme reset failed.\n");
+ return -1;
+ }
+ ns_ctx = ns_ctx->next;
+ }
+ did_reset = true;
+ }
+
+ if (spdk_get_ticks() > tsc_end) {
+ break;
+ }
+ }
+
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx != NULL) {
+ drain_io(ns_ctx);
+ spdk_nvme_ctrlr_free_io_qpair(ns_ctx->qpair);
+ ns_ctx = ns_ctx->next;
+ }
+
+ return 0;
+}
+
+static void usage(char *program_name)
+{
+ printf("%s options", program_name);
+ printf("\n");
+ printf("\t[-q io depth]\n");
+ printf("\t[-s io size in bytes]\n");
+ printf("\t[-w io pattern type, must be one of\n");
+ printf("\t\t(read, write, randread, randwrite, rw, randrw)]\n");
+ printf("\t[-M rwmixread (100 for reads, 0 for writes)]\n");
+ printf("\t[-t time in seconds(should be larger than 15 seconds)]\n");
+ printf("\t[-m max completions per poll]\n");
+ printf("\t\t(default:0 - unlimited)\n");
+}
+
+static int
+print_stats(void)
+{
+ uint64_t io_completed, io_submitted, io_completed_error;
+ uint64_t total_completed_io, total_submitted_io, total_completed_err_io;
+ struct worker_thread *worker;
+ struct ns_worker_ctx *ns_ctx;
+
+ total_completed_io = 0;
+ total_submitted_io = 0;
+ total_completed_err_io = 0;
+
+ worker = g_workers;
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx) {
+ io_completed = ns_ctx->io_completed;
+ io_submitted = ns_ctx->io_submitted;
+ io_completed_error = ns_ctx->io_completed_error;
+ total_completed_io += io_completed;
+ total_submitted_io += io_submitted;
+ total_completed_err_io += io_completed_error;
+ ns_ctx = ns_ctx->next;
+ }
+
+ printf("========================================================\n");
+ printf("%16lu IO completed successfully\n", total_completed_io);
+ printf("%16lu IO completed with error\n", total_completed_err_io);
+ printf("--------------------------------------------------------\n");
+ printf("%16lu IO completed total\n", total_completed_io + total_completed_err_io);
+ printf("%16lu IO submitted\n", total_submitted_io);
+
+ if (total_submitted_io != (total_completed_io + total_completed_err_io)) {
+ fprintf(stderr, "Some IO are missing......\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ const char *workload_type;
+ int op;
+ bool mix_specified = false;
+
+ /* default value */
+ g_queue_depth = 0;
+ g_io_size_bytes = 0;
+ workload_type = NULL;
+ g_time_in_sec = 0;
+ g_rw_percentage = -1;
+
+ while ((op = getopt(argc, argv, "m:q:s:t:w:M:")) != -1) {
+ switch (op) {
+ case 'q':
+ g_queue_depth = atoi(optarg);
+ break;
+ case 's':
+ g_io_size_bytes = atoi(optarg);
+ break;
+ case 't':
+ g_time_in_sec = atoi(optarg);
+ break;
+ case 'w':
+ workload_type = optarg;
+ break;
+ case 'M':
+ g_rw_percentage = atoi(optarg);
+ mix_specified = true;
+ break;
+ default:
+ usage(argv[0]);
+ return 1;
+ }
+ }
+
+ if (!g_queue_depth) {
+ usage(argv[0]);
+ return 1;
+ }
+ if (!g_io_size_bytes) {
+ usage(argv[0]);
+ return 1;
+ }
+ if (!workload_type) {
+ usage(argv[0]);
+ return 1;
+ }
+ if (!g_time_in_sec) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ if (strcmp(workload_type, "read") &&
+ strcmp(workload_type, "write") &&
+ strcmp(workload_type, "randread") &&
+ strcmp(workload_type, "randwrite") &&
+ strcmp(workload_type, "rw") &&
+ strcmp(workload_type, "randrw")) {
+ fprintf(stderr,
+ "io pattern type must be one of\n"
+ "(read, write, randread, randwrite, rw, randrw)\n");
+ return 1;
+ }
+
+ if (!strcmp(workload_type, "read") ||
+ !strcmp(workload_type, "randread")) {
+ g_rw_percentage = 100;
+ }
+
+ if (!strcmp(workload_type, "write") ||
+ !strcmp(workload_type, "randwrite")) {
+ g_rw_percentage = 0;
+ }
+
+ if (!strcmp(workload_type, "read") ||
+ !strcmp(workload_type, "randread") ||
+ !strcmp(workload_type, "write") ||
+ !strcmp(workload_type, "randwrite")) {
+ if (mix_specified) {
+ fprintf(stderr, "Ignoring -M option... Please use -M option"
+ " only when using rw or randrw.\n");
+ }
+ }
+
+ if (!strcmp(workload_type, "rw") ||
+ !strcmp(workload_type, "randrw")) {
+ if (g_rw_percentage < 0 || g_rw_percentage > 100) {
+ fprintf(stderr,
+ "-M must be specified to value from 0 to 100 "
+ "for rw or randrw.\n");
+ return 1;
+ }
+ }
+
+ if (!strcmp(workload_type, "read") ||
+ !strcmp(workload_type, "write") ||
+ !strcmp(workload_type, "rw")) {
+ g_is_random = 0;
+ } else {
+ g_is_random = 1;
+ }
+
+ return 0;
+}
+
+static int
+register_workers(void)
+{
+ struct worker_thread *worker;
+
+ worker = malloc(sizeof(struct worker_thread));
+ if (worker == NULL) {
+ perror("worker_thread malloc");
+ return -1;
+ }
+
+ memset(worker, 0, sizeof(struct worker_thread));
+ worker->lcore = spdk_env_get_current_core();
+
+ g_workers = worker;
+
+ return 0;
+}
+
+
+static bool
+probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
+ struct spdk_nvme_ctrlr_opts *opts)
+{
+ 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)
+{
+ register_ctrlr(ctrlr);
+}
+
+static int
+register_controllers(void)
+{
+ printf("Initializing NVMe Controllers\n");
+
+ if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "spdk_nvme_probe() failed\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+unregister_controllers(void)
+{
+ struct ctrlr_entry *entry = g_controllers;
+
+ while (entry) {
+ struct ctrlr_entry *next = entry->next;
+ spdk_nvme_detach(entry->ctrlr);
+ free(entry);
+ entry = next;
+ }
+}
+
+static int
+associate_workers_with_ns(void)
+{
+ struct ns_entry *entry = g_namespaces;
+ struct worker_thread *worker = g_workers;
+ struct ns_worker_ctx *ns_ctx;
+ int i, count;
+
+ count = g_num_namespaces;
+
+ for (i = 0; i < count; i++) {
+ if (entry == NULL) {
+ break;
+ }
+ ns_ctx = malloc(sizeof(struct ns_worker_ctx));
+ if (!ns_ctx) {
+ return -1;
+ }
+ memset(ns_ctx, 0, sizeof(*ns_ctx));
+
+ printf("Associating %s with lcore %d\n", entry->name, worker->lcore);
+ ns_ctx->entry = entry;
+ ns_ctx->next = worker->ns_ctx;
+ worker->ns_ctx = ns_ctx;
+
+ worker = g_workers;
+
+ entry = entry->next;
+ if (entry == NULL) {
+ entry = g_namespaces;
+ }
+ }
+
+ return 0;
+}
+
+static int
+run_nvme_reset_cycle(int retry_count)
+{
+ struct worker_thread *worker;
+ struct ns_worker_ctx *ns_ctx;
+
+ spdk_nvme_retry_count = retry_count;
+
+ if (work_fn(g_workers) != 0) {
+ return -1;
+ }
+
+ if (print_stats() != 0) {
+ return -1;
+ }
+
+ worker = g_workers;
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx != NULL) {
+ ns_ctx->io_completed = 0;
+ ns_ctx->io_completed_error = 0;
+ ns_ctx->io_submitted = 0;
+ ns_ctx->is_draining = false;
+ ns_ctx = ns_ctx->next;
+ }
+
+ return 0;
+}
+
+static void
+spdk_reset_free_tasks(void)
+{
+ if (spdk_mempool_count(task_pool) != TASK_POOL_NUM) {
+ fprintf(stderr, "task_pool count is %zu but should be %d\n",
+ spdk_mempool_count(task_pool), TASK_POOL_NUM);
+ }
+ spdk_mempool_free(task_pool);
+}
+
+int main(int argc, char **argv)
+{
+ int rc;
+ int i;
+ struct spdk_env_opts opts;
+
+
+ rc = parse_args(argc, argv);
+ if (rc != 0) {
+ return rc;
+ }
+
+ spdk_env_opts_init(&opts);
+ opts.name = "reset";
+ opts.core_mask = "0x1";
+ opts.shm_id = 0;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ if (register_controllers() != 0) {
+ return 1;
+ }
+
+ if (!g_controllers) {
+ printf("No NVMe controller found, %s exiting\n", argv[0]);
+ return 1;
+ }
+
+ task_pool = spdk_mempool_create("task_pool", TASK_POOL_NUM,
+ sizeof(struct reset_task),
+ 64, SPDK_ENV_SOCKET_ID_ANY);
+ if (!task_pool) {
+ fprintf(stderr, "Cannot create task pool\n");
+ return 1;
+ }
+
+ g_tsc_rate = spdk_get_ticks_hz();
+
+ if (register_workers() != 0) {
+ return 1;
+ }
+
+ if (associate_workers_with_ns() != 0) {
+ rc = 1;
+ goto cleanup;
+ }
+
+ printf("Initialization complete. Launching workers.\n");
+
+ for (i = 2; i >= 0; i--) {
+ rc = run_nvme_reset_cycle(i);
+ if (rc != 0) {
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ unregister_controllers();
+ spdk_reset_free_tasks();
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: errors occured\n", argv[0]);
+ }
+
+ return rc;
+}
diff --git a/src/spdk/test/nvme/sgl/.gitignore b/src/spdk/test/nvme/sgl/.gitignore
new file mode 100644
index 00000000..d1cebd68
--- /dev/null
+++ b/src/spdk/test/nvme/sgl/.gitignore
@@ -0,0 +1 @@
+sgl
diff --git a/src/spdk/test/nvme/sgl/Makefile b/src/spdk/test/nvme/sgl/Makefile
new file mode 100644
index 00000000..f0e0fc50
--- /dev/null
+++ b/src/spdk/test/nvme/sgl/Makefile
@@ -0,0 +1,39 @@
+#
+# 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 = sgl
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/sgl/sgl.c b/src/spdk/test/nvme/sgl/sgl.c
new file mode 100644
index 00000000..ccff652c
--- /dev/null
+++ b/src/spdk/test/nvme/sgl/sgl.c
@@ -0,0 +1,542 @@
+/*-
+ * 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/nvme.h"
+#include "spdk/env.h"
+#include "spdk/util.h"
+
+#define MAX_DEVS 64
+
+#define MAX_IOVS 128
+
+#define DATA_PATTERN 0x5A
+
+#define BASE_LBA_START 0x100000
+
+struct dev {
+ struct spdk_nvme_ctrlr *ctrlr;
+ char name[SPDK_NVMF_TRADDR_MAX_LEN + 1];
+};
+
+static struct dev devs[MAX_DEVS];
+static int num_devs = 0;
+
+#define foreach_dev(iter) \
+ for (iter = devs; iter - devs < num_devs; iter++)
+
+static int io_complete_flag = 0;
+
+struct sgl_element {
+ void *base;
+ size_t offset;
+ size_t len;
+};
+
+struct io_request {
+ uint32_t current_iov_index;
+ uint32_t current_iov_bytes_left;
+ struct sgl_element iovs[MAX_IOVS];
+ uint32_t nseg;
+ uint32_t misalign;
+};
+
+static void nvme_request_reset_sgl(void *cb_arg, uint32_t sgl_offset)
+{
+ uint32_t i;
+ uint32_t offset = 0;
+ struct sgl_element *iov;
+ struct io_request *req = (struct io_request *)cb_arg;
+
+ for (i = 0; i < req->nseg; i++) {
+ iov = &req->iovs[i];
+ offset += iov->len;
+ if (offset > sgl_offset) {
+ break;
+ }
+ }
+ req->current_iov_index = i;
+ req->current_iov_bytes_left = offset - sgl_offset;
+ return;
+}
+
+static int nvme_request_next_sge(void *cb_arg, void **address, uint32_t *length)
+{
+ struct io_request *req = (struct io_request *)cb_arg;
+ struct sgl_element *iov;
+
+ if (req->current_iov_index >= req->nseg) {
+ *length = 0;
+ *address = NULL;
+ return 0;
+ }
+
+ iov = &req->iovs[req->current_iov_index];
+
+ if (req->current_iov_bytes_left) {
+ *address = iov->base + iov->offset + iov->len - req->current_iov_bytes_left;
+ *length = req->current_iov_bytes_left;
+ req->current_iov_bytes_left = 0;
+ } else {
+ *address = iov->base + iov->offset;
+ *length = iov->len;
+ }
+
+ req->current_iov_index++;
+
+ return 0;
+}
+
+static void
+io_complete(void *ctx, const struct spdk_nvme_cpl *cpl)
+{
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ io_complete_flag = 2;
+ } else {
+ io_complete_flag = 1;
+ }
+}
+
+static void build_io_request_0(struct io_request *req)
+{
+ req->nseg = 1;
+
+ req->iovs[0].base = spdk_dma_zmalloc(0x800, 4, NULL);
+ req->iovs[0].len = 0x800;
+}
+
+static void build_io_request_1(struct io_request *req)
+{
+ req->nseg = 1;
+
+ /* 512B for 1st sge */
+ req->iovs[0].base = spdk_dma_zmalloc(0x200, 0x200, NULL);
+ req->iovs[0].len = 0x200;
+}
+
+static void build_io_request_2(struct io_request *req)
+{
+ req->nseg = 1;
+
+ /* 256KB for 1st sge */
+ req->iovs[0].base = spdk_dma_zmalloc(0x40000, 0x1000, NULL);
+ req->iovs[0].len = 0x40000;
+}
+
+static void build_io_request_3(struct io_request *req)
+{
+ req->nseg = 3;
+
+ /* 2KB for 1st sge, make sure the iov address start at 0x800 boundary,
+ * and end with 0x1000 boundary */
+ req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[0].offset = 0x800;
+ req->iovs[0].len = 0x800;
+
+ /* 4KB for 2th sge */
+ req->iovs[1].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[1].len = 0x1000;
+
+ /* 12KB for 3th sge */
+ req->iovs[2].base = spdk_dma_zmalloc(0x3000, 0x1000, NULL);
+ req->iovs[2].len = 0x3000;
+}
+
+static void build_io_request_4(struct io_request *req)
+{
+ uint32_t i;
+
+ req->nseg = 32;
+
+ /* 4KB for 1st sge */
+ req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[0].len = 0x1000;
+
+ /* 8KB for the rest 31 sge */
+ for (i = 1; i < req->nseg; i++) {
+ req->iovs[i].base = spdk_dma_zmalloc(0x2000, 0x1000, NULL);
+ req->iovs[i].len = 0x2000;
+ }
+}
+
+static void build_io_request_5(struct io_request *req)
+{
+ req->nseg = 1;
+
+ /* 8KB for 1st sge */
+ req->iovs[0].base = spdk_dma_zmalloc(0x2000, 0x1000, NULL);
+ req->iovs[0].len = 0x2000;
+}
+
+static void build_io_request_6(struct io_request *req)
+{
+ req->nseg = 2;
+
+ /* 4KB for 1st sge */
+ req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[0].len = 0x1000;
+
+ /* 4KB for 2st sge */
+ req->iovs[1].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[1].len = 0x1000;
+}
+
+static void build_io_request_7(struct io_request *req)
+{
+ uint8_t *base;
+
+ req->nseg = 1;
+
+ /*
+ * Create a 64KB sge, but ensure it is *not* aligned on a 4KB
+ * boundary. This is valid for single element buffers with PRP.
+ */
+ base = spdk_dma_zmalloc(0x11000, 0x1000, NULL);
+ req->misalign = 64;
+ req->iovs[0].base = base + req->misalign;
+ req->iovs[0].len = 0x10000;
+}
+
+static void build_io_request_8(struct io_request *req)
+{
+ req->nseg = 2;
+
+ /*
+ * 1KB for 1st sge, make sure the iov address does not start and end
+ * at 0x1000 boundary
+ */
+ req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[0].offset = 0x400;
+ req->iovs[0].len = 0x400;
+
+ /*
+ * 1KB for 1st sge, make sure the iov address does not start and end
+ * at 0x1000 boundary
+ */
+ req->iovs[1].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[1].offset = 0x400;
+ req->iovs[1].len = 0x400;
+}
+
+static void build_io_request_9(struct io_request *req)
+{
+ /*
+ * Check if mixed PRP complaint and not complaint requests are handled
+ * properly by splitting them into subrequests.
+ * Construct buffers with following theme:
+ */
+ const size_t req_len[] = { 2048, 4096, 2048, 4096, 2048, 1024 };
+ const size_t req_off[] = { 0x800, 0x0, 0x0, 0x100, 0x800, 0x800 };
+ struct sgl_element *iovs = req->iovs;
+ uint32_t i;
+ req->nseg = SPDK_COUNTOF(req_len);
+ assert(SPDK_COUNTOF(req_len) == SPDK_COUNTOF(req_off));
+
+ for (i = 0; i < req->nseg; i++) {
+ iovs[i].base = spdk_dma_zmalloc(req_off[i] + req_len[i], 0x4000, NULL);
+ iovs[i].offset = req_off[i];
+ iovs[i].len = req_len[i];
+ }
+}
+
+static void build_io_request_10(struct io_request *req)
+{
+ /*
+ * Test the case where we have a valid PRP list, but the first and last
+ * elements are not exact multiples of the logical block size.
+ */
+ const size_t req_len[] = { 4004, 4096, 92 };
+ const size_t req_off[] = { 0x5c, 0x0, 0x0 };
+ struct sgl_element *iovs = req->iovs;
+ uint32_t i;
+ req->nseg = SPDK_COUNTOF(req_len);
+ assert(SPDK_COUNTOF(req_len) == SPDK_COUNTOF(req_off));
+
+ for (i = 0; i < req->nseg; i++) {
+ iovs[i].base = spdk_dma_zmalloc(req_off[i] + req_len[i], 0x4000, NULL);
+ iovs[i].offset = req_off[i];
+ iovs[i].len = req_len[i];
+ }
+}
+
+static void build_io_request_11(struct io_request *req)
+{
+ /* This test case focuses on the last element not starting on a page boundary. */
+ const size_t req_len[] = { 512, 512 };
+ const size_t req_off[] = { 0xe00, 0x800 };
+ struct sgl_element *iovs = req->iovs;
+ uint32_t i;
+ req->nseg = SPDK_COUNTOF(req_len);
+ assert(SPDK_COUNTOF(req_len) == SPDK_COUNTOF(req_off));
+
+ for (i = 0; i < req->nseg; i++) {
+ iovs[i].base = spdk_dma_zmalloc(req_off[i] + req_len[i], 0x4000, NULL);
+ iovs[i].offset = req_off[i];
+ iovs[i].len = req_len[i];
+ }
+}
+
+typedef void (*nvme_build_io_req_fn_t)(struct io_request *req);
+
+static void
+free_req(struct io_request *req)
+{
+ uint32_t i;
+
+ if (req == NULL) {
+ return;
+ }
+
+ for (i = 0; i < req->nseg; i++) {
+ spdk_dma_free(req->iovs[i].base - req->misalign);
+ }
+
+ spdk_dma_free(req);
+}
+
+static int
+writev_readv_tests(struct dev *dev, nvme_build_io_req_fn_t build_io_fn, const char *test_name)
+{
+ int rc = 0;
+ uint32_t len, lba_count;
+ uint32_t i, j, nseg, remainder;
+ char *buf;
+
+ struct io_request *req;
+ struct spdk_nvme_ns *ns;
+ struct spdk_nvme_qpair *qpair;
+ const struct spdk_nvme_ns_data *nsdata;
+
+ ns = spdk_nvme_ctrlr_get_ns(dev->ctrlr, 1);
+ if (!ns) {
+ fprintf(stderr, "Null namespace\n");
+ return 0;
+ }
+ nsdata = spdk_nvme_ns_get_data(ns);
+ if (!nsdata || !spdk_nvme_ns_get_sector_size(ns)) {
+ fprintf(stderr, "Empty nsdata or wrong sector size\n");
+ return 0;
+ }
+
+ if (spdk_nvme_ns_get_flags(ns) & SPDK_NVME_NS_DPS_PI_SUPPORTED) {
+ return 0;
+ }
+
+ req = spdk_dma_zmalloc(sizeof(*req), 0, NULL);
+ if (!req) {
+ fprintf(stderr, "Allocate request failed\n");
+ return 0;
+ }
+
+ /* IO parameters setting */
+ build_io_fn(req);
+
+ len = 0;
+ for (i = 0; i < req->nseg; i++) {
+ struct sgl_element *sge = &req->iovs[i];
+
+ len += sge->len;
+ }
+
+ lba_count = len / spdk_nvme_ns_get_sector_size(ns);
+ remainder = len % spdk_nvme_ns_get_sector_size(ns);
+ if (!lba_count || remainder || (BASE_LBA_START + lba_count > (uint32_t)nsdata->nsze)) {
+ fprintf(stderr, "%s: %s Invalid IO length parameter\n", dev->name, test_name);
+ free_req(req);
+ return 0;
+ }
+
+ qpair = spdk_nvme_ctrlr_alloc_io_qpair(dev->ctrlr, NULL, 0);
+ if (!qpair) {
+ free_req(req);
+ return -1;
+ }
+
+ nseg = req->nseg;
+ for (i = 0; i < nseg; i++) {
+ memset(req->iovs[i].base + req->iovs[i].offset, DATA_PATTERN, req->iovs[i].len);
+ }
+
+ rc = spdk_nvme_ns_cmd_writev(ns, qpair, BASE_LBA_START, lba_count,
+ io_complete, req, 0,
+ nvme_request_reset_sgl,
+ nvme_request_next_sge);
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: %s writev failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ io_complete_flag = 0;
+
+ while (!io_complete_flag) {
+ spdk_nvme_qpair_process_completions(qpair, 1);
+ }
+
+ if (io_complete_flag != 1) {
+ fprintf(stderr, "%s: %s writev failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ /* reset completion flag */
+ io_complete_flag = 0;
+
+ for (i = 0; i < nseg; i++) {
+ memset(req->iovs[i].base + req->iovs[i].offset, 0, req->iovs[i].len);
+ }
+
+ rc = spdk_nvme_ns_cmd_readv(ns, qpair, BASE_LBA_START, lba_count,
+ io_complete, req, 0,
+ nvme_request_reset_sgl,
+ nvme_request_next_sge);
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: %s readv failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ while (!io_complete_flag) {
+ spdk_nvme_qpair_process_completions(qpair, 1);
+ }
+
+ if (io_complete_flag != 1) {
+ fprintf(stderr, "%s: %s readv failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ for (i = 0; i < nseg; i++) {
+ buf = (char *)req->iovs[i].base + req->iovs[i].offset;
+ for (j = 0; j < req->iovs[i].len; j++) {
+ if (buf[j] != DATA_PATTERN) {
+ fprintf(stderr, "%s: %s write/read success, but memcmp Failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+ }
+ }
+
+ fprintf(stdout, "%s: %s test passed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return rc;
+}
+
+static bool
+probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
+ struct spdk_nvme_ctrlr_opts *opts)
+{
+ printf("Attaching to %s\n", trid->traddr);
+
+ 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 dev *dev;
+
+ /* add to dev list */
+ dev = &devs[num_devs++];
+
+ dev->ctrlr = ctrlr;
+
+ snprintf(dev->name, sizeof(dev->name), "%s",
+ trid->traddr);
+
+ printf("Attached to %s\n", dev->name);
+}
+
+int main(int argc, char **argv)
+{
+ struct dev *iter;
+ int rc, i;
+ struct spdk_env_opts opts;
+
+ spdk_env_opts_init(&opts);
+ opts.name = "nvme_sgl";
+ opts.core_mask = "0x1";
+ opts.shm_id = 0;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ printf("NVMe Readv/Writev Request test\n");
+
+ if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "nvme_probe() failed\n");
+ exit(1);
+ }
+
+ rc = 0;
+ foreach_dev(iter) {
+#define TEST(x) writev_readv_tests(iter, x, #x)
+ if (TEST(build_io_request_0)
+ || TEST(build_io_request_1)
+ || TEST(build_io_request_2)
+ || TEST(build_io_request_3)
+ || TEST(build_io_request_4)
+ || TEST(build_io_request_5)
+ || TEST(build_io_request_6)
+ || TEST(build_io_request_7)
+ || TEST(build_io_request_8)
+ || TEST(build_io_request_9)
+ || TEST(build_io_request_10)
+ || TEST(build_io_request_11)) {
+#undef TEST
+ rc = 1;
+ printf("%s: failed sgl tests\n", iter->name);
+ }
+ }
+
+ printf("Cleaning up...\n");
+
+ for (i = 0; i < num_devs; i++) {
+ struct dev *dev = &devs[i];
+
+ spdk_nvme_detach(dev->ctrlr);
+ }
+
+ return rc;
+}
diff --git a/src/spdk/test/nvme/spdk_nvme_cli.sh b/src/spdk/test/nvme/spdk_nvme_cli.sh
new file mode 100755
index 00000000..0051df1d
--- /dev/null
+++ b/src/spdk/test/nvme/spdk_nvme_cli.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+
+set -ex
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/scripts/common.sh
+source $rootdir/test/common/autotest_common.sh
+
+if [ -z "${DEPENDENCY_DIR}" ]; then
+ echo DEPENDENCY_DIR not defined!
+ exit 1
+fi
+
+spdk_nvme_cli="${DEPENDENCY_DIR}/nvme-cli"
+
+if [ ! -d $spdk_nvme_cli ]; then
+ echo "nvme-cli repository not found at $spdk_nvme_cli; skipping tests."
+ exit 0
+fi
+
+timing_enter nvme_cli
+
+if [ `uname` = Linux ]; then
+ start_stub "-s 2048 -i 0 -m 0xF"
+ trap "kill_stub; exit 1" SIGINT SIGTERM EXIT
+fi
+
+# Build against the version of SPDK under test
+rm -f "$spdk_nvme_cli/spdk"
+ln -sf "$rootdir" "$spdk_nvme_cli/spdk"
+
+bdfs=$(iter_pci_class_code 01 08 02)
+bdf=$(echo $bdfs|awk '{ print $1 }')
+
+cd $spdk_nvme_cli
+make clean && make -j$(nproc) LDFLAGS="$(make -s -C $spdk_nvme_cli/spdk ldflags)"
+sed -i 's/spdk=0/spdk=1/g' spdk.conf
+sed -i 's/shm_id=1/shm_id=0/g' spdk.conf
+./nvme list
+./nvme id-ctrl $bdf
+./nvme list-ctrl $bdf
+./nvme get-ns-id $bdf
+./nvme id-ns $bdf
+./nvme fw-log $bdf
+./nvme smart-log $bdf
+./nvme error-log $bdf
+./nvme list-ns $bdf -n 1
+./nvme get-feature $bdf -n 1 -f 1 -s 1 -l 100
+./nvme get-log $bdf -n 1 -i 1 -l 100
+./nvme reset $bdf
+if [ `uname` = Linux ]; then
+ trap - SIGINT SIGTERM EXIT
+ kill_stub
+fi
+
+report_test_completion spdk_nvme_cli
+timing_exit nvme_cli
diff --git a/src/spdk/test/nvmf/bdev_io_wait/bdev_io_wait.sh b/src/spdk/test/nvmf/bdev_io_wait/bdev_io_wait.sh
new file mode 100755
index 00000000..29106bde
--- /dev/null
+++ b/src/spdk/test/nvmf/bdev_io_wait/bdev_io_wait.sh
@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization.
+# e.g. sudo ./bdev_io_wait.sh iso
+nvmftestinit $1
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter bdev_io_wait
+timing_enter start_nvmf_tgt
+
+$NVMF_APP -m 0xF --wait-for-rpc &
+nvmfpid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $nvmfpid
+# Minimal number of bdev io pool (5) and cache (1)
+$rpc_py set_bdev_options -p 5 -c 1
+$rpc_py start_subsystem_init
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+modprobe -v nvme-rdma
+
+bdevs="$bdevs $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev
+done
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+
+echo "[Nvme]" > $testdir/bdevperf.conf
+echo " TransportID \"trtype:RDMA adrfam:IPv4 subnqn:nqn.2016-06.io.spdk:cnode1 traddr:$NVMF_FIRST_TARGET_IP trsvcid:4420\" Nvme0" >> $testdir/bdevperf.conf
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w write -t 1
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w read -t 1
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w flush -t 1
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w unmap -t 1
+sync
+rm -rf $testdir/bdevperf.conf
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+
+trap - SIGINT SIGTERM EXIT
+
+nvmfcleanup
+killprocess $nvmfpid
+nvmftestfini $1
+timing_exit bdev_io_wait
diff --git a/src/spdk/test/nvmf/common.sh b/src/spdk/test/nvmf/common.sh
new file mode 100755
index 00000000..af79e3f7
--- /dev/null
+++ b/src/spdk/test/nvmf/common.sh
@@ -0,0 +1,200 @@
+#!/usr/bin/env bash
+
+NVMF_PORT=4420
+NVMF_IP_PREFIX="192.168.100"
+NVMF_IP_LEAST_ADDR=8
+
+if [ -z "$NVMF_APP" ]; then
+ NVMF_APP=./app/nvmf_tgt/nvmf_tgt
+fi
+
+if [ -z "$NVMF_TEST_CORE_MASK" ]; then
+ NVMF_TEST_CORE_MASK=0xFF
+fi
+
+function load_ib_rdma_modules()
+{
+ if [ `uname` != Linux ]; then
+ return 0
+ fi
+
+ modprobe ib_cm
+ modprobe ib_core
+ # Newer kernels do not have the ib_ucm module
+ modprobe ib_ucm || true
+ modprobe ib_umad
+ modprobe ib_uverbs
+ modprobe iw_cm
+ modprobe rdma_cm
+ modprobe rdma_ucm
+}
+
+
+function detect_soft_roce_nics()
+{
+ if hash rxe_cfg; then
+ rxe_cfg start
+ rdma_nics=$(get_rdma_if_list)
+ all_nics=$(ip -o link | awk '{print $2}' | cut -d":" -f1)
+ non_rdma_nics=$(echo -e "$rdma_nics\n$all_nics" | sort | uniq -u)
+ for nic in $non_rdma_nics; do
+ if [[ -d /sys/class/net/${nic}/bridge ]]; then
+ continue
+ fi
+ rxe_cfg add $nic || true
+ done
+ fi
+}
+
+function detect_mellanox_nics()
+{
+ if ! hash lspci; then
+ echo "No NICs"
+ return 0
+ fi
+
+ nvmf_nic_bdfs=`lspci | grep Ethernet | grep Mellanox | awk -F ' ' '{print "0000:"$1}'`
+ mlx_core_driver="mlx4_core"
+ mlx_ib_driver="mlx4_ib"
+ mlx_en_driver="mlx4_en"
+
+ if [ -z "$nvmf_nic_bdfs" ]; then
+ echo "No NICs"
+ return 0
+ fi
+
+ # for nvmf target loopback test, suppose we only have one type of card.
+ for nvmf_nic_bdf in $nvmf_nic_bdfs
+ do
+ result=`lspci -vvv -s $nvmf_nic_bdf | grep 'Kernel modules' | awk -F ' ' '{print $3}'`
+ if [ "$result" == "mlx5_core" ]; then
+ mlx_core_driver="mlx5_core"
+ mlx_ib_driver="mlx5_ib"
+ mlx_en_driver=""
+ fi
+ break;
+ done
+
+ modprobe $mlx_core_driver
+ modprobe $mlx_ib_driver
+ if [ -n "$mlx_en_driver" ]; then
+ modprobe $mlx_en_driver
+ fi
+
+ # The mlx4 driver takes an extra few seconds to load after modprobe returns,
+ # otherwise iproute2 operations will do nothing.
+ sleep 5
+}
+
+function detect_rdma_nics()
+{
+ nics=$(detect_mellanox_nics)
+ if [ "$nics" == "No NICs" ]; then
+ detect_soft_roce_nics
+ fi
+}
+
+function allocate_nic_ips()
+{
+ let count=$NVMF_IP_LEAST_ADDR
+ for nic_name in $(get_rdma_if_list); do
+ ip="$(get_ip_address $nic_name)"
+ if [ -z $ip ]; then
+ ip addr add $NVMF_IP_PREFIX.$count/24 dev $nic_name
+ ip link set $nic_name up
+ let count=$count+1
+ fi
+ # dump configuration for debug log
+ ip addr show $nic_name
+ done
+}
+
+function get_available_rdma_ips()
+{
+ for nic_name in $(get_rdma_if_list); do
+ get_ip_address $nic_name
+ done
+}
+
+function get_rdma_if_list()
+{
+ for nic_type in `ls /sys/class/infiniband`; do
+ for nic_name in `ls /sys/class/infiniband/${nic_type}/device/net`; do
+ echo "$nic_name"
+ done
+ done
+}
+
+function get_ip_address()
+{
+ interface=$1
+ ip -o -4 addr show $interface | awk '{print $4}' | cut -d"/" -f1
+}
+
+function nvmfcleanup()
+{
+ sync
+ set +e
+ for i in {1..20}; do
+ modprobe -v -r nvme-rdma nvme-fabrics
+ if [ $? -eq 0 ]; then
+ set -e
+ return
+ fi
+ sleep 1
+ done
+ set -e
+
+ # So far unable to remove the kernel modules. Try
+ # one more time and let it fail.
+ modprobe -v -r nvme-rdma nvme-fabrics
+}
+
+function nvmftestinit()
+{
+ if [ "$1" == "iso" ]; then
+ $rootdir/scripts/setup.sh
+ rdma_device_init
+ fi
+}
+
+function nvmftestfini()
+{
+ if [ "$1" == "iso" ]; then
+ $rootdir/scripts/setup.sh reset
+ rdma_device_init
+ fi
+}
+
+function rdma_device_init()
+{
+ load_ib_rdma_modules
+ detect_rdma_nics
+ allocate_nic_ips
+}
+
+function revert_soft_roce()
+{
+ if hash rxe_cfg; then
+ interfaces="$(ip -o link | awk '{print $2}' | cut -d":" -f1)"
+ for interface in $interfaces; do
+ rxe_cfg remove $interface || true
+ done
+ rxe_cfg stop || true
+ fi
+}
+
+function check_ip_is_soft_roce()
+{
+ IP=$1
+ if hash rxe_cfg; then
+ dev=$(ip -4 -o addr show | grep $IP | cut -d" " -f2)
+ if rxe_cfg | grep $dev; then
+ return 0
+ else
+ return 1
+ fi
+ else
+ return 1
+ fi
+}
diff --git a/src/spdk/test/nvmf/create_transport/create_transport.sh b/src/spdk/test/nvmf/create_transport/create_transport.sh
new file mode 100755
index 00000000..3514dbc1
--- /dev/null
+++ b/src/spdk/test/nvmf/create_transport/create_transport.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+NULL_BDEV_SIZE=102400
+NULL_BLOCK_SIZE=512
+
+rpc_py="python $rootdir/scripts/rpc.py"
+
+set -e
+
+# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization.
+# e.g. sudo ./crt_trprt.sh iso
+nvmftestinit $1
+
+if ! hash nvme; then
+ echo "nvme command not found; skipping create transport test"
+ exit 0
+fi
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter cr_trprt
+timing_enter start_nvmf_tgt
+# Start up the NVMf target in another process
+$NVMF_APP -m 0xF &
+nvmfpid=$!
+
+trap "killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $nvmfpid
+# Use nvmf_create_transport call to create transport
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+null_bdevs="$($rpc_py construct_null_bdev Null0 $NULL_BDEV_SIZE $NULL_BLOCK_SIZE) "
+null_bdevs+="$($rpc_py construct_null_bdev Null1 $NULL_BDEV_SIZE $NULL_BLOCK_SIZE)"
+
+modprobe -v nvme-rdma
+
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+for null_bdev in $null_bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $null_bdev
+done
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+
+nvme discover -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT
+
+echo "Perform nvmf subsystem discovery via RPC"
+$rpc_py get_nvmf_subsystems
+
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+
+for null_bdev in $null_bdevs; do
+ $rpc_py delete_null_bdev $null_bdev
+done
+
+check_bdevs=$($rpc_py get_bdevs | jq -r '.[].name')
+if [ -n "$check_bdevs" ]; then
+ echo $check_bdevs
+ exit 1
+fi
+
+trap - SIGINT SIGTERM EXIT
+
+nvmfcleanup
+killprocess $nvmfpid
+nvmftestfini $1
+timing_exit crt_trprt
diff --git a/src/spdk/test/nvmf/discovery/discovery.sh b/src/spdk/test/nvmf/discovery/discovery.sh
new file mode 100755
index 00000000..f0db8ab3
--- /dev/null
+++ b/src/spdk/test/nvmf/discovery/discovery.sh
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+NULL_BDEV_SIZE=102400
+NULL_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization.
+# e.g. sudo ./discovery.sh iso
+nvmftestinit $1
+
+if ! hash nvme; then
+ echo "nvme command not found; skipping discovery test"
+ exit 0
+fi
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter discovery
+timing_enter start_nvmf_tgt
+# Start up the NVMf target in another process
+$NVMF_APP -m 0xF &
+nvmfpid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $nvmfpid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+null_bdevs="$($rpc_py construct_null_bdev Null0 $NULL_BDEV_SIZE $NULL_BLOCK_SIZE) "
+null_bdevs+="$($rpc_py construct_null_bdev Null1 $NULL_BDEV_SIZE $NULL_BLOCK_SIZE)"
+
+modprobe -v nvme-rdma
+
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+for null_bdev in $null_bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $null_bdev
+done
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+
+nvme discover -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT
+
+echo "Perform nvmf subsystem discovery via RPC"
+$rpc_py get_nvmf_subsystems
+
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+
+for null_bdev in $null_bdevs; do
+ $rpc_py delete_null_bdev $null_bdev
+done
+
+check_bdevs=$($rpc_py get_bdevs | jq -r '.[].name')
+if [ -n "$check_bdevs" ]; then
+ echo $check_bdevs
+ exit 1
+fi
+
+trap - SIGINT SIGTERM EXIT
+
+nvmfcleanup
+killprocess $nvmfpid
+nvmftestfini $1
+timing_exit discovery
diff --git a/src/spdk/test/nvmf/filesystem/filesystem.sh b/src/spdk/test/nvmf/filesystem/filesystem.sh
new file mode 100755
index 00000000..057fc579
--- /dev/null
+++ b/src/spdk/test/nvmf/filesystem/filesystem.sh
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization.
+# e.g. sudo ./filesystem.sh iso
+nvmftestinit $1
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter fs_test
+
+for incapsule in 0 4096; do
+ # Start up the NVMf target in another process
+ $NVMF_APP -m 0xF &
+ nvmfpid=$!
+
+ trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT
+
+ waitforlisten $nvmfpid
+ $rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4 -c $incapsule
+
+ bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+ bdevs+=" $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+
+ modprobe -v nvme-rdma
+
+ $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+ for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev
+ done
+ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+
+ nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+
+ waitforblk "nvme0n1"
+ waitforblk "nvme0n2"
+
+ mkdir -p /mnt/device
+
+ devs=`lsblk -l -o NAME | grep nvme`
+
+ for dev in $devs; do
+ timing_enter parted
+ parted -s /dev/$dev mklabel msdos mkpart primary '0%' '100%'
+ timing_exit parted
+ sleep 1
+
+ for fstype in "ext4" "btrfs" "xfs"; do
+ timing_enter $fstype
+ if [ $fstype = ext4 ]; then
+ force=-F
+ else
+ force=-f
+ fi
+
+ mkfs.${fstype} $force /dev/${dev}p1
+
+ mount /dev/${dev}p1 /mnt/device
+ touch /mnt/device/aaa
+ sync
+ rm /mnt/device/aaa
+ sync
+ umount /mnt/device
+ timing_exit $fstype
+ done
+
+ parted -s /dev/$dev rm 1
+ done
+
+ sync
+ nvme disconnect -n "nqn.2016-06.io.spdk:cnode1" || true
+
+ $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+
+ trap - SIGINT SIGTERM EXIT
+
+ nvmfcleanup
+ killprocess $nvmfpid
+done
+
+nvmftestfini $1
+timing_exit fs_test
diff --git a/src/spdk/test/nvmf/fio/fio.sh b/src/spdk/test/nvmf/fio/fio.sh
new file mode 100755
index 00000000..ba3b12b3
--- /dev/null
+++ b/src/spdk/test/nvmf/fio/fio.sh
@@ -0,0 +1,108 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization.
+# e.g. sudo ./fio.sh iso
+nvmftestinit $1
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter fio
+timing_enter start_nvmf_tgt
+# Start up the NVMf target in another process
+$NVMF_APP -m 0xF &
+nvmfpid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $nvmfpid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+
+timing_exit start_nvmf_tgt
+
+malloc_bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) "
+malloc_bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+# Create a RAID-0 bdev from two malloc bdevs
+raid_malloc_bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) "
+raid_malloc_bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+$rpc_py construct_raid_bdev -n raid0 -s 64 -r 0 -b "$raid_malloc_bdevs"
+
+modprobe -v nvme-rdma
+
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+for malloc_bdev in $malloc_bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 "$malloc_bdev"
+done
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+
+# Append the raid0 bdev into subsystem
+$rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 raid0
+
+nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+
+waitforblk "nvme0n1"
+waitforblk "nvme0n2"
+waitforblk "nvme0n3"
+
+$testdir/nvmf_fio.py 4096 1 write 1 verify
+$testdir/nvmf_fio.py 4096 1 randwrite 1 verify
+$testdir/nvmf_fio.py 4096 128 write 1 verify
+$testdir/nvmf_fio.py 4096 128 randwrite 1 verify
+
+sync
+
+#start hotplug test case
+$testdir/nvmf_fio.py 4096 1 read 10 &
+fio_pid=$!
+
+sleep 3
+set +e
+
+$rpc_py destroy_raid_bdev "raid0"
+for malloc_bdev in $malloc_bdevs; do
+ $rpc_py delete_malloc_bdev "$malloc_bdev"
+done
+
+wait $fio_pid
+fio_status=$?
+
+nvme disconnect -n "nqn.2016-06.io.spdk:cnode1" || true
+
+if [ $fio_status -eq 0 ]; then
+ echo "nvmf hotplug test: fio successful - expected failure"
+ nvmfcleanup
+ killprocess $nvmfpid
+ exit 1
+else
+ echo "nvmf hotplug test: fio failed as expected"
+fi
+set -e
+
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+
+rm -f ./local-job0-0-verify.state
+rm -f ./local-job1-1-verify.state
+rm -f ./local-job2-2-verify.state
+
+trap - SIGINT SIGTERM EXIT
+
+nvmfcleanup
+killprocess $nvmfpid
+nvmftestfini $1
+timing_exit fio
diff --git a/src/spdk/test/nvmf/fio/nvmf_fio.py b/src/spdk/test/nvmf/fio/nvmf_fio.py
new file mode 100755
index 00000000..6096dd72
--- /dev/null
+++ b/src/spdk/test/nvmf/fio/nvmf_fio.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+
+from subprocess import check_call, call, check_output, Popen, PIPE, CalledProcessError
+import re
+import sys
+import signal
+
+fio_template = """
+[global]
+thread=1
+invalidate=1
+rw=%(testtype)s
+time_based=1
+runtime=%(runtime)s
+ioengine=libaio
+direct=1
+bs=%(blocksize)d
+iodepth=%(iodepth)d
+%(verify)s
+verify_dump=1
+
+"""
+
+verify_template = """
+do_verify=1
+verify=meta
+verify_pattern="meta"
+"""
+
+
+fio_job_template = """
+[job%(jobnumber)d]
+filename=%(device)s
+
+"""
+
+
+def interrupt_handler(signum, frame):
+ fio.terminate()
+ print("FIO terminated")
+ sys.exit(0)
+
+
+def main():
+
+ global fio
+ if (len(sys.argv) < 5):
+ print("usage:")
+ print(" " + sys.argv[0] + " <io_size> <queue_depth> <test_type> <runtime>")
+ print("advanced usage:")
+ print("If you want to run fio with verify, please add verify string after runtime.")
+ print("Currently fio.py only support write rw randwrite randrw with verify enabled.")
+ sys.exit(1)
+
+ io_size = int(sys.argv[1])
+ queue_depth = int(sys.argv[2])
+ test_type = sys.argv[3]
+ runtime = sys.argv[4]
+ if len(sys.argv) > 5:
+ verify = True
+ else:
+ verify = False
+
+ devices = get_target_devices()
+ print("Found devices: ", devices)
+
+ # configure_devices(devices)
+ try:
+ fio_executable = check_output("which fio", shell=True).split()[0]
+ except CalledProcessError as e:
+ sys.stderr.write(str(e))
+ sys.stderr.write("\nCan't find the fio binary, please install it.\n")
+ sys.exit(1)
+
+ device_paths = ['/dev/' + dev for dev in devices]
+ print(device_paths)
+ sys.stdout.flush()
+ signal.signal(signal.SIGTERM, interrupt_handler)
+ signal.signal(signal.SIGINT, interrupt_handler)
+ fio = Popen([fio_executable, '-'], stdin=PIPE)
+ fio.communicate(create_fio_config(io_size, queue_depth, device_paths, test_type, runtime, verify))
+ fio.stdin.close()
+ rc = fio.wait()
+ print("FIO completed with code %d\n" % rc)
+ sys.stdout.flush()
+ sys.exit(rc)
+
+
+def get_target_devices():
+ output = str(check_output('lsblk -l -o NAME', shell=True).decode())
+ return re.findall("(nvme[0-9]+n[0-9]+)\n", output)
+
+
+def create_fio_config(size, q_depth, devices, test, run_time, verify):
+ if not verify:
+ verifyfio = ""
+ else:
+ verifyfio = verify_template
+ fiofile = fio_template % {"blocksize": size, "iodepth": q_depth,
+ "testtype": test, "runtime": run_time, "verify": verifyfio}
+ for (i, dev) in enumerate(devices):
+ fiofile += fio_job_template % {"jobnumber": i, "device": dev}
+ return fiofile.encode()
+
+
+def set_device_parameter(devices, filename_template, value):
+ for dev in devices:
+ filename = filename_template % dev
+ f = open(filename, 'r+b')
+ f.write(value)
+ f.close()
+
+
+def configure_devices(devices):
+ set_device_parameter(devices, "/sys/block/%s/queue/nomerges", "2")
+ set_device_parameter(devices, "/sys/block/%s/queue/nr_requests", "128")
+ requested_qd = 128
+ qd = requested_qd
+ while qd > 0:
+ try:
+ set_device_parameter(devices, "/sys/block/%s/device/queue_depth", str(qd))
+ break
+ except IOError:
+ qd = qd - 1
+ if qd == 0:
+ print("Could not set block device queue depths.")
+ else:
+ print("Requested queue_depth {} but only {} is supported.".format(str(requested_qd), str(qd)))
+ set_device_parameter(devices, "/sys/block/%s/queue/scheduler", "noop")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/spdk/test/nvmf/host/aer.sh b/src/spdk/test/nvmf/host/aer.sh
new file mode 100755
index 00000000..66e597aa
--- /dev/null
+++ b/src/spdk/test/nvmf/host/aer.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter aer
+timing_enter start_nvmf_tgt
+
+$NVMF_APP -m 0xF &
+nvmfpid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $nvmfpid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+modprobe -v nvme-rdma
+
+$rpc_py construct_malloc_bdev 64 512 --name Malloc0
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 -m 2
+$rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 Malloc0
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT
+
+$rpc_py get_nvmf_subsystems
+
+# TODO: this aer test tries to invoke an AER completion by setting the temperature
+#threshold to a very low value. This does not work with emulated controllers
+#though so currently the test is disabled.
+
+#$rootdir/test/nvme/aer/aer -r "\
+# trtype:RDMA \
+# adrfam:IPv4 \
+# traddr:$NVMF_FIRST_TARGET_IP \
+# trsvcid:$NVMF_PORT \
+# subnqn:nqn.2014-08.org.nvmexpress.discovery"
+
+# Namespace Attribute Notice Tests
+$rootdir/test/nvme/aer/aer -r "\
+ trtype:RDMA \
+ adrfam:IPv4 \
+ traddr:$NVMF_FIRST_TARGET_IP \
+ trsvcid:$NVMF_PORT \
+ subnqn:nqn.2016-06.io.spdk:cnode1" -n 2 &
+aerpid=$!
+
+# Waiting for aer start to work
+sleep 5
+
+# Add a new namespace
+$rpc_py construct_malloc_bdev 64 4096 --name Malloc1
+$rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 Malloc1 -n 2
+$rpc_py get_nvmf_subsystems
+
+wait $aerpid
+
+$rpc_py delete_malloc_bdev Malloc0
+$rpc_py delete_malloc_bdev Malloc1
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+
+trap - SIGINT SIGTERM EXIT
+
+nvmfcleanup
+killprocess $nvmfpid
+timing_exit aer
diff --git a/src/spdk/test/nvmf/host/bdevperf.sh b/src/spdk/test/nvmf/host/bdevperf.sh
new file mode 100755
index 00000000..1247177f
--- /dev/null
+++ b/src/spdk/test/nvmf/host/bdevperf.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter bdevperf
+timing_enter start_nvmf_tgt
+
+$NVMF_APP -m 0xF &
+nvmfpid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $nvmfpid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+bdevs="$bdevs $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev
+done
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+
+echo "[Nvme]" > $testdir/bdevperf.conf
+echo " TransportID \"trtype:RDMA adrfam:IPv4 subnqn:nqn.2016-06.io.spdk:cnode1 traddr:$NVMF_FIRST_TARGET_IP trsvcid:4420\" Nvme0" >> $testdir/bdevperf.conf
+$rootdir/test/bdev/bdevperf/bdevperf -c $testdir/bdevperf.conf -q 128 -o 4096 -w verify -t 1
+sync
+rm -rf $testdir/bdevperf.conf
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+
+trap - SIGINT SIGTERM EXIT
+
+killprocess $nvmfpid
+timing_exit bdevperf
diff --git a/src/spdk/test/nvmf/host/fio.sh b/src/spdk/test/nvmf/host/fio.sh
new file mode 100755
index 00000000..ceed86b8
--- /dev/null
+++ b/src/spdk/test/nvmf/host/fio.sh
@@ -0,0 +1,92 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/scripts/common.sh
+source $rootdir/test/nvmf/common.sh
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+if [ ! -d /usr/src/fio ]; then
+ echo "FIO not available"
+ exit 0
+fi
+
+timing_enter fio
+timing_enter start_nvmf_tgt
+
+$NVMF_APP -m 0xF &
+nvmfpid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $nvmfpid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+bdevs="$bdevs $($rpc_py construct_malloc_bdev 64 512)"
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev
+done
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+
+PLUGIN_DIR=$rootdir/examples/nvme/fio_plugin
+
+# Test fio_plugin as host with malloc backend
+LD_PRELOAD=$PLUGIN_DIR/fio_plugin /usr/src/fio/fio $PLUGIN_DIR/example_config.fio --filename="trtype=RDMA adrfam=IPv4 \
+traddr=$NVMF_FIRST_TARGET_IP trsvcid=4420 ns=1"
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ # Test fio_plugin as host with nvme lvol backend
+ bdfs=$(iter_pci_class_code 01 08 02)
+ $rpc_py construct_nvme_bdev -b Nvme0 -t PCIe -a $(echo $bdfs | awk '{ print $1 }')
+ ls_guid=$($rpc_py construct_lvol_store Nvme0n1 lvs_0)
+ get_lvs_free_mb $ls_guid
+ lb_guid=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_0 $free_mb)
+ $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode2 -a -s SPDK00000000000001
+ for bdev in $lb_guid; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode2 $bdev
+ done
+ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode2 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+ LD_PRELOAD=$PLUGIN_DIR/fio_plugin /usr/src/fio/fio $PLUGIN_DIR/example_config.fio --filename="trtype=RDMA adrfam=IPv4 \
+ traddr=$NVMF_FIRST_TARGET_IP trsvcid=4420 ns=1"
+ $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode2
+
+ # Test fio_plugin as host with nvme lvol nested backend
+ ls_nested_guid=$($rpc_py construct_lvol_store $lb_guid lvs_n_0)
+ get_lvs_free_mb $ls_nested_guid
+ lb_nested_guid=$($rpc_py construct_lvol_bdev -u $ls_nested_guid lbd_nest_0 $free_mb)
+ $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode3 -a -s SPDK00000000000001
+ for bdev in $lb_nested_guid; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode3 $bdev
+ done
+ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode3 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+ LD_PRELOAD=$PLUGIN_DIR/fio_plugin /usr/src/fio/fio $PLUGIN_DIR/example_config.fio --filename="trtype=RDMA adrfam=IPv4 \
+ traddr=$NVMF_FIRST_TARGET_IP trsvcid=4420 ns=1"
+ $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode3
+
+ sync
+ # Delete lvol_bdev and destroy lvol_store.
+ $rpc_py destroy_lvol_bdev "$lb_nested_guid"
+ $rpc_py destroy_lvol_store -l lvs_n_0
+ $rpc_py destroy_lvol_bdev "$lb_guid"
+ $rpc_py destroy_lvol_store -l lvs_0
+ $rpc_py delete_nvme_controller Nvme0
+fi
+
+trap - SIGINT SIGTERM EXIT
+
+killprocess $nvmfpid
+timing_exit fio
diff --git a/src/spdk/test/nvmf/host/identify.sh b/src/spdk/test/nvmf/host/identify.sh
new file mode 100755
index 00000000..ad101980
--- /dev/null
+++ b/src/spdk/test/nvmf/host/identify.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+timing_enter identify
+timing_enter start_nvmf_tgt
+
+$NVMF_APP -m 0xF &
+nvmfpid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $nvmfpid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+bdevs="$bdevs $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+for bdev in $bdevs; do
+ # NOTE: This will assign the same NGUID and EUI64 to all bdevs,
+ # but currently we only have one (see above), so this is OK.
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 "$bdev" \
+ --nguid "ABCDEF0123456789ABCDEF0123456789" \
+ --eui64 "ABCDEF0123456789"
+done
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_FIRST_TARGET_IP -s 4420
+
+$rpc_py get_nvmf_subsystems
+
+$rootdir/examples/nvme/identify/identify -r "\
+ trtype:RDMA \
+ adrfam:IPv4 \
+ traddr:$NVMF_FIRST_TARGET_IP \
+ trsvcid:$NVMF_PORT \
+ subnqn:nqn.2014-08.org.nvmexpress.discovery" -L all
+$rootdir/examples/nvme/identify/identify -r "\
+ trtype:RDMA \
+ adrfam:IPv4 \
+ traddr:$NVMF_FIRST_TARGET_IP \
+ trsvcid:$NVMF_PORT \
+ subnqn:nqn.2016-06.io.spdk:cnode1" -L all
+sync
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+
+trap - SIGINT SIGTERM EXIT
+
+killprocess $nvmfpid
+timing_exit identify
diff --git a/src/spdk/test/nvmf/host/identify_kernel_nvmf.sh b/src/spdk/test/nvmf/host/identify_kernel_nvmf.sh
new file mode 100755
index 00000000..d6afe52f
--- /dev/null
+++ b/src/spdk/test/nvmf/host/identify_kernel_nvmf.sh
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+set -e
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter identify_kernel_nvmf_tgt
+
+subsystemname=nqn.2016-06.io.spdk:testnqn
+
+modprobe null_blk nr_devices=1
+modprobe nvmet
+modprobe nvmet-rdma
+modprobe nvmet-fc
+modprobe lpfc
+
+if [ ! -d /sys/kernel/config/nvmet/subsystems/$subsystemname ]; then
+ mkdir /sys/kernel/config/nvmet/subsystems/$subsystemname
+fi
+echo 1 > /sys/kernel/config/nvmet/subsystems/$subsystemname/attr_allow_any_host
+
+if [ ! -d /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1 ]; then
+ mkdir /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1
+fi
+
+echo -n /dev/nullb0 > /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1/device_path
+echo 1 > /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1/enable
+
+if [ ! -d /sys/kernel/config/nvmet/ports/1 ]; then
+ mkdir /sys/kernel/config/nvmet/ports/1
+fi
+
+echo -n rdma > /sys/kernel/config/nvmet/ports/1/addr_trtype
+echo -n ipv4 > /sys/kernel/config/nvmet/ports/1/addr_adrfam
+echo -n $NVMF_FIRST_TARGET_IP > /sys/kernel/config/nvmet/ports/1/addr_traddr
+echo -n $NVMF_PORT > /sys/kernel/config/nvmet/ports/1/addr_trsvcid
+
+ln -s /sys/kernel/config/nvmet/subsystems/$subsystemname /sys/kernel/config/nvmet/ports/1/subsystems/$subsystemname
+
+sleep 4
+
+$rootdir/examples/nvme/identify/identify -r "\
+ trtype:RDMA \
+ adrfam:IPv4 \
+ traddr:$NVMF_FIRST_TARGET_IP \
+ trsvcid:$NVMF_PORT \
+ subnqn:nqn.2014-08.org.nvmexpress.discovery" -t all
+$rootdir/examples/nvme/identify/identify -r "\
+ trtype:RDMA \
+ adrfam:IPv4 \
+ traddr:$NVMF_FIRST_TARGET_IP \
+ trsvcid:$NVMF_PORT \
+ subnqn:$subsystemname"
+
+rm -rf /sys/kernel/config/nvmet/ports/1/subsystems/$subsystemname
+
+echo 0 > /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1/enable
+echo -n 0 > /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1/device_path
+
+rmdir --ignore-fail-on-non-empty /sys/kernel/config/nvmet/subsystems/$subsystemname/namespaces/1
+rmdir --ignore-fail-on-non-empty /sys/kernel/config/nvmet/subsystems/$subsystemname
+rmdir --ignore-fail-on-non-empty /sys/kernel/config/nvmet/ports/1
+
+rmmod lpfc
+rmmod nvmet_fc
+rmmod nvmet-rdma
+rmmod null_blk
+rmmod nvmet
+
+timing_exit identify_kernel_nvmf_tgt
diff --git a/src/spdk/test/nvmf/host/perf.sh b/src/spdk/test/nvmf/host/perf.sh
new file mode 100755
index 00000000..24faed5b
--- /dev/null
+++ b/src/spdk/test/nvmf/host/perf.sh
@@ -0,0 +1,95 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter perf
+timing_enter start_nvmf_tgt
+
+$NVMF_APP -m 0xF -i 0 &
+nvmfpid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $nvmfpid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+$rootdir/scripts/gen_nvme.sh --json | $rpc_py load_subsystem_config
+timing_exit start_nvmf_tgt
+
+local_nvme_trid="trtype:PCIe traddr:"$($rpc_py get_subsystem_config bdev | jq -r '.[].params | select(.name=="Nvme0").traddr')
+bdevs="$bdevs $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+
+if [ -n "$local_nvme_trid" ]; then
+ bdevs="$bdevs Nvme0n1"
+fi
+
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev
+done
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+
+# Test multi-process access to local NVMe device
+if [ -n "$local_nvme_trid" ]; then
+ $rootdir/examples/nvme/perf/perf -i 0 -q 32 -o 4096 -w randrw -M 50 -t 1 -r "$local_nvme_trid"
+fi
+
+$rootdir/examples/nvme/perf/perf -q 32 -o 4096 -w randrw -M 50 -t 1 -r "trtype:RDMA adrfam:IPv4 traddr:$NVMF_FIRST_TARGET_IP trsvcid:4420"
+sync
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ # Configure nvme devices with nvmf lvol_bdev backend
+ if [ -n "$local_nvme_trid" ]; then
+ ls_guid=$($rpc_py construct_lvol_store Nvme0n1 lvs_0)
+ get_lvs_free_mb $ls_guid
+ lb_guid=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_0 $free_mb)
+
+ # Create lvol bdev for nested lvol stores
+ ls_nested_guid=$($rpc_py construct_lvol_store $lb_guid lvs_n_0)
+ get_lvs_free_mb $ls_nested_guid
+ lb_nested_guid=$($rpc_py construct_lvol_bdev -u $ls_nested_guid lbd_nest_0 $free_mb)
+ $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+ for bdev in $lb_nested_guid; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev
+ done
+ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s 4420
+ # Test perf as host with different io_size and qd_depth in nightly
+ qd_depth=("1" "128")
+ io_size=("512" "131072")
+ for qd in ${qd_depth[@]}; do
+ for o in ${io_size[@]}; do
+ $rootdir/examples/nvme/perf/perf -q $qd -o $o -w randrw -M 50 -t 10 -r "trtype:RDMA adrfam:IPv4 traddr:$NVMF_FIRST_TARGET_IP trsvcid:4420"
+ done
+ done
+
+ # Delete subsystems, lvol_bdev and destroy lvol_store.
+ $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+ $rpc_py destroy_lvol_bdev "$lb_nested_guid"
+ $rpc_py destroy_lvol_store -l lvs_n_0
+ $rpc_py destroy_lvol_bdev "$lb_guid"
+ $rpc_py destroy_lvol_store -l lvs_0
+ $rpc_py delete_nvme_controller Nvme0
+ fi
+fi
+
+trap - SIGINT SIGTERM EXIT
+
+killprocess $nvmfpid
+timing_exit perf
diff --git a/src/spdk/test/nvmf/lvol/nvmf_lvol.sh b/src/spdk/test/nvmf/lvol/nvmf_lvol.sh
new file mode 100755
index 00000000..7fe93a6c
--- /dev/null
+++ b/src/spdk/test/nvmf/lvol/nvmf_lvol.sh
@@ -0,0 +1,118 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+LVOL_BDEV_SIZE=10
+SUBSYS_NR=2
+LVOL_BDEVS_NR=6
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+function disconnect_nvmf()
+{
+ for i in `seq 1 $SUBSYS_NR`; do
+ nvme disconnect -n "nqn.2016-06.io.spdk:cnode${i}" || true
+ done
+}
+
+set -e
+
+# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization.
+# e.g. sudo ./nvmf_lvol.sh iso
+nvmftestinit $1
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+# SoftRoce does not have enough queues available for
+# multiconnection tests. Detect if we're using software RDMA.
+# If so - lower the number of subsystems for test.
+if check_ip_is_soft_roce $NVMF_FIRST_TARGET_IP; then
+ echo "Using software RDMA, lowering number of NVMeOF subsystems."
+ SUBSYS_NR=1
+fi
+
+timing_enter lvol_integrity
+timing_enter start_nvmf_tgt
+# Start up the NVMf target in another process
+$NVMF_APP -m 0xF &
+pid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; disconnect_nvmf; killprocess $pid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+modprobe -v nvme-rdma
+
+lvol_stores=()
+lvol_bdevs=()
+# Create the first LVS from a Raid-0 bdev, which is created from two malloc bdevs
+# Create remaining LVSs from a malloc bdev, respectively
+for i in `seq 1 $SUBSYS_NR`; do
+ if [ $i -eq 1 ]; then
+ # construct RAID bdev and put its name in $bdev
+ malloc_bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) "
+ malloc_bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+ $rpc_py construct_raid_bdev -n raid0 -s 64 -r 0 -b "$malloc_bdevs"
+ bdev="raid0"
+ else
+ # construct malloc bdev and put its name in $bdev
+ bdev="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+ fi
+ ls_guid="$($rpc_py construct_lvol_store $bdev lvs_$i -c 524288)"
+ lvol_stores+=("$ls_guid")
+
+ # 1 NVMe-OF subsystem per malloc bdev / lvol store / 10 lvol bdevs
+ ns_bdevs=""
+
+ # Create lvol bdevs on each lvol store
+ for j in `seq 1 $LVOL_BDEVS_NR`; do
+ lb_name="$($rpc_py construct_lvol_bdev -u $ls_guid lbd_$j $LVOL_BDEV_SIZE)"
+ lvol_bdevs+=("$lb_name")
+ ns_bdevs+="$lb_name "
+ done
+
+ $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode$i -a -s SPDK$i
+ for bdev in $ns_bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$i $bdev
+ done
+ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode$i -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT
+done
+
+for i in `seq 1 $SUBSYS_NR`; do
+ k=$[$i-1]
+ nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode${i}" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+
+ for j in `seq 1 $LVOL_BDEVS_NR`; do
+ waitforblk "nvme${k}n${j}"
+ done
+done
+
+$testdir/../fio/nvmf_fio.py 262144 64 randwrite 10 verify
+
+sync
+disconnect_nvmf
+
+for i in `seq 1 $SUBSYS_NR`; do
+ $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode$i
+done
+
+rm -f ./local-job*
+
+trap - SIGINT SIGTERM EXIT
+
+nvmfcleanup
+killprocess $pid
+nvmftestfini $1
+timing_exit lvol_integrity
diff --git a/src/spdk/test/nvmf/multiconnection/multiconnection.sh b/src/spdk/test/nvmf/multiconnection/multiconnection.sh
new file mode 100755
index 00000000..97155e78
--- /dev/null
+++ b/src/spdk/test/nvmf/multiconnection/multiconnection.sh
@@ -0,0 +1,82 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+MALLOC_BDEV_SIZE=128
+MALLOC_BLOCK_SIZE=512
+NVMF_SUBSYS=11
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization.
+# e.g. sudo ./multiconnection.sh iso
+nvmftestinit $1
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+# SoftRoce does not have enough queues available for
+# multiconnection tests. Detect if we're using software RDMA.
+# If so - lower the number of subsystems for test.
+if check_ip_is_soft_roce $NVMF_FIRST_TARGET_IP; then
+ echo "Using software RDMA, lowering number of NVMeOF subsystems."
+ NVMF_SUBSYS=1
+fi
+
+timing_enter multiconnection
+timing_enter start_nvmf_tgt
+# Start up the NVMf target in another process
+$NVMF_APP -m 0xF &
+pid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $pid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+modprobe -v nvme-rdma
+
+for i in `seq 1 $NVMF_SUBSYS`
+do
+ bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+ $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode$i -a -s SPDK$i
+ for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$i $bdev
+ done
+ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode$i -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT
+done
+
+for i in `seq 1 $NVMF_SUBSYS`; do
+ k=$[$i-1]
+ nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode${i}" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+
+ waitforblk "nvme${k}n1"
+done
+
+$testdir/../fio/nvmf_fio.py 262144 64 read 10
+$testdir/../fio/nvmf_fio.py 262144 64 randwrite 10
+
+sync
+for i in `seq 1 $NVMF_SUBSYS`; do
+ nvme disconnect -n "nqn.2016-06.io.spdk:cnode${i}" || true
+ $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode${i}
+done
+
+rm -f ./local-job0-0-verify.state
+
+trap - SIGINT SIGTERM EXIT
+
+nvmfcleanup
+killprocess $pid
+nvmftestfini $1
+timing_exit multiconnection
diff --git a/src/spdk/test/nvmf/nmic/nmic.sh b/src/spdk/test/nvmf/nmic/nmic.sh
new file mode 100755
index 00000000..7b66a977
--- /dev/null
+++ b/src/spdk/test/nvmf/nmic/nmic.sh
@@ -0,0 +1,87 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+MALLOC_BDEV_SIZE=128
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="python $rootdir/scripts/rpc.py"
+
+set -e
+
+# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization.
+# e.g. sudo ./nmic.sh iso
+nvmftestinit $1
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+NVMF_SECOND_TARGET_IP=$(echo "$RDMA_IP_LIST" | sed -n 2p)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter nmic
+timing_enter start_nvmf_tgt
+# Start up the NVMf target in another process
+$NVMF_APP -m 0xF &
+pid=$!
+
+trap "killprocess $pid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+# Create subsystems
+bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK1
+for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev
+done
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_FIRST_TARGET_IP -s "$NVMF_PORT"
+
+echo "test case1: single bdev can't be used in multiple subsystems"
+set +e
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode2 -a -s SPDK2
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode2 -t RDMA -a $NVMF_FIRST_TARGET_IP -s "$NVMF_PORT"
+for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode2 $bdev
+ nmic_status=$?
+
+ if [ $nmic_status -eq 0 ]; then
+ echo " Adding namespace passed - failure expected."
+ killprocess $pid
+ exit 1
+ else
+ echo " Adding namespace failed - expected result."
+ fi
+done
+set -e
+
+modprobe -v nvme-rdma
+
+echo "test case2: host connect to nvmf target in multiple paths"
+if [ ! -z $NVMF_SECOND_TARGET_IP ]; then
+ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_SECOND_TARGET_IP -s $NVMF_PORT
+
+ nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+ nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_SECOND_TARGET_IP" -s "$NVMF_PORT"
+
+ waitforblk "nvme0n1"
+
+ $testdir/../fio/nvmf_fio.py 4096 1 write 1 verify
+fi
+
+nvme disconnect -n "nqn.2016-06.io.spdk:cnode1" || true
+
+trap - SIGINT SIGTERM EXIT
+
+nvmfcleanup
+killprocess $pid
+
+nvmftestfini $1
+timing_exit nmic
diff --git a/src/spdk/test/nvmf/nvme_cli/nvme_cli.sh b/src/spdk/test/nvmf/nvme_cli/nvme_cli.sh
new file mode 100755
index 00000000..c8b40794
--- /dev/null
+++ b/src/spdk/test/nvmf/nvme_cli/nvme_cli.sh
@@ -0,0 +1,96 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+if [ -z "${DEPENDENCY_DIR}" ]; then
+ echo DEPENDENCY_DIR not defined!
+ exit 1
+fi
+
+spdk_nvme_cli="${DEPENDENCY_DIR}/nvme-cli"
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization.
+# e.g. sudo ./nvme_cli.sh iso
+nvmftestinit $1
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter nvme_cli
+timing_enter start_nvmf_tgt
+$NVMF_APP -m 0xF &
+nvmfpid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $nvmfpid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $nvmfpid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE) "
+bdevs+="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+
+modprobe -v nvme-rdma
+
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev
+done
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT
+
+nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+
+waitforblk "nvme0n1"
+waitforblk "nvme0n2"
+
+nvme list
+
+for ctrl in /dev/nvme?; do
+ nvme id-ctrl $ctrl
+ nvme smart-log $ctrl
+done
+
+for ns in /dev/nvme?n*; do
+ nvme id-ns $ns
+done
+
+nvme disconnect -n "nqn.2016-06.io.spdk:cnode1" || true
+nvme disconnect -n "nqn.2016-06.io.spdk:cnode2" || true
+
+if [ -d $spdk_nvme_cli ]; then
+ # Test spdk/nvme-cli NVMe-oF commands: discover, connect and disconnect
+ cd $spdk_nvme_cli
+ ./nvme discover -t rdma -a $NVMF_FIRST_TARGET_IP -s "$NVMF_PORT"
+ nvme_num_before_connection=$(nvme list |grep "/dev/nvme*"|awk '{print $1}'|wc -l)
+ ./nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+ sleep 1
+ nvme_num=$(nvme list |grep "/dev/nvme*"|awk '{print $1}'|wc -l)
+ ./nvme disconnect -n "nqn.2016-06.io.spdk:cnode1"
+ if [ $nvme_num -le $nvme_num_before_connection ]; then
+ echo "spdk/nvme-cli connect target devices failed"
+ exit 1
+ fi
+fi
+
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+trap - SIGINT SIGTERM EXIT
+
+nvmfcleanup
+killprocess $nvmfpid
+nvmftestfini $1
+report_test_completion "nvmf_spdk_nvme_cli"
+timing_exit nvme_cli
diff --git a/src/spdk/test/nvmf/nvmf.sh b/src/spdk/test/nvmf/nvmf.sh
new file mode 100755
index 00000000..70055777
--- /dev/null
+++ b/src/spdk/test/nvmf/nvmf.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/test/common/autotest_common.sh
+
+if [ ! $(uname -s) = Linux ]; then
+ exit 0
+fi
+
+source $rootdir/test/nvmf/common.sh
+
+timing_enter nvmf_tgt
+
+# NVMF_TEST_CORE_MASK is the biggest core mask specified by
+# any of the nvmf_tgt tests. Using this mask for the stub
+# ensures that if this mask spans CPU sockets, that we will
+# allocate memory from both sockets. The stub will *not*
+# run anything on the extra cores (and will sleep on master
+# core 0) so there is no impact to the nvmf_tgt tests by
+# specifying the bigger core mask.
+start_stub "-s 2048 -i 0 -m $NVMF_TEST_CORE_MASK"
+trap "kill_stub; exit 1" SIGINT SIGTERM EXIT
+
+export NVMF_APP_SHM_ID="0"
+export NVMF_APP="./app/nvmf_tgt/nvmf_tgt -i $NVMF_APP_SHM_ID -e 0xFFFF"
+
+run_test suite test/nvmf/filesystem/filesystem.sh
+run_test suite test/nvmf/discovery/discovery.sh
+if [ $SPDK_TEST_NVME_CLI -eq 1 ]; then
+ run_test suite test/nvmf/nvme_cli/nvme_cli.sh
+fi
+run_test suite test/nvmf/lvol/nvmf_lvol.sh
+run_test suite test/nvmf/shutdown/shutdown.sh
+run_test suite test/nvmf/bdev_io_wait/bdev_io_wait.sh
+run_test suite test/nvmf/create_transport/create_transport.sh
+
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ run_test suite test/nvmf/multiconnection/multiconnection.sh
+fi
+
+timing_enter host
+
+run_test suite test/nvmf/host/bdevperf.sh
+run_test suite test/nvmf/host/identify.sh
+run_test suite test/nvmf/host/perf.sh
+# TODO: disabled due to intermittent failures (RDMA_CM_EVENT_UNREACHABLE/ETIMEDOUT)
+#run_test test/nvmf/host/identify_kernel_nvmf.sh
+run_test suite test/nvmf/host/aer.sh
+run_test suite test/nvmf/host/fio.sh
+
+run_test suite test/nvmf/nmic/nmic.sh
+
+timing_exit host
+trap - SIGINT SIGTERM EXIT
+kill_stub
+
+# TODO: enable nvme device detachment for multi-process so that
+# we can use the stub for this test
+run_test suite test/nvmf/rpc/rpc.sh
+run_test suite test/nvmf/fio/fio.sh
+revert_soft_roce
+
+report_test_completion "nvmf"
+timing_exit nvmf_tgt
diff --git a/src/spdk/test/nvmf/nvmfjson/json_config.sh b/src/spdk/test/nvmf/nvmfjson/json_config.sh
new file mode 100755
index 00000000..bc624d21
--- /dev/null
+++ b/src/spdk/test/nvmf/nvmfjson/json_config.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+set -xe
+NVMF_JSON_DIR=$(readlink -f $(dirname $0))
+. $NVMF_JSON_DIR/../../json_config/common.sh
+base_nvmf_config=$JSON_DIR/base_nvmf_config.json
+last_nvmf_config=$JSON_DIR/last_nvmf_config.json
+
+function test_subsystems() {
+ run_spdk_tgt
+
+ rpc_py="$spdk_rpc_py"
+ clear_config_py="$spdk_clear_config_py"
+
+ $rpc_py start_subsystem_init
+ create_nvmf_subsystem_config
+ $rpc_py save_config > $base_nvmf_config
+ test_json_config
+
+ clear_nvmf_subsystem_config
+ kill_targets
+
+ run_spdk_tgt
+ $rpc_py load_config < $base_nvmf_config
+ $rpc_py save_config > $last_nvmf_config
+
+ json_diff $base_nvmf_config $last_nvmf_config
+
+ clear_nvmf_subsystem_config
+ kill_targets
+ rm -f $base_nvmf_config $last_nvmf_config
+}
+
+trap 'on_error_exit "${FUNCNAME}" "${LINENO}"; rm -f $base_nvmf_config $last_nvmf_config' ERR
+
+timing_enter nvmf_json_config
+test_subsystems
+timing_exit nvmf_json_config
+revert_soft_roce
+
+report_test_completion nvmf_json_config
diff --git a/src/spdk/test/nvmf/rpc/rpc.sh b/src/spdk/test/nvmf/rpc/rpc.sh
new file mode 100755
index 00000000..5e8837d1
--- /dev/null
+++ b/src/spdk/test/nvmf/rpc/rpc.sh
@@ -0,0 +1,145 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization.
+# e.g. sudo ./rpc.sh iso
+nvmftestinit $1
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter rpc
+timing_enter start_nvmf_tgt
+# Start up the NVMf target in another process
+$NVMF_APP -m 0xF &
+pid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $pid; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+# set times for subsystem construct/delete
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ times=50
+else
+ times=3
+fi
+
+MALLOC_BDEV_SIZE=64
+MALLOC_BLOCK_SIZE=512
+
+bdevs="$bdevs $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+
+# Disallow host NQN and make sure connect fails
+$rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev
+done
+$rpc_py nvmf_subsystem_allow_any_host -d nqn.2016-06.io.spdk:cnode1
+$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT
+
+modprobe -v nvme-rdma
+trap "killprocess $pid; nvmfcleanup; exit 1" SIGINT SIGTERM EXIT
+
+# This connect should fail - the host NQN is not allowed
+! nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+
+# Add the host NQN and verify that the connect succeeds
+$rpc_py nvmf_subsystem_add_host nqn.2016-06.io.spdk:cnode1 nqn.2016-06.io.spdk:host1
+nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+waitforblk "nvme0n1"
+nvme disconnect -n nqn.2016-06.io.spdk:cnode1
+
+# Remove the host and verify that the connect fails
+$rpc_py nvmf_subsystem_remove_host nqn.2016-06.io.spdk:cnode1 nqn.2016-06.io.spdk:host1
+! nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+
+# Allow any host and verify that the connect succeeds
+$rpc_py nvmf_subsystem_allow_any_host -e nqn.2016-06.io.spdk:cnode1
+nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+waitforblk "nvme0n1"
+nvme disconnect -n nqn.2016-06.io.spdk:cnode1
+
+$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1
+
+# do frequent add delete of namespaces with different nsid.
+for i in `seq 1 $times`
+do
+ j=0
+ for bdev in $bdevs; do
+ let j=j+1
+ $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode$j -s SPDK00000000000001
+ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode$j -t RDMA -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$j $bdev -n 5
+ $rpc_py nvmf_subsystem_allow_any_host nqn.2016-06.io.spdk:cnode$j
+ nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode$j -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+ done
+
+ waitforblk "nvme0n1"
+ n=$j
+ for j in `seq 1 $n`
+ do
+ nvme disconnect -n nqn.2016-06.io.spdk:cnode$j
+ done
+
+ j=0
+ for bdev in $bdevs; do
+ let j=j+1
+ $rpc_py nvmf_subsystem_remove_ns nqn.2016-06.io.spdk:cnode$j 5
+ done
+
+ n=$j
+ for j in `seq 1 $n`
+ do
+ $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode$j
+ done
+
+done
+
+nvmfcleanup
+trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT
+
+# do frequent add delete.
+for i in `seq 1 $times`
+do
+ j=0
+ for bdev in $bdevs; do
+ let j=j+1
+ $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode$j -s SPDK00000000000001
+ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode$j -t RDMA -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$j $bdev
+ $rpc_py nvmf_subsystem_allow_any_host nqn.2016-06.io.spdk:cnode$j
+ done
+
+ j=0
+ for bdev in $bdevs; do
+ let j=j+1
+ $rpc_py nvmf_subsystem_remove_ns nqn.2016-06.io.spdk:cnode$j $j
+ done
+
+ n=$j
+ for j in `seq 1 $n`
+ do
+ $rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode$j
+ done
+done
+
+trap - SIGINT SIGTERM EXIT
+
+killprocess $pid
+nvmftestfini $1
+timing_exit rpc
diff --git a/src/spdk/test/nvmf/shutdown/shutdown.sh b/src/spdk/test/nvmf/shutdown/shutdown.sh
new file mode 100755
index 00000000..f68c4b21
--- /dev/null
+++ b/src/spdk/test/nvmf/shutdown/shutdown.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../../..)
+source $rootdir/test/common/autotest_common.sh
+source $rootdir/test/nvmf/common.sh
+
+MALLOC_BDEV_SIZE=128
+MALLOC_BLOCK_SIZE=512
+
+rpc_py="$rootdir/scripts/rpc.py"
+
+set -e
+
+# pass the parameter 'iso' to this script when running it in isolation to trigger rdma device initialization.
+# e.g. sudo ./shutdown.sh iso
+nvmftestinit $1
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_FIRST_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+if [ -z $NVMF_FIRST_TARGET_IP ]; then
+ echo "no NIC for nvmf test"
+ exit 0
+fi
+
+timing_enter shutdown
+timing_enter start_nvmf_tgt
+# Start up the NVMf target in another process
+$NVMF_APP -m 0xF &
+pid=$!
+
+trap "process_shm --id $NVMF_APP_SHM_ID; killprocess $pid; nvmfcleanup; nvmftestfini $1; exit 1" SIGINT SIGTERM EXIT
+
+waitforlisten $pid
+$rpc_py nvmf_create_transport -t RDMA -u 8192 -p 4
+timing_exit start_nvmf_tgt
+
+num_subsystems=10
+# SoftRoce does not have enough queues available for
+# this test. Detect if we're using software RDMA.
+# If so, only use four subsystems.
+if check_ip_is_soft_roce "$NVMF_FIRST_TARGET_IP"; then
+ num_subsystems=4
+fi
+
+# Create subsystems
+for i in `seq 1 $num_subsystems`
+do
+ bdevs="$($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)"
+ $rpc_py nvmf_subsystem_create nqn.2016-06.io.spdk:cnode$i -a -s SPDK$i
+ for bdev in $bdevs; do
+ $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$i $bdev
+ done
+ $rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode$i -t rdma -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT
+done
+
+modprobe -v nvme-rdma
+modprobe -v nvme-fabrics
+
+# Repeatedly connect and disconnect
+for ((x=0; x<5;x++)); do
+ # Connect kernel host to subsystems
+ for i in `seq 1 $num_subsystems`; do
+ nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode${i}" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+ done
+ # Disconnect the subsystems in reverse order
+ for i in `seq $num_subsystems -1 1`; do
+ nvme disconnect -n nqn.2016-06.io.spdk:cnode${i}
+ done
+done
+
+# Start a series of connects right before disconnecting
+for i in `seq 1 $num_subsystems`; do
+ nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode${i}" -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT"
+done
+
+waitforblk "nvme0n1"
+
+# Kill nvmf tgt without removing any subsystem to check whether it can shutdown correctly
+rm -f ./local-job0-0-verify.state
+
+trap - SIGINT SIGTERM EXIT
+
+killprocess $pid
+
+nvmfcleanup
+nvmftestfini $1
+timing_exit shutdown
diff --git a/src/spdk/test/nvmf/test_plan.md b/src/spdk/test/nvmf/test_plan.md
new file mode 100644
index 00000000..94347ef8
--- /dev/null
+++ b/src/spdk/test/nvmf/test_plan.md
@@ -0,0 +1,95 @@
+# SPDK nvmf_tgt test plan
+
+## Objective
+The purpose of these tests is to verify correct behavior of SPDK NVMe-oF
+feature.
+These tests are run either per-commit or as nightly tests.
+
+## Configuration
+All tests share the same basic configuration file for SPDK nvmf_tgt to run.
+Static configuration from config file consists of setting number of per session
+queues and enabling RPC for further configuration via RPC calls.
+RPC calls used for dynamic configuration consist:
+- creating Malloc backend devices
+- creating Null Block backend devices
+- constructing NVMe-oF subsystems
+- deleting NVMe-oF subsystems
+
+### Tests
+
+#### Test 1: NVMe-oF namespace on a Logical Volumes device
+This test configures a SPDK NVMe-oF subsystem backed by logical volume
+devices and uses FIO to generate I/Os that target those subsystems.
+The logical volume bdevs are backed by malloc bdevs.
+Test steps:
+- Step 1: Assign IP addresses to RDMA NICs.
+- Step 2: Start SPDK nvmf_tgt application.
+- Step 3: Create malloc bdevs.
+- Step 4: Create logical volume stores on malloc bdevs.
+- Step 5: Create 10 logical volume bdevs on each logical volume store.
+- Step 6: Create NVMe-oF subsystems with logical volume bdev namespaces.
+- Step 7: Connect to NVMe-oF susbsystems with kernel initiator.
+- Step 8: Run FIO with workload parameters: blocksize=256k, iodepth=64,
+workload=randwrite; varify flag is enabled so that FIO reads and verifies
+the data written to the logical device. The run time is 10 seconds for a
+quick test an 10 minutes for longer nightly test.
+- Step 9: Disconnect kernel initiator from NVMe-oF subsystems.
+- Step 10: Delete NVMe-oF subsystems from configuration.
+
+### Compatibility testing
+
+- Verify functionality of SPDK `nvmf_tgt` with Linux kernel NVMe-oF host
+ - Exercise various kernel NVMe host parameters
+ - `nr_io_queues`
+ - `queue_size`
+ - Test discovery subsystem with `nvme` CLI tool
+ - Verify that discovery service works correctly with `nvme discover`
+ - Verify that large responses work (many subsystems)
+
+### Specification compliance
+
+- NVMe base spec compliance
+ - Verify all mandatory admin commands are implemented
+ - Get Log Page
+ - Identify (including all mandatory CNS values)
+ - Identify Namespace
+ - Identify Controller
+ - Active Namespace List
+ - Allocated Namespace List
+ - Identify Allocated Namespace
+ - Attached Controller List
+ - Controller List
+ - Abort
+ - Set Features
+ - Get Features
+ - Asynchronous Event Request
+ - Keep Alive
+ - Verify all mandatory NVM command set I/O commands are implemented
+ - Flush
+ - Write
+ - Read
+ - Verify all mandatory log pages
+ - Error Information
+ - SMART / Health Information
+ - Firmware Slot Information
+ - Verify all mandatory Get/Set Features
+ - Arbitration
+ - Power Management
+ - Temperature Threshold
+ - Error Recovery
+ - Number of Queues
+ - Write Atomicity Normal
+ - Asynchronous Event Configuration
+ - Verify all implemented commands behave as required by the specification
+- Fabric command processing
+ - Verify that Connect commands with invalid parameters are failed with correct response
+ - Invalid RECFMT
+ - Invalid SQSIZE
+ - Invalid SUBNQN, HOSTNQN (too long, incorrect format, not null terminated)
+ - QID != 0 before admin queue created
+ - CNTLID != 0xFFFF (static controller mode)
+ - Verify that non-Fabric commands are only allowed in the correct states
+
+### Configuration and RPC
+
+- Verify that invalid NQNs cannot be configured via conf file or RPC
diff --git a/src/spdk/test/pmem/common.sh b/src/spdk/test/pmem/common.sh
new file mode 100644
index 00000000..add36719
--- /dev/null
+++ b/src/spdk/test/pmem/common.sh
@@ -0,0 +1,107 @@
+BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../ && pwd)"
+rpc_py="$TEST_DIR/scripts/rpc.py "
+
+source $TEST_DIR/test/common/autotest_common.sh
+
+# Prints error message and return error code, closes vhost app and remove
+# pmem pool file
+# input: error message, error code
+function error()
+{
+ local error_code=${2:-1}
+ echo "==========="
+ echo -e "ERROR: $1"
+ echo "error code: $error_code"
+ echo "==========="
+ vhost_kill
+ pmem_clean_pool_file
+ return $error_code
+}
+
+# check if there is pool file & remove it
+# input: path to pool file
+# default: $TEST_DIR/test/pmem/pool_file
+function pmem_clean_pool_file()
+{
+ local pool_file=${1:-$TEST_DIR/test/pmem/pool_file}
+
+ if [ -f $pool_file ]; then
+ echo "Deleting old pool_file"
+ rm $pool_file
+ fi
+}
+
+# create new pmem file
+# input: path to pool file, size in MB, block_size
+# default: $TEST_DIR/test/pmem/pool_file 32 512
+function pmem_create_pool_file()
+{
+ local pool_file=${1:-$TEST_DIR/test/pmem/pool_file}
+ local size=${2:-32}
+ local block_size=${3:-512}
+
+ pmem_clean_pool_file $pool_file
+ echo "Creating new pool file"
+ if ! $rpc_py create_pmem_pool $pool_file $size $block_size; then
+ error "Creating pool_file failed!"
+ fi
+
+ if [ ! -f $pool_file ]; then
+ error "Creating pool_file failed!"
+ fi
+}
+
+function pmem_unmount_ramspace
+{
+ if [ -d "$TEST_DIR/test/pmem/ramspace" ]; then
+ if mount | grep -q "$TEST_DIR/test/pmem/ramspace"; then
+ umount $TEST_DIR/test/pmem/ramspace
+ fi
+
+ rm -rf $TEST_DIR/test/pmem/ramspace
+ fi
+}
+
+function pmem_print_tc_name
+{
+ echo ""
+ echo "==============================================================="
+ echo "Now running: $1"
+ echo "==============================================================="
+}
+
+function vhost_start()
+{
+ local vhost_pid
+
+ $TEST_DIR/app/vhost/vhost &
+ if [ $? != 0 ]; then
+ echo -e "ERROR: Failed to launch vhost!"
+ return 1
+ fi
+
+ vhost_pid=$!
+ echo $vhost_pid > $TEST_DIR/test/pmem/vhost.pid
+ waitforlisten $vhost_pid
+}
+
+function vhost_kill()
+{
+ local vhost_pid_file="$TEST_DIR/test/pmem/vhost.pid"
+ local vhost_pid="$(cat $vhost_pid_file)"
+
+ if [[ ! -f $TEST_DIR/test/pmem/vhost.pid ]]; then
+ echo -e "ERROR: No vhost pid file found!"
+ return 1
+ fi
+
+ if ! kill -s INT $vhost_pid; then
+ echo -e "ERROR: Failed to exit vhost / invalid pid!"
+ rm $vhost_pid_file
+ return 1
+ fi
+
+ sleep 1
+ rm $vhost_pid_file
+}
diff --git a/src/spdk/test/pmem/json_config/json_config.sh b/src/spdk/test/pmem/json_config/json_config.sh
new file mode 100755
index 00000000..bd232b8f
--- /dev/null
+++ b/src/spdk/test/pmem/json_config/json_config.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+set -ex
+VHOST_JSON_DIR=$(readlink -f $(dirname $0))
+. $VHOST_JSON_DIR/../../json_config/common.sh
+
+function test_subsystems() {
+ run_spdk_tgt
+
+ rpc_py="$spdk_rpc_py"
+ clear_config_py="$spdk_clear_config_py"
+ $rpc_py start_subsystem_init
+
+ create_pmem_bdev_subsytem_config
+ test_json_config
+ clear_pmem_bdev_subsystem_config
+
+ kill_targets
+}
+
+trap 'on_error_exit "${FUNCNAME}" "${LINENO}"' ERR
+timing_enter json_config_pmem
+
+test_subsystems
+timing_exit json_config_pmem
+report_test_completion json_config_pmem
diff --git a/src/spdk/test/pmem/pmem.sh b/src/spdk/test/pmem/pmem.sh
new file mode 100755
index 00000000..15188635
--- /dev/null
+++ b/src/spdk/test/pmem/pmem.sh
@@ -0,0 +1,701 @@
+#!/usr/bin/env bash
+
+BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../ && pwd)"
+
+enable_script_debug=false
+test_info=false
+test_create=false
+test_delete=false
+test_construct_bdev=false
+test_delete_bdev=false
+test_all=true
+test_all_get=false
+default_pool_file=$TEST_DIR/test/pmem/pool_file
+obj_pool_file=$TEST_DIR/test/pmem/obj_pool_file
+bdev_name=pmem0
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for automated RPC tests for PMEM"
+ echo "For test details, check test_plan.md or"
+ echo "https://review.gerrithub.io/#/c/378618/18/test/pmem/test_plan.md"
+ echo
+ echo "Usage: $(basename $1) [OPTIONS]"
+ echo
+ echo "-h, --help Print help and exit"
+ echo "-x set -x for script debug"
+ echo " --info Run test cases for pmem_pool_info"
+ echo " --create Run test cases for create_pmem_pool"
+ echo " --delete Run test cases for delete_pmem_pool"
+ echo " --construct_bdev Run test cases for constructing pmem bdevs"
+ echo " --delete_bdev Run test cases for deleting pmem bdevs"
+ echo " --all Run all test cases (default)"
+ exit 0
+}
+
+while getopts 'xh-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 ;;
+ info) test_info=true; test_all=false;;
+ create) test_create=true; test_all=false;;
+ delete) test_delete=true; test_all=false;;
+ construct_bdev) test_construct_bdev=true; test_all=false;;
+ delete_bdev) test_delete_bdev=true; test_all=false;;
+ all) test_all_get=true;;
+ *) usage $0 "Invalid argument '$OPTARG'" ;;
+ esac
+ ;;
+ h) usage $0 ;;
+ x) enable_script_debug=true ;;
+ *) usage $0 "Invalid argument '$OPTARG'"
+ esac
+done
+
+if $test_all_get; then
+ test_all=true
+fi
+
+if [[ $EUID -ne 0 ]]; then
+ echo "Go away user come back as root"
+ exit 1
+fi
+
+source $TEST_DIR/test/pmem/common.sh
+source $TEST_DIR/test/common/autotest_common.sh
+
+#================================================
+# pmem_pool_info tests
+#================================================
+function pmem_pool_info_tc1()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+
+ if $rpc_py pmem_pool_info; then
+ error "pmem_pool_info passed with missing argument!"
+ fi
+
+ return 0
+}
+
+function pmem_pool_info_tc2()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+
+ if $rpc_py pmem_pool_info $TEST_DIR/non/existing/path/non_existent_file; then
+ error "pmem_pool_info passed with invalid path!"
+ fi
+
+ return 0
+}
+
+function pmem_pool_info_tc3()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file
+
+ echo "Creating new type OBJ pool file"
+ if hash pmempool; then
+ pmempool create -s 32000000 obj $obj_pool_file
+ else
+ echo "Warning: pmempool package not found! Creating stub file."
+ truncate -s "32M" $obj_pool_file
+ fi
+
+ if $rpc_py pmem_pool_info $TEST_DIR/test/pmem/obj_pool_file; then
+ pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file
+ error "Pmem_pool_info passed with invalid pool_file type!"
+ fi
+
+ pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file
+ return 0
+}
+
+function pmem_pool_info_tc4()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+
+ pmem_create_pool_file
+ if ! $rpc_py pmem_pool_info $default_pool_file; then
+ error "Failed to get pmem_pool_info!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+#================================================
+# create_pmem_pool tests
+#================================================
+function create_pmem_pool_tc1()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+
+ if $rpc_py create_pmem_pool 32 512; then
+ error "Mem pool file created w/out given path!"
+ fi
+
+ if $rpc_py create_pmem_pool $default_pool_file; then
+ error "Mem pool file created w/out size & block size arguments!"
+ fi
+
+ if $rpc_py pmem_pool_info $default_pool_file; then
+ error "create_pmem_pool created invalid pool file!"
+ fi
+
+ if $rpc_py create_pmem_pool $default_pool_file 32; then
+ error "Mem pool file created w/out block size argument!"
+ fi
+
+ if $rpc_py pmem_pool_info $default_pool_file; then
+ error "create_pmem_pool created invalid pool file!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+function create_pmem_pool_tc2()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+
+ if $rpc_py create_pmem_pool $TEST_DIR/non/existing/path/non_existent_file 32 512; then
+ error "Mem pool file created with incorrect path!"
+ fi
+
+ if $rpc_py pmem_pool_info $TEST_DIR/non/existing/path/non_existent_file; then
+ error "create_pmem_pool created invalid pool file!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+function create_pmem_pool_tc3()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+
+ if ! $rpc_py create_pmem_pool $default_pool_file 256 512; then
+ error "Failed to create pmem pool!"
+ fi
+
+ if ! $rpc_py pmem_pool_info $default_pool_file; then
+ error "Failed to get pmem info"
+ fi
+
+ if ! $rpc_py delete_pmem_pool $default_pool_file; then
+ error "Failed to delete pool file!"
+ fi
+
+ if $rpc_py pmem_pool_info $default_pool_file; then
+ error "Got pmem file info but file should be deleted"
+ fi
+
+ if [ -f $default_pool_file ]; then
+ error "Failed to delete pmem file!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+function create_pmem_pool_tc4()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+
+ pmem_unmount_ramspace
+ mkdir $TEST_DIR/test/pmem/ramspace
+ mount -t tmpfs -o size=300m tmpfs $TEST_DIR/test/pmem/ramspace
+ if ! $rpc_py create_pmem_pool $TEST_DIR/test/pmem/ramspace/pool_file 256 512; then
+ pmem_unmount_ramspace
+ error "Failed to create pmem pool!"
+ fi
+
+ if ! $rpc_py pmem_pool_info $TEST_DIR/test/pmem/ramspace/pool_file; then
+ pmem_unmount_ramspace
+ error "Failed to get pmem info"
+ fi
+
+ if ! $rpc_py delete_pmem_pool $TEST_DIR/test/pmem/ramspace/pool_file; then
+ pmem_unmount_ramspace
+ error "Failed to delete pool file!"
+ fi
+
+ if [ -f $TEST_DIR/test/pmem/ramspace/pool_file ]; then
+ pmem_unmount_ramspace
+ error "Failed to delete pmem file / file still exists!"
+ fi
+
+ pmem_unmount_ramspace
+ return 0
+}
+
+function create_pmem_pool_tc5()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+ local pmem_block_size
+ local pmem_num_block
+
+ if ! $rpc_py create_pmem_pool $default_pool_file 256 512; then
+ error "Failed to create pmem pool!"
+ fi
+
+ if $rpc_py pmem_pool_info $default_pool_file; then
+ pmem_block_size=$($rpc_py pmem_pool_info $default_pool_file | jq -r '.[] .block_size')
+ pmem_num_block=$($rpc_py pmem_pool_info $default_pool_file | jq -r '.[] .num_blocks')
+ else
+ error "Failed to get pmem info!"
+ fi
+
+ if $rpc_py create_pmem_pool $default_pool_file 512 4096; then
+ error "Pmem pool with already occupied path has been created!"
+ fi
+
+ if $rpc_py pmem_pool_info $default_pool_file; then
+ if [ $pmem_block_size != $($rpc_py pmem_pool_info $default_pool_file | jq -r '.[] .block_size') ]; then
+ error "Invalid block size of pmem pool!"
+ fi
+
+ if [ $pmem_num_block != $($rpc_py pmem_pool_info $default_pool_file | jq -r '.[] .num_blocks') ]; then
+ error "Invalid number of blocks of pmem pool!"
+ fi
+ else
+ error "Failed to get pmem info!"
+ fi
+
+ if ! $rpc_py delete_pmem_pool $default_pool_file; then
+ error "Failed to delete pmem file!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+function create_pmem_pool_tc6()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+ local created_pmem_block_size
+
+ for i in 511 512 1024 2048 4096 131072 262144
+ do
+ if ! $rpc_py create_pmem_pool $default_pool_file 256 $i; then
+ error "Failed to create pmem pool!"
+ fi
+
+ created_pmem_block_size=$($rpc_py pmem_pool_info $default_pool_file | jq -r '.[] .block_size')
+ if [ $? != 0 ]; then
+ error "Failed to get pmem info!"
+ fi
+
+ if [ $i != $created_pmem_block_size ]; then
+ error "Invalid block size of pmem pool!"
+ fi
+
+ if ! $rpc_py delete_pmem_pool $default_pool_file; then
+ error "Failed to delete pmem file!"
+ fi
+ done
+
+ pmem_clean_pool_file
+ return 0
+}
+
+function create_pmem_pool_tc7()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+
+ if $rpc_py create_pmem_pool $default_pool_file 15 512; then
+ error "Created pmem pool with invalid size!"
+ fi
+
+ if $rpc_py pmem_pool_info $default_pool_file; then
+ error "Pmem file shouldn' exist!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+function create_pmem_pool_tc8()
+{
+ pmem_print_tc_name "create_pmem_pool_tc8"
+ pmem_clean_pool_file
+
+ if $rpc_py create_pmem_pool $default_pool_file 32 65536; then
+ error "Created pmem pool with invalid block number!"
+ fi
+
+ if $rpc_py pmem_pool_info $default_pool_file; then
+ error "Pmem file shouldn' exist!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+function create_pmem_pool_tc9()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+
+ if $rpc_py create_pmem_pool $default_pool_file 256 -1; then
+ error "Created pmem pool with negative block size number!"
+ fi
+
+ if $rpc_py pmem_pool_info $default_pool_file; then
+ error "create_pmem_pool create invalid pool file!"
+ fi
+
+ if $rpc_py create_pmem_pool $default_pool_file -1 512; then
+ error "Created pmem pool with negative size number!"
+ fi
+
+ if $rpc_py pmem_pool_info $default_pool_file; then
+ error "create_pmem_pool create invalid pool file!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+#================================================
+# delete_pmem_pool tests
+#================================================
+function delete_pmem_pool_tc1()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+
+ if $rpc_py delete_pmem_pool $default_pool_file; then
+ error "delete_pmem_pool deleted inexistant pool file!"
+ fi
+
+ return 0
+}
+
+function delete_pmem_pool_tc2()
+{
+ pmem_print_tc_name "delete_pmem_pool_tc2"
+ pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file
+
+ echo "Creating new type OBJ pool file"
+ if hash pmempool; then
+ pmempool create -s 32000000 obj $obj_pool_file
+ else
+ echo "Warning: pmempool package not found! Creating stub file."
+ truncate -s "32M" $obj_pool_file
+ fi
+
+ if $rpc_py delete_pmem_pool $TEST_DIR/test/pmem/obj_pool_file; then
+ pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file
+ error "delete_pmem_pool deleted invalid pmem pool type!"
+ fi
+
+ pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file
+ return 0
+}
+
+function delete_pmem_pool_tc3()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+
+ pmem_create_pool_file
+ if ! $rpc_py pmem_pool_info $default_pool_file; then
+ error "Failed to get info on pmem pool file!"
+ fi
+
+ if ! $rpc_py delete_pmem_pool $default_pool_file; then
+ error "Failed to delete pmem pool file!"
+ fi
+
+ if $rpc_py pmem_pool_info $default_pool_file; then
+ error "Pmem pool file exists after using pmem_pool_info!"
+ fi
+
+ return 0
+}
+
+function delete_pmem_pool_tc4()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+
+ delete_pmem_pool_tc3
+ if $rpc_py delete_pmem_pool $default_pool_file; then
+ error "Deleted pmem pool file that shouldn't exist!"
+ fi
+
+ return 0
+}
+
+#================================================
+# construct_pmem_bdev tests
+#================================================
+function construct_pmem_bdev_tc1()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+
+ pmem_create_pool_file
+ if $rpc_py construct_pmem_bdev; then
+ error "construct_pmem_bdev passed with missing argument!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+function construct_pmem_bdev_tc2()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+
+ pmem_create_pool_file
+ if $rpc_py construct_pmem_bdev -n $bdev_name $TEST_DIR/non/existing/path/non_existent_file; then
+ error "Created pmem bdev w/out valid pool file!"
+ fi
+
+ if $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi pmem; then
+ error "construct_pmem_bdev passed with invalid argument!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+function construct_pmem_bdev_tc3()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+
+ truncate -s 32M $TEST_DIR/test/pmem/random_file
+ if $rpc_py construct_pmem_bdev -n $bdev_name $TEST_DIR/test/pmem/random_file; then
+ error "Created pmem bdev from random file!"
+ fi
+
+ if [ -f $TEST_DIR/test/pmem/random_file ]; then
+ echo "Deleting previously created random file"
+ rm $TEST_DIR/test/pmem/random_file
+ fi
+
+ return 0
+}
+
+function construct_pmem_bdev_tc4()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file
+
+ echo "Creating new type OBJ pool file"
+ if hash pmempool; then
+ pmempool create -s 32000000 obj $obj_pool_file
+ else
+ echo "Warning: pmempool package not found! Creating stub file."
+ truncate -s "32M" $obj_pool_file
+ fi
+
+ if $rpc_py construct_pmem_bdev -n $bdev_name $TEST_DIR/test/pmem/obj_pool_file; then
+ pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file
+ error "Created pmem bdev from obj type pmem file!"
+ fi
+
+ pmem_clean_pool_file $TEST_DIR/test/pmem/obj_pool_file
+ return 0
+}
+
+function construct_pmem_bdev_tc5()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+ pmem_create_pool_file
+ local pmem_bdev_name
+
+ if ! $rpc_py pmem_pool_info $default_pool_file; then
+ error "Failed to get pmem info!"
+ fi
+
+ pmem_bdev_name=$($rpc_py construct_pmem_bdev -n $bdev_name $default_pool_file)
+ if [ $? != 0 ]; then
+ error "Failed to create pmem bdev"
+ fi
+
+ if ! $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi $pmem_bdev_name; then
+ error "Pmem bdev not found!"
+ fi
+
+ if ! $rpc_py delete_pmem_bdev $pmem_bdev_name; then
+ error "Failed to delete pmem bdev!"
+ fi
+
+ if ! $rpc_py delete_pmem_pool $default_pool_file; then
+ error "Failed to delete pmem pool file!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+function construct_pmem_bdev_tc6()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ local pmem_bdev_name
+ pmem_clean_pool_file
+
+ pmem_create_pool_file
+ if ! $rpc_py pmem_pool_info $default_pool_file; then
+ error "Failed to get info on pmem pool file!"
+ fi
+
+ pmem_bdev_name=$($rpc_py construct_pmem_bdev -n $bdev_name $default_pool_file)
+ if [ $? != 0 ]; then
+ error "Failed to create pmem bdev!"
+ fi
+
+ if ! $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi $pmem_bdev_name; then
+ error "Pmem bdev not found!"
+ fi
+
+ if $rpc_py construct_pmem_bdev -n $bdev_name $default_pool_file; then
+ error "Constructed pmem bdev with occupied path!"
+ fi
+
+ if ! $rpc_py delete_pmem_bdev $pmem_bdev_name; then
+ error "Failed to delete pmem bdev!"
+ fi
+
+ if ! $rpc_py delete_pmem_pool $default_pool_file; then
+ error "Failed to delete pmem pool file!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+#================================================
+# delete_pmem_bdev tests
+#================================================
+function delete_bdev_tc1()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ local pmem_bdev_name
+ local bdevs_names
+ pmem_clean_pool_file
+
+ pmem_create_pool_file $default_pool_file 256 512
+ if ! $rpc_py pmem_pool_info $default_pool_file; then
+ error "Failed to get pmem info!"
+ fi
+
+ pmem_bdev_name=$($rpc_py construct_pmem_bdev -n $bdev_name $default_pool_file)
+ if [ $? != 0 ]; then
+ error "Failed to create pmem bdev!"
+ fi
+
+ if ! $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi $pmem_bdev_name; then
+ error "$pmem_bdev_name bdev not found!"
+ fi
+
+ if ! $rpc_py delete_pmem_bdev $pmem_bdev_name; then
+ error "Failed to delete $pmem_bdev_name bdev!"
+ fi
+
+ bdevs_names=$($rpc_py get_bdevs | jq -r '.[] .name')
+ if echo $bdevs_names | grep -qi $pmem_bdev_name; then
+ error "$pmem_bdev_name bdev is not deleted!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+function delete_bdev_tc2()
+{
+ pmem_print_tc_name ${FUNCNAME[0]}
+ pmem_clean_pool_file
+ pmem_create_pool_file $default_pool_file 256 512
+ local pmem_bdev_name
+
+ if ! $rpc_py pmem_pool_info $default_pool_file; then
+ error "Failed to get pmem info!"
+ fi
+
+ pmem_bdev_name=$($rpc_py construct_pmem_bdev -n $bdev_name $default_pool_file)
+ if [ $? != 0 ]; then
+ error "Failed to create pmem bdev"
+ fi
+
+ if ! $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi $pmem_bdev_name; then
+ error "Pmem bdev not found!"
+ fi
+
+ if ! $rpc_py delete_pmem_bdev $pmem_bdev_name; then
+ error "Failed to delete pmem bdev!"
+ fi
+
+ if $rpc_py delete_pmem_bdev $pmem_bdev_name; then
+ error "delete_pmem_bdev deleted pmem bdev for second time!"
+ fi
+
+ pmem_clean_pool_file
+ return 0
+}
+
+timing_enter pmem
+vhost_start
+if ! $enable_script_debug; then
+ set +x
+fi
+
+if $test_info || $test_all; then
+ pmem_pool_info_tc1
+ pmem_pool_info_tc2
+ pmem_pool_info_tc3
+ pmem_pool_info_tc4
+fi
+
+if $test_create || $test_all; then
+ create_pmem_pool_tc1
+ create_pmem_pool_tc2
+ create_pmem_pool_tc3
+ create_pmem_pool_tc4
+ create_pmem_pool_tc5
+ create_pmem_pool_tc6
+ create_pmem_pool_tc7
+ create_pmem_pool_tc8
+ create_pmem_pool_tc9
+fi
+
+if $test_delete || $test_all; then
+ delete_pmem_pool_tc1
+ delete_pmem_pool_tc2
+ delete_pmem_pool_tc3
+ delete_pmem_pool_tc4
+fi
+
+if $test_construct_bdev || $test_all; then
+ construct_pmem_bdev_tc1
+ construct_pmem_bdev_tc2
+ construct_pmem_bdev_tc3
+ construct_pmem_bdev_tc4
+ construct_pmem_bdev_tc5
+ construct_pmem_bdev_tc6
+fi
+
+if $test_delete_bdev || $test_all; then
+ delete_bdev_tc1
+ delete_bdev_tc2
+fi
+
+pmem_clean_pool_file
+report_test_completion "pmem"
+vhost_kill
+timing_exit pmem
diff --git a/src/spdk/test/pmem/test_plan.md b/src/spdk/test/pmem/test_plan.md
new file mode 100644
index 00000000..18e99f36
--- /dev/null
+++ b/src/spdk/test/pmem/test_plan.md
@@ -0,0 +1,310 @@
+# PMEM bdev feature test plan
+
+## Objective
+The purpose of these tests is to verify possibility of using pmem bdev
+configuration in SPDK by running functional tests FIO traffic verification
+tests.
+
+## Configuration
+Configuration in tests is to be done using example stub application
+(spdk/example/bdev/io/bdev_io).
+All possible management is done using RPC calls with the exception of
+use of split bdevs which have to be configured in .conf file.
+
+Functional tests are executed as scenarios - sets of smaller test steps
+in which results and return codes of RPC calls are validated.
+Some configuration calls may also additionally be validated
+by use of "get" (e.g. get_bdevs) RPC calls, which provide additional
+information for veryfing results.
+In some steps additional write/read operations will be performed on
+PMEM bdevs in order to check IO path correct behavior.
+
+FIO traffic verification tests will serve as integration tests and will
+be executed to config correct behavior of PMEM bdev when working with vhost,
+nvmf_tgt and iscsi_tgt applications.
+
+## Functional tests
+
+### pmem_pool_info
+
+#### pmem_pool_info_tc1
+Negative test for checking pmem pool file.
+Call with missing path argument.
+Steps & expected results:
+- Call pmem_pool_info with missing path argument
+- Check that return code != 0 and error code =
+
+#### pmem_pool_info_tc2
+Negative test for checking pmem pool file.
+Call with non-existant path argument.
+Steps & expected results:
+- Call pmem_pool_info with path argument that points to not existing file.
+- Check that return code != 0 and error code = ENODEV
+
+#### pmem_pool_info_tc3
+Negative test for checking pmem pool file.
+Call with other type of pmem pool file.
+Steps & expected results:
+- Using pmem utility tools create pool of OBJ type instead of BLK
+(if needed utility tools are not available - create random file in filesystem)
+- Call pmem_pool_info and point to file created in previous step.
+- Check that return code != 0 and error code = ENODEV
+
+#### pmem_pool_info_tc4
+Positive test for checking pmem pool file.
+Call with existing pmem pool file.
+Steps & expected results:
+- Call pmem_pool_info with path argument that points to existing file.
+- Check that return code == 0
+
+### create_pmem_pool
+From libpmemblk documentation:
+- PMEM block size has to be bigger than 512 internal blocks; if lower value
+is used then PMEM library will silently round it up to 512 which is defined
+in pmem/libpmemblk.h file as PMEMBLK_MIN_BLK.
+- Total pool size cannot be less than 16MB which is defined i
+pmem/libpmemblk.h file as PMEMBLK_MIN_POOL
+- Total number of segments in PMEP pool file cannot be less than 256
+
+#### create_pmem_pool_tc1
+Negative test case for creating a new pmem.
+Call create_pmem_pool with missing arguments.
+Steps & expected results:
+- call create_pmem_pool without path argument
+- call return code != 0
+- call pmem_pool_info and check that pmem pool file was not created
+- call return code != 0
+- call create_pmem_pool with path but without size and block size arguments
+- call return code != 0
+- call pmem_pool_info and check that pmem pool file was not created
+- call return code != 0
+- call create_pmem_pool with path and size but without block size arguments
+- call return code != 0
+- call pmem_pool_info and check that pmem pool file was not created
+- call return code != 0
+
+#### create_pmem_pool_tc2
+Negative test case for creating a new pmem.
+Call create_pmem_pool with non existing path argument.
+Steps & expected results:
+- call create_pmem_pool with path that does not exist
+- call return code != 0
+- call pmem_pool_info and check that pmem pool file was not created
+- call return code != 0
+
+#### create_pmem_pool_tc3
+Positive test case for creating a new pmem pool on disk space.
+Steps & expected results:
+- call create_pmem_pool with correct path argument,
+blocksize=512 and total size=256MB
+- call return code = 0
+- call pmem_pool_info and check that pmem file was created
+- call return code = 0
+- call delete_pmem_pool on previously created pmem
+- return code = 0 and no error code
+
+#### create_pmem_pool_tc4
+Positive test case for creating a new pmem pool in RAM space.
+# TODO: Research test steps for creating a pool in RAM!!!
+Steps & expected results:
+- call create_pmem_pool with correct path argument,
+blocksize=512 and total size=256MB
+- call return code = 0
+- call pmem_pool_info and check that pmem file was created
+- call return code = 0
+- call delete_pmem_pool on previously created pmem
+- return code = 0 and no error code
+
+#### create_pmem_pool_tc5
+Negative test case for creating two pmems with same path.
+Steps & expected results:
+- call create_pmem_pool with correct path argument,
+blocksize=512 and total size=256MB
+- call return code = 0
+- call pmem_pool_info and check that pmem file was created
+- call return code = 0
+- call create_pmem_pool with the same path argument as before,
+blocksize=4096 and total size=512MB
+- call return code != 0, error code = EEXIST
+- call create_pmem_pool and check that first pmem pool file is still
+available and not modified (block size and total size stay the same)
+- call return code = 0
+- call delete_pmem_pool on first created pmem pool
+- return code =0 and no error code
+
+#### create_pmem_pool_tc6
+Positive test case for creating pmem pool file with various block sizes.
+Steps & expected results:
+- call create_pmem_pool with correct path argument, total size=256MB
+with different block size arguments - 1, 511, 512, 513, 1024, 4096, 128k and 256k
+- call pmem_pool_info on each of created pmem pool and check if it was created;
+For pool files created with block size <512 their block size should be rounded up
+to 512; other pool files should have the same block size as specified in create
+command
+- call return code = 0; block sizes as expected
+- call delete_pmem_pool on all created pool files
+
+#### create_pmem_pool_tc7
+Negative test case for creating pmem pool file with total size of less than 16MB.
+Steps & expected results:
+- call create_pmem_pool with correct path argument, block size=512 and
+total size less than 16MB
+- return code !=0 and error code !=0
+- call pmem_pool_info to verify pmem pool file was not created
+- return code = 0
+
+#### create_pmem_pool_tc8
+Negative test case for creating pmem pool file with less than 256 blocks.
+Steps & expected results:
+- call create_pmem_pool with correct path argument, block size=128k and
+total size=30MB
+- return code !=0 and error code !=0
+- call pmem_pool_info to verify pmem pool file was not created
+- return code = 0
+
+### delete_pmem_pool
+
+#### delete_pmem_pool_tc1
+Negative test case for deleting a pmem.
+Call delete_pmem_pool on non-exisiting pmem.
+Steps & expected results:
+- call delete_pmem_pool on non-existing pmem.
+- return code !=0 and error code = ENOENT
+
+#### delete_pmem_pool_tc2
+Negative test case for deleting a pmem.
+Call delete_pmem_pool on a file of wrong type
+Steps & expected results:
+- Using pmem utility tools create pool of OBJ type instead of BLK
+(if needed utility tools are not available - create random file in filesystem)
+- Call delete_pmem_pool and point to file created in previous step.
+- return code !=0 and error code = ENOTBLK
+
+#### delete_pmem_pool_tc3
+Positive test case for creating and deleting a pemem.
+Steps & expected results:
+- call create_pmem_pool with correct arguments
+- return code = 0 and no error code
+- using pmem_pool_info check that pmem was created
+- return code = 0 and no error code
+- call delete_pmem_pool on previously created pmem
+- return code = 0 and no error code
+- using pmem_pool_info check that pmem no longer exists
+- return code !=0 and error code = ENODEV
+
+#### delete_pmem_pool_tc4
+Negative test case for creating and deleting a pemem.
+Steps & expected results:
+- run scenario from test case 3
+- call delete_pmem_pool on already deleted pmem pool
+- return code !=0 and error code = ENODEV
+
+### construct_pmem_bdev
+
+#### construct_pmem_bdev_tc1
+Negative test for constructing new pmem bdev.
+Call create_pmem_bdev with missing argument.
+Steps & expected results:
+- Call construct_pmem_bdev with missing path argument.
+- Check that return code != 0
+
+#### construct_pmem_bdev_tc2
+Negative test for constructing new pmem bdev.
+Call construct_pmem_bdev with not existing path argument.
+Steps & expected results:
+- call construct_pmem_bdev with incorrect (not existing) path
+- call return code != 0 and error code = ENODEV
+- using get_bdevs check that no pmem bdev was created
+
+#### construct_pmem_bdev_tc3
+Negative test for constructing pmem bdevs with random file instead of pmemblk pool.
+Steps & expected results:
+- using a system tool (like dd) create a random file
+- call construct_pmem_bdev with path pointing to that file
+- return code != 0, error code = ENOTBLK
+
+#### construct_pmem_bdev_tc4
+Negative test for constructing pmem bdevs with pmemobj instead of pmemblk pool.
+Steps & expected results:
+- Using pmem utility tools create pool of OBJ type instead of BLK
+(if needed utility tools are not available - create random file in filesystem)
+- call construct_pmem_bdev with path pointing to that pool
+- return code != 0, error code = ENOTBLK
+
+#### construct_pmem_bdev_tc5
+Positive test for constructing pmem bdev.
+Steps & expected results:
+- call create_pmem_pool with correct arguments
+- return code = 0, no errors
+- call pmem_pool_info and check if pmem files exists
+- return code = 0, no errors
+- call construct_pmem_bdev with with correct arguments to create a pmem bdev
+- return code = 0, no errors
+- using get_bdevs check that pmem bdev was created
+- delete pmem bdev using delete_bdev
+- return code = 0, no error code
+- delete previously created pmem pool
+- return code = 0, no error code
+
+#### construct_pmem_bdev_tc6
+Negative test for constructing pmem bdevs twice on the same pmem.
+Steps & expected results:
+- call create_pmem_pool with correct arguments
+- return code = 0, no errors
+- call pmem_pool_info and check if pmem files exists
+- return code = 0, no errors
+- call construct_pmem_bdev with with correct arguments to create a pmem bdev
+- return code = 0, no errors
+- using get_bdevs check that pmem bdev was created
+- call construct_pmem_bdev again on the same pmem file
+- return code != 0, error code = EEXIST
+- delete pmem bdev using delete_bdev
+- return code = 0, no error code
+- delete previously created pmem pool
+- return code = 0, no error code
+
+### delete_bdev
+
+#### delete_bdev_tc1
+Positive test for deleting pmem bdevs using common delete_bdev call.
+Steps & expected results:
+- construct malloc and aio bdevs (also NVMe if possible)
+- all calls - return code = 0, no errors; bdevs created
+- call create_pmem_pool with correct path argument,
+block size=512, total size=256M
+- return code = 0, no errors
+- call pmem_pool_info and check if pmem file exists
+- return code = 0, no errors
+- call construct_pmem_bdev and create a pmem bdev
+- return code = 0, no errors
+- using get_bdevs check that pmem bdev was created
+- delete pmem bdev using delete_bdev
+- return code = 0, no errors
+- using get_bdevs confirm that pmem bdev was deleted and other bdevs
+were unaffected.
+
+#### delete_bdev_tc2
+Negative test for deleting pmem bdev twice.
+Steps & expected results:
+- call create_pmem_pool with correct path argument,
+block size=512, total size=256M
+- return code = 0, no errors
+- call pmem_pool_info and check if pmem file exists
+- return code = 0, no errors
+- call construct_pmem_bdev and create a pmem bdev
+- return code = 0, no errors
+- using get_bdevs check that pmem bdev was created
+- delete pmem bdev using delete_bdev
+- return code = 0, no errors
+- using get_bdevs confirm that pmem bdev was deleted
+- delete pmem bdev using delete_bdev second time
+- return code != 0, error code = ENODEV
+
+
+## Integration tests
+Description of integration tests which run FIO verification traffic against
+pmem_bdevs used in vhost, iscsi_tgt and nvmf_tgt applications can be found in
+test directories for these components:
+- spdk/test/vhost
+- spdk/test/nvmf
+- spdk/test/iscsi_tgt
diff --git a/src/spdk/test/rpc_client/.gitignore b/src/spdk/test/rpc_client/.gitignore
new file mode 100644
index 00000000..e878ca3a
--- /dev/null
+++ b/src/spdk/test/rpc_client/.gitignore
@@ -0,0 +1 @@
+rpc_client_test
diff --git a/src/spdk/test/rpc_client/Makefile b/src/spdk/test/rpc_client/Makefile
new file mode 100644
index 00000000..50e976a2
--- /dev/null
+++ b/src/spdk/test/rpc_client/Makefile
@@ -0,0 +1,56 @@
+#
+# 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.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk
+
+APP = rpc_client_test
+
+C_SRCS := rpc_client_test.c
+
+SPDK_LIB_LIST = jsonrpc json log util
+
+LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS)
+
+all : $(APP)
+ @:
+
+$(APP) : $(OBJS) $(SPDK_LIB_FILES)
+ $(LINK_C)
+
+clean :
+ $(CLEAN_C) $(APP)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk
diff --git a/src/spdk/test/rpc_client/rpc_client.sh b/src/spdk/test/rpc_client/rpc_client.sh
new file mode 100755
index 00000000..296a6fff
--- /dev/null
+++ b/src/spdk/test/rpc_client/rpc_client.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+
+set -e
+
+source $rootdir/test/common/autotest_common.sh
+
+function rpc_client_test() {
+ if [ $(uname -s) = Linux ]; then
+ local conf=$rootdir/test/bdev/bdev.conf.in
+
+ if [ ! -e $conf ]; then
+ return 1
+ fi
+
+ $rootdir/test/app/bdev_svc/bdev_svc -i 0 -c ${conf} &
+ svc_pid=$!
+ echo "Process bdev_svc pid: $svc_pid"
+ waitforlisten $svc_pid
+ trap "killprocess $svc_pid" SIGINT SIGTERM EXIT
+
+ $rootdir/test/rpc_client/rpc_client_test
+
+ killprocess $svc_pid
+ fi
+
+ return 0
+}
+
+timing_enter rpc_client
+rpc_client_test
+timing_exit rpc_client
+
+trap - SIGINT SIGTERM EXIT
diff --git a/src/spdk/test/rpc_client/rpc_client_test.c b/src/spdk/test/rpc_client/rpc_client_test.c
new file mode 100644
index 00000000..68f84713
--- /dev/null
+++ b/src/spdk/test/rpc_client/rpc_client_test.c
@@ -0,0 +1,118 @@
+/*-
+ * 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/jsonrpc.h"
+
+#define RPC_MAX_METHODS 200
+
+static const char *g_rpcsock_addr = SPDK_DEFAULT_RPC_ADDR;
+static int g_addr_family = AF_UNIX;
+
+#define RPC_MAX_METHODS 200
+
+struct get_jsonrpc_methods_resp {
+ char *method_names[RPC_MAX_METHODS];
+ size_t method_num;
+};
+
+static int
+get_jsonrpc_method_json_parser(void *parser_ctx,
+ const struct spdk_json_val *result)
+{
+ struct get_jsonrpc_methods_resp *resp = parser_ctx;
+
+ return spdk_json_decode_array(result, spdk_json_decode_string, resp->method_names,
+ RPC_MAX_METHODS, &resp->method_num, sizeof(char *));
+}
+
+static int
+spdk_jsonrpc_client_check_rpc_method(struct spdk_jsonrpc_client *client, char *method_name)
+{
+ int rc, i;
+ struct get_jsonrpc_methods_resp resp = {};
+ struct spdk_json_write_ctx *w;
+ struct spdk_jsonrpc_client_request *request;
+
+ request = spdk_jsonrpc_client_create_request();
+ if (request == NULL) {
+ return -ENOMEM;
+ }
+
+ w = spdk_jsonrpc_begin_request(request, 1, "get_rpc_methods");
+ spdk_jsonrpc_end_request(request, w);
+ spdk_jsonrpc_client_send_request(client, request);
+ spdk_jsonrpc_client_free_request(request);
+
+ rc = spdk_jsonrpc_client_recv_response(client, get_jsonrpc_method_json_parser, &resp);
+
+ if (rc) {
+ goto out;
+ }
+
+ for (i = 0; i < (int)resp.method_num; i++) {
+ if (strcmp(method_name, resp.method_names[i]) == 0) {
+ rc = 0;
+ goto out;
+ }
+ }
+
+ rc = -1;
+
+out:
+ for (i = 0; i < (int)resp.method_num; i++) {
+ SPDK_NOTICELOG("%s\n", resp.method_names[i]);
+ free(resp.method_names[i]);
+ }
+
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ struct spdk_jsonrpc_client *client;
+ int rc;
+ char *method_name = "get_rpc_methods";
+
+ client = spdk_jsonrpc_client_connect(g_rpcsock_addr, g_addr_family);
+ if (!client) {
+ return EXIT_FAILURE;
+ }
+
+ rc = spdk_jsonrpc_client_check_rpc_method(client, method_name);
+
+ spdk_jsonrpc_client_close(client);
+
+ return rc ? EXIT_FAILURE : 0;
+}
diff --git a/src/spdk/test/spdk_cunit.h b/src/spdk/test/spdk_cunit.h
new file mode 100644
index 00000000..6696bff3
--- /dev/null
+++ b/src/spdk/test/spdk_cunit.h
@@ -0,0 +1,56 @@
+/*-
+ * 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 SPDK_CUNIT_H
+#define SPDK_CUNIT_H
+
+#include "spdk/stdinc.h"
+
+#include <CUnit/Basic.h>
+
+/*
+ * CU_ASSERT_FATAL calls a function that does a longjmp() internally, but only for fatal asserts,
+ * so the function itself is not marked as noreturn. Add an abort() after the assert to help
+ * static analyzers figure out that it really doesn't return.
+ * The abort() will never actually execute.
+ */
+#define SPDK_CU_ASSERT_FATAL(cond) \
+ do { \
+ int result_ = !!(cond); \
+ CU_ASSERT_FATAL(result_); \
+ if (!result_) { \
+ abort(); \
+ } \
+ } while (0)
+
+#endif /* SPDK_CUNIT_H */
diff --git a/src/spdk/test/spdkcli/common.sh b/src/spdk/test/spdkcli/common.sh
new file mode 100644
index 00000000..80ea6ab1
--- /dev/null
+++ b/src/spdk/test/spdkcli/common.sh
@@ -0,0 +1,26 @@
+set -xe
+
+testdir=$(readlink -f $(dirname $0))
+SPDKCLI_BUILD_DIR=$(readlink -f $testdir/../..)
+spdkcli_job="$SPDKCLI_BUILD_DIR/test/spdkcli/spdkcli_job.py"
+. $SPDKCLI_BUILD_DIR/test/common/autotest_common.sh
+
+function on_error_exit() {
+ set +e
+ killprocess $spdk_tgt_pid
+ rm -f $testdir/${MATCH_FILE} $testdir/match_files/spdkcli_details_vhost.test /tmp/sample_aio /tmp/sample_pmem
+ print_backtrace
+ exit 1
+}
+
+function run_spdk_tgt() {
+ $SPDKCLI_BUILD_DIR/app/spdk_tgt/spdk_tgt -m 0x3 -p 0 -s 4096 &
+ spdk_tgt_pid=$!
+ waitforlisten $spdk_tgt_pid
+}
+
+function check_match() {
+ $SPDKCLI_BUILD_DIR/scripts/spdkcli.py ll $SPDKCLI_BRANCH > $testdir/match_files/${MATCH_FILE}
+ $SPDKCLI_BUILD_DIR/test/app/match/match -v $testdir/match_files/${MATCH_FILE}.match
+ rm -f $testdir/match_files/${MATCH_FILE}
+}
diff --git a/src/spdk/test/spdkcli/iscsi.sh b/src/spdk/test/spdkcli/iscsi.sh
new file mode 100755
index 00000000..e33aba85
--- /dev/null
+++ b/src/spdk/test/spdkcli/iscsi.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+set -xe
+
+MATCH_FILE="spdkcli_iscsi.test"
+SPDKCLI_BRANCH="/iscsi"
+testdir=$(readlink -f $(dirname $0))
+. $testdir/common.sh
+. $testdir/../iscsi_tgt/common.sh
+
+timing_enter spdkcli_iscsi
+trap 'on_error_exit;' ERR
+
+timing_enter run_spdk_tgt
+run_spdk_tgt
+timing_exit run_spdk_tgt
+
+timing_enter spdkcli_create_iscsi_config
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc0" "Malloc0" True
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc1" "Malloc1" True
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc2" "Malloc2" True
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc3" "Malloc3" True
+$spdkcli_job "/iscsi/portal_groups create 1 \"127.0.0.1:3261 127.0.0.1:3263@0x1\"" "host=127.0.0.1, port=3261" True
+$spdkcli_job "/iscsi/portal_groups create 2 127.0.0.1:3262" "host=127.0.0.1, port=3262" True
+$spdkcli_job "/iscsi/initiator_groups create 2 ANY 10.0.2.15/32" "hostname=ANY, netmask=10.0.2.15/32" True
+$spdkcli_job "/iscsi/initiator_groups create 3 ANZ 10.0.2.15/32" "hostname=ANZ, netmask=10.0.2.15/32" True
+$spdkcli_job "/iscsi/initiator_groups add_initiator 2 ANW 10.0.2.16/32" "hostname=ANW, netmask=10.0.2.16" True
+$spdkcli_job "/iscsi/target_nodes create Target0 Target0_alias \"Malloc0:0 Malloc1:1\" 1:2 64 g=1" "Target0" True
+$spdkcli_job "/iscsi/target_nodes create Target1 Target1_alias Malloc2:0 1:2 64 g=1" "Target1" True
+$spdkcli_job "/iscsi/target_nodes/iqn.2016-06.io.spdk:Target0 add_pg_ig_maps \"1:3 2:2\"" "portal_group1 - initiator_group3" True
+$spdkcli_job "/iscsi/target_nodes add_lun iqn.2016-06.io.spdk:Target1 Malloc3 2" "Malloc3" True
+$spdkcli_job "/iscsi/auth_groups create 1 \"user:test secret:test muser:mutual_test msecret:mutual_test \
+user:test3 secret:test3 muser:mutual_test3 msecret:mutual_test3\"" "user=test3" True
+$spdkcli_job "/iscsi/auth_groups add_secret 1 user=test2 secret=test2 muser=mutual_test2 msecret=mutual_test2" "user=test2" True
+$spdkcli_job "/iscsi/target_nodes/iqn.2016-06.io.spdk:Target0 set_auth g=1 d=true" "disable_chap: True" True
+$spdkcli_job "/iscsi/global_params set_auth g=1 d=true r=false" "disable_chap: True" True
+$spdkcli_job "/iscsi ls" "Malloc" True
+timing_exit spdkcli_create_iscsi_config
+
+timing_enter spdkcli_check_match
+check_match
+timing_exit spdkcli_check_match
+
+timing_enter spdkcli_clear_iscsi_config
+$spdkcli_job "/iscsi/auth_groups delete_secret 1 test2" "user=test2"
+$spdkcli_job "/iscsi/auth_groups delete 1" "user=test"
+$spdkcli_job "/iscsi/target_nodes/iqn.2016-06.io.spdk:Target0 delete_pg_ig_maps \"1:3 2:2\"" "portal_group1 - initiator_group3"
+$spdkcli_job "/iscsi/target_nodes delete iqn.2016-06.io.spdk:Target1" "Target1"
+$spdkcli_job "/iscsi/target_nodes delete iqn.2016-06.io.spdk:Target0" "Target0"
+$spdkcli_job "/iscsi/initiator_groups delete_initiator 2 ANW 10.0.2.16/32" "ANW"
+$spdkcli_job "/iscsi/initiator_groups delete 3" "ANYZ"
+$spdkcli_job "/iscsi/portal_groups delete 1" "127.0.0.1:3261"
+$spdkcli_job "/bdevs/malloc delete Malloc3" "Malloc3"
+$spdkcli_job "/bdevs/malloc delete Malloc2" "Malloc2"
+$spdkcli_job "/bdevs/malloc delete Malloc1" "Malloc1"
+$spdkcli_job "/bdevs/malloc delete Malloc0" "Malloc0"
+timing_exit spdkcli_clear_iscsi_config
+
+killprocess $spdk_tgt_pid
+timing_exit spdkcli_iscsi
+report_test_completion spdk_cli
diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_details_vhost.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_details_vhost.test.match
new file mode 100644
index 00000000..8cbd9e80
--- /dev/null
+++ b/src/spdk/test/spdkcli/match_files/spdkcli_details_vhost.test.match
@@ -0,0 +1,28 @@
+{
+ "aliases": [],
+ "assigned_rate_limits": {
+ "rw_ios_per_sec": $(N),
+ "rw_mbytes_per_sec": $(N)
+ },
+ "block_size": $(N),
+ "claimed": false,
+ "driver_specific": {
+ "split": {
+ "base_bdev": "Nvme0n1",
+ "offset_blocks": $(N)
+ }
+ },
+ "name": "Nvme0n1p0",
+ "num_blocks": $(N),
+ "product_name": "Split Disk",
+ "supported_io_types": {
+ "flush": $(S),
+ "nvme_admin": $(S),
+ "nvme_io": $(S),
+ "read": $(S),
+ "reset": $(S),
+ "unmap": $(S),
+ "write": $(S),
+ "write_zeroes": $(S)
+ }
+}
diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_iscsi.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_iscsi.test.match
new file mode 100644
index 00000000..e48d8d98
--- /dev/null
+++ b/src/spdk/test/spdkcli/match_files/spdkcli_iscsi.test.match
@@ -0,0 +1,53 @@
+o- iscsi ..................................................................................................................... [...]
+ o- auth_groups ....................................................................................................... [Groups: 1]
+ | o- group1 ......................................................................................................... [Secrets: 2]
+ | o- user=test2, secret=test2, muser=mutual_test2, msecret=mutual_test2 .................................................. [...]
+ | o- user=test3, secret=test3, muser=mutual_test3, msecret=mutual_test3 .................................................. [...]
+ o- global_params ........................................................................................................... [...]
+ | o- allow_duplicated_isid: False .......................................................................................... [...]
+ | o- chap_group: 1 ......................................................................................................... [...]
+ | o- default_time2retain: 20 ............................................................................................... [...]
+ | o- default_time2wait: 2 .................................................................................................. [...]
+ | o- disable_chap: True .................................................................................................... [...]
+ | o- error_recovery_level: 0 ............................................................................................... [...]
+ | o- first_burst_length: 8192 .............................................................................................. [...]
+ | o- immediate_data: True .................................................................................................. [...]
+ | o- max_connections_per_session: 2 ........................................................................................ [...]
+ | o- max_queue_depth: 64 ................................................................................................... [...]
+ | o- max_sessions: 128 ..................................................................................................... [...]
+ | o- min_connections_per_core: 4 ........................................................................................... [...]
+ | o- mutual_chap: False .................................................................................................... [...]
+ | o- node_base: iqn.2016-06.io.spdk ........................................................................................ [...]
+ | o- nop_in_interval: 30 ................................................................................................... [...]
+ | o- nop_timeout: 60 ....................................................................................................... [...]
+ | o- require_chap: False ................................................................................................... [...]
+ o- initiator_groups ........................................................................................ [Initiator groups: 2]
+ | o- initiator_group2 ............................................................................................ [Initiators: 2]
+ | | o- hostname=ANW, netmask=$(N).$(N).$(N).$(N)/32 $(S) [...]
+ | | o- hostname=ANY, netmask=$(N).$(N).$(N).$(N)/32 $(S) [...]
+ | o- initiator_group3 ............................................................................................ [Initiators: 1]
+ | o- hostname=ANZ, netmask=$(N).$(N).$(N).$(N)/32 $(S) [...]
+ o- iscsi_connections ............................................................................................ [Connections: 0]
+ o- portal_groups .............................................................................................. [Portal groups: 2]
+ | o- portal_group1 .................................................................................................. [Portals: 2]
+ | | o- host=127.0.0.1, port=3261, cpumask=0x3 .............................................................................. [...]
+ | | o- host=127.0.0.1, port=3263, cpumask=0x1 .............................................................................. [...]
+ | o- portal_group2 .................................................................................................. [Portals: 1]
+ | o- host=127.0.0.1, port=3262, cpumask=0x3 .............................................................................. [...]
+ o- target_nodes ................................................................................................ [Target nodes: 2]
+ o- iqn.2016-06.io.spdk:Target0 ......................................................................... [Id: 0, QueueDepth: 64]
+ | o- auths ........................................ [disable_chap: True, require_chap: False, mutual_chap: False, chap_group: 1]
+ | o- luns ............................................................................................................ [Luns: 2]
+ | | o- lun 0 ......................................................................................................... [Malloc0]
+ | | o- lun 1 ......................................................................................................... [Malloc1]
+ | o- pg_ig_maps ................................................................................................ [Pg_ig_maps: 3]
+ | o- portal_group1 - initiator_group2 .................................................................................. [...]
+ | o- portal_group1 - initiator_group3 .................................................................................. [...]
+ | o- portal_group2 - initiator_group2 .................................................................................. [...]
+ o- iqn.2016-06.io.spdk:Target1 ......................................................................... [Id: 1, QueueDepth: 64]
+ o- auths ....................................... [disable_chap: False, require_chap: False, mutual_chap: False, chap_group: 1]
+ o- luns ............................................................................................................ [Luns: 2]
+ | o- lun 0 ......................................................................................................... [Malloc2]
+ | o- lun 2 ......................................................................................................... [Malloc3]
+ o- pg_ig_maps ................................................................................................ [Pg_ig_maps: 1]
+ o- portal_group1 - initiator_group2 .................................................................................. [...]
diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_nvmf.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_nvmf.test.match
new file mode 100644
index 00000000..95b36f64
--- /dev/null
+++ b/src/spdk/test/spdkcli/match_files/spdkcli_nvmf.test.match
@@ -0,0 +1,32 @@
+o- nvmf ...................................................................................................................... [...]
+ o- subsystem ..................................................................................................... [Subsystems: 4]
+ o- nqn.2014-08.org.nvmexpress.discovery ......................................................... [st=Discovery, Allow any host]
+ | o- hosts .......................................................................................................... [Hosts: 0]
+ | o- listen_addresses ........................................................................................... [Addresses: 0]
+ o- nqn.2014-08.org.spdk:cnode1 ...................................................... [sn=$(S), st=NVMe, Allow any host]
+ | o- hosts .......................................................................................................... [Hosts: 1]
+ | | o- nqn.2014-08.org.spdk:cnode2 ....................................................................................... [...]
+ | o- listen_addresses ........................................................................................... [Addresses: 3]
+ | | o- $(N).$(N).$(N).$(N):4260 $(S) [RDMA]
+ | | o- $(N).$(N).$(N).$(N):4261 $(S) [RDMA]
+ | | o- $(N).$(N).$(N).$(N):4262 $(S) [RDMA]
+ | o- namespaces ................................................................................................ [Namespaces: 4]
+ | o- Malloc3 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc3, 1]
+ | o- Malloc4 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc4, 2]
+ | o- Malloc5 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc5, 3]
+ | o- Malloc6 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc6, 4]
+ o- nqn.2014-08.org.spdk:cnode2 ...................................................... [sn=$(S), st=NVMe, Allow any host]
+ | o- hosts .......................................................................................................... [Hosts: 0]
+ | o- listen_addresses ........................................................................................... [Addresses: 1]
+ | | o- $(N).$(N).$(N).$(N):4260 $(S) [RDMA]
+ | o- namespaces ................................................................................................ [Namespaces: 1]
+ | o- Malloc2 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc2, 1]
+ o- nqn.2014-08.org.spdk:cnode3 ...................................................... [sn=$(S), st=NVMe, Allow any host]
+ o- hosts .......................................................................................................... [Hosts: 2]
+ | o- nqn.2014-08.org.spdk:cnode1 ....................................................................................... [...]
+ | o- nqn.2014-08.org.spdk:cnode2 ....................................................................................... [...]
+ o- listen_addresses ........................................................................................... [Addresses: 2]
+ | o- $(N).$(N).$(N).$(N):4260 $(S) [RDMA]
+ | o- $(N).$(N).$(N).$(N):4261 $(S) [RDMA]
+ o- namespaces ................................................................................................ [Namespaces: 1]
+ o- Malloc1 .............................................................. [$(X)-$(X)-$(X)-$(X)-$(X), Malloc1, 1]
diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_pmem.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_pmem.test.match
new file mode 100644
index 00000000..0ee659b5
--- /dev/null
+++ b/src/spdk/test/spdkcli/match_files/spdkcli_pmem.test.match
@@ -0,0 +1,2 @@
+o- pmemblk .............................................................................................................. [Bdevs: 1]
+ o- pmem_bdev ........................................................................................... [Size=31.6M, Not claimed]
diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_rbd.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_rbd.test.match
new file mode 100644
index 00000000..ea748e90
--- /dev/null
+++ b/src/spdk/test/spdkcli/match_files/spdkcli_rbd.test.match
@@ -0,0 +1,2 @@
+o- rbd .................................................................................................................. [Bdevs: 1]
+ o- Ceph0 ............................................................................................. [Size=1000.0M, Not claimed]
diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_vhost.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_vhost.test.match
new file mode 100644
index 00000000..a1ed8dc7
--- /dev/null
+++ b/src/spdk/test/spdkcli/match_files/spdkcli_vhost.test.match
@@ -0,0 +1,75 @@
+o- / ......................................................................................................................... [...]
+ o- bdevs ................................................................................................................... [...]
+ | o- aio .............................................................................................................. [Bdevs: 1]
+ | | o- sample ........................................................................................... [Size=$(FP)M, Not claimed]
+ | o- error ............................................................................................................ [Bdevs: 1]
+ | | o- EE_Malloc1 ...................................................................................... [Size=$(FP)M, Not claimed]
+ | o- iscsi ............................................................................................................ [Bdevs: 0]
+ | o- logical_volume ................................................................................................... [Bdevs: 1]
+ | | o- $(X)-$(X)-$(X)-$(X)-$(X) .................................................. [lvs/lvol, Size=$(FP)M, Not claimed]
+ | o- malloc ........................................................................................................... [Bdevs: 4]
+ | | o- Malloc0 ............................................................................................. [Size=$(FP)M, Claimed]
+ | | o- Malloc1 ............................................................................................. [Size=$(FP)M, Claimed]
+ | | o- Malloc2 ......................................................................................... [Size=$(FP)M, Not claimed]
+ | | o- Malloc3 ......................................................................................... [Size=$(FP)M, Not claimed]
+ | o- null ............................................................................................................. [Bdevs: 1]
+ | | o- null_bdev ....................................................................................... [Size=$(FP)M, Not claimed]
+ | o- nvme ............................................................................................................. [Bdevs: 1]
+ | | o- Nvme0n1 $(S) [Size=$(FP)G, Claimed]
+ | o- pmemblk .......................................................................................................... [Bdevs: 0]
+ | o- rbd .............................................................................................................. [Bdevs: 0]
+ | o- split_disk ....................................................................................................... [Bdevs: 4]
+ | | o- Nvme0n1p0 $(S) [Size=$(FP)G, Not claimed]
+ | | o- Nvme0n1p1 $(S) [Size=$(FP)G, Not claimed]
+ | | o- Nvme0n1p2 $(S) [Size=$(FP)G, Not claimed]
+ | | o- Nvme0n1p3 $(S) [Size=$(FP)G, Not claimed]
+ | o- virtioblk_disk ................................................................................................... [Bdevs: 0]
+ | o- virtioscsi_disk .................................................................................................. [Bdevs: 0]
+ o- iscsi ................................................................................................................... [...]
+ | o- auth_groups ..................................................................................................... [Groups: 0]
+ | o- global_params ......................................................................................................... [...]
+ | | o- allow_duplicated_isid: False ........................................................................................ [...]
+ | | o- chap_group: 0 ....................................................................................................... [...]
+ | | o- default_time2retain: 20 ............................................................................................. [...]
+ | | o- default_time2wait: 2 ................................................................................................ [...]
+ | | o- disable_chap: False ................................................................................................. [...]
+ | | o- error_recovery_level: 0 ............................................................................................. [...]
+ | | o- first_burst_length: 8192 ............................................................................................ [...]
+ | | o- immediate_data: True ................................................................................................ [...]
+ | | o- max_connections_per_session: 2 ...................................................................................... [...]
+ | | o- max_queue_depth: 64 ................................................................................................. [...]
+ | | o- max_sessions: 128 ................................................................................................... [...]
+ | | o- min_connections_per_core: 4 ......................................................................................... [...]
+ | | o- mutual_chap: False .................................................................................................. [...]
+ | | o- node_base: iqn.2016-06.io.spdk ...................................................................................... [...]
+ | | o- nop_in_interval: 30 ................................................................................................. [...]
+ | | o- nop_timeout: 60 ..................................................................................................... [...]
+ | | o- require_chap: False ................................................................................................. [...]
+ | o- initiator_groups ...................................................................................... [Initiator groups: 0]
+ | o- iscsi_connections .......................................................................................... [Connections: 0]
+ | o- portal_groups ............................................................................................ [Portal groups: 0]
+ | o- target_nodes .............................................................................................. [Target nodes: 0]
+ o- lvol_stores .................................................................................................. [Lvol stores: 1]
+ | o- lvs ................................................................................................ [Size=$(FP)M, Free=$(FP)M]
+ o- nvmf .................................................................................................................... [...]
+ | o- subsystem ................................................................................................... [Subsystems: 1]
+ | o- nqn.2014-08.org.nvmexpress.discovery ....................................................... [st=Discovery, Allow any host]
+ | o- hosts ........................................................................................................ [Hosts: 0]
+ | o- listen_addresses ......................................................................................... [Addresses: 0]
+ o- vhost ................................................................................................................... [...]
+ o- block ................................................................................................................. [...]
+ | o- vhost_blk1 $(S) [$(S)]
+ | | o- Nvme0n1p0 ......................................................................................................... [...]
+ | o- vhost_blk2 $(S) [$(S), Readonly]
+ | o- Nvme0n1p1 ......................................................................................................... [...]
+ o- scsi .................................................................................................................. [...]
+ o- vhost_scsi1 $(S) [$(S)]
+ | o- Target_0 .......................................................................................... [LUNs: 1,TargetID: 0]
+ | o- Malloc2 ......................................................................................................... [...]
+ o- vhost_scsi2 $(S) [$(S)]
+ o- Target_0 .......................................................................................... [LUNs: 1,TargetID: 0]
+ | o- Malloc3 ......................................................................................................... [...]
+ o- Target_1 .......................................................................................... [LUNs: 1,TargetID: 1]
+ | o- Nvme0n1p2 ....................................................................................................... [...]
+ o- Target_2 .......................................................................................... [LUNs: 1,TargetID: 2]
+ o- Nvme0n1p3 ....................................................................................................... [...]
diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_virtio_pci.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_virtio_pci.test.match
new file mode 100644
index 00000000..0eca06e1
--- /dev/null
+++ b/src/spdk/test/spdkcli/match_files/spdkcli_virtio_pci.test.match
@@ -0,0 +1,18 @@
+o- bdevs ..................................................................................................................... [...]
+ o- aio ................................................................................................................ [Bdevs: 0]
+ o- error .............................................................................................................. [Bdevs: 0]
+ o- iscsi .............................................................................................................. [Bdevs: 0]
+ o- logical_volume ..................................................................................................... [Bdevs: 0]
+ o- malloc ............................................................................................................. [Bdevs: 2]
+ | o- Malloc0 ........................................................................................... [Size=32.0M, Not claimed]
+ | o- Malloc1 ........................................................................................... [Size=32.0M, Not claimed]
+ o- null ............................................................................................................... [Bdevs: 0]
+ o- nvme ............................................................................................................... [Bdevs: 0]
+ o- pmemblk ............................................................................................................ [Bdevs: 0]
+ o- rbd ................................................................................................................ [Bdevs: 0]
+ o- split_disk ......................................................................................................... [Bdevs: 0]
+ o- virtioblk_disk ..................................................................................................... [Bdevs: 1]
+ | o- virtioblk_pci $(S) [Size=$(FP)M, Not claimed]
+ o- virtioscsi_disk .................................................................................................... [Bdevs: 1]
+ o- virtioscsi_pci ............................................................................................... [$(S)]
+ o- virtioscsi_pcit0 $(S) [Size=$(FP)M, Not claimed]
diff --git a/src/spdk/test/spdkcli/match_files/spdkcli_virtio_user.test.match b/src/spdk/test/spdkcli/match_files/spdkcli_virtio_user.test.match
new file mode 100644
index 00000000..157938e7
--- /dev/null
+++ b/src/spdk/test/spdkcli/match_files/spdkcli_virtio_user.test.match
@@ -0,0 +1,8 @@
+o- vhost ..................................................................................................................... [...]
+ o- block ................................................................................................................... [...]
+ | o- sample_block $(S) [$(S)]
+ | o- Malloc1 ............................................................................................................. [...]
+ o- scsi .................................................................................................................... [...]
+ o- sample_scsi $(S) [$(S)]
+ o- Target_0 ............................................................................................ [LUNs: 1,TargetID: 0]
+ o- Malloc0 ........................................................................................................... [...]
diff --git a/src/spdk/test/spdkcli/nvmf.sh b/src/spdk/test/spdkcli/nvmf.sh
new file mode 100755
index 00000000..cf592a4e
--- /dev/null
+++ b/src/spdk/test/spdkcli/nvmf.sh
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+set -xe
+
+MATCH_FILE="spdkcli_nvmf.test"
+SPDKCLI_BRANCH="/nvmf"
+testdir=$(readlink -f $(dirname $0))
+. $testdir/common.sh
+. $testdir/../nvmf/common.sh
+
+timing_enter spdkcli_nvmf
+trap 'on_error_exit; revert_soft_roce' ERR
+rdma_device_init
+
+timing_enter run_spdk_tgt
+run_spdk_tgt
+timing_exit run_spdk_tgt
+
+RDMA_IP_LIST=$(get_available_rdma_ips)
+NVMF_TARGET_IP=$(echo "$RDMA_IP_LIST" | head -n 1)
+
+timing_enter spdkcli_create_nvmf_config
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc1" "Malloc1" True
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc2" "Malloc2" True
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc3" "Malloc3" True
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc4" "Malloc4" True
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc5" "Malloc5" True
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc6" "Malloc6" True
+$spdkcli_job "/nvmf/subsystem create nqn.2014-08.org.spdk:cnode1 N37SXV509SRW\
+ max_namespaces=4 allow_any_host=True" "nqn.2014-08.org.spdk:cnode1" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces create Malloc3 1" "Malloc3" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces create Malloc4 2" "Malloc4" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/listen_addresses create \
+ RDMA $NVMF_TARGET_IP 4260 IPv4" "$NVMF_TARGET_IP:4260" True
+$spdkcli_job "/nvmf/subsystem create nqn.2014-08.org.spdk:cnode2 N37SXV509SRD\
+ max_namespaces=2 allow_any_host=True" "nqn.2014-08.org.spdk:cnode2" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode2/namespaces create Malloc2" "Malloc2" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode2/listen_addresses create \
+ RDMA $NVMF_TARGET_IP 4260 IPv4" "$NVMF_TARGET_IP:4260" True
+$spdkcli_job "/nvmf/subsystem create nqn.2014-08.org.spdk:cnode3 N37SXV509SRR\
+ max_namespaces=2 allow_any_host=True" "nqn.2014-08.org.spdk:cnode2" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode3/namespaces create Malloc1" "Malloc1" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode3/listen_addresses create \
+ RDMA $NVMF_TARGET_IP 4260 IPv4" "$NVMF_TARGET_IP:4260" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode3/listen_addresses create \
+ RDMA $NVMF_TARGET_IP 4261 IPv4" "$NVMF_TARGET_IP:4261" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode3/hosts create \
+ nqn.2014-08.org.spdk:cnode1" "nqn.2014-08.org.spdk:cnode1" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode3/hosts create \
+ nqn.2014-08.org.spdk:cnode2" "nqn.2014-08.org.spdk:cnode2" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1 allow_any_host True" "Allow any host"
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1 allow_any_host False" "Allow any host" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/listen_addresses create RDMA $NVMF_TARGET_IP 4261 IPv4" "$NVMF_TARGET_IP:4261" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/listen_addresses create RDMA $NVMF_TARGET_IP 4262 IPv4" "$NVMF_TARGET_IP:4262" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/hosts create nqn.2014-08.org.spdk:cnode2" "nqn.2014-08.org.spdk:cnode2" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces create Malloc5" "Malloc5" True
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces create Malloc6" "Malloc6" True
+timing_exit spdkcli_create_nvmf_config
+
+timing_enter spdkcli_check_match
+check_match
+timing_exit spdkcli_check_match
+
+timing_enter spdkcli_clear_nvmf_config
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces delete nsid=1" "Malloc3"
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/namespaces delete nsid=2" "Malloc4"
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/hosts delete nqn.2014-08.org.spdk:cnode2" "nqn.2014-08.org.spdk:cnode2"
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/listen_addresses delete RDMA $NVMF_TARGET_IP 4262" "$NVMF_TARGET_IP:4262"
+$spdkcli_job "/nvmf/subsystem/nqn.2014-08.org.spdk:cnode1/listen_addresses delete RDMA $NVMF_TARGET_IP 4261" "$NVMF_TARGET_IP:4261"
+$spdkcli_job "/nvmf/subsystem delete nqn.2014-08.org.spdk:cnode3" "nqn.2014-08.org.spdk:cnode3"
+$spdkcli_job "/nvmf/subsystem delete nqn.2014-08.org.spdk:cnode2" "nqn.2014-08.org.spdk:cnode2"
+$spdkcli_job "/nvmf/subsystem delete nqn.2014-08.org.spdk:cnode1" "nqn.2014-08.org.spdk:cnode1"
+$spdkcli_job "/bdevs/malloc delete Malloc6" "Malloc6"
+$spdkcli_job "/bdevs/malloc delete Malloc5" "Malloc5"
+$spdkcli_job "/bdevs/malloc delete Malloc4" "Malloc4"
+$spdkcli_job "/bdevs/malloc delete Malloc3" "Malloc3"
+$spdkcli_job "/bdevs/malloc delete Malloc2" "Malloc2"
+$spdkcli_job "/bdevs/malloc delete Malloc1" "Malloc1"
+timing_exit spdkcli_clear_nvmf_config
+
+killprocess $spdk_tgt_pid
+#revert_soft_roce
+timing_exit spdkcli_nvmf
+report_test_completion spdk_cli_nvmf
diff --git a/src/spdk/test/spdkcli/pmem.sh b/src/spdk/test/spdkcli/pmem.sh
new file mode 100755
index 00000000..bdac1873
--- /dev/null
+++ b/src/spdk/test/spdkcli/pmem.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+set -xe
+
+MATCH_FILE="spdkcli_pmem.test"
+SPDKCLI_BRANCH="/bdevs/pmemblk"
+testdir=$(readlink -f $(dirname $0))
+. $testdir/common.sh
+
+timing_enter spdkcli_pmem
+trap 'on_error_exit;' ERR
+
+timing_enter run_spdk_tgt
+run_spdk_tgt
+timing_exit run_spdk_tgt
+
+timing_enter spdkcli_create_pmem_config
+$spdkcli_job "/bdevs/pmemblk create_pmem_pool /tmp/sample_pmem 32 512" "" True
+$spdkcli_job "/bdevs/pmemblk create /tmp/sample_pmem pmem_bdev" "pmem_bdev" True
+timing_exit spdkcli_create_pmem_config
+
+timing_enter spdkcli_check_match
+check_match
+timing_exit spdkcli_check_match
+
+timing_enter spdkcli_clear_pmem_config
+$spdkcli_job "/bdevs/pmemblk delete pmem_bdev" "pmem_bdev"
+$spdkcli_job "/bdevs/pmemblk delete_pmem_pool /tmp/sample_pmem" ""
+rm -f /tmp/sample_pmem
+timing_exit spdkcli_clear_pmem_config
+
+killprocess $spdk_tgt_pid
+timing_exit spdkcli_pmem
+report_test_completion spdk_cli
diff --git a/src/spdk/test/spdkcli/rbd.sh b/src/spdk/test/spdkcli/rbd.sh
new file mode 100755
index 00000000..352d0645
--- /dev/null
+++ b/src/spdk/test/spdkcli/rbd.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+set -xe
+
+MATCH_FILE="spdkcli_rbd.test"
+SPDKCLI_BRANCH="/bdevs/rbd"
+testdir=$(readlink -f $(dirname $0))
+. $testdir/common.sh
+
+timing_enter spdk_cli_rbd
+trap 'on_error_exit' ERR
+timing_enter run_spdk_tgt
+run_spdk_tgt
+timing_exit run_spdk_tgt
+
+timing_enter spdkcli_create_rbd_config
+trap 'rbd_cleanup; on_error_exit' ERR
+rootdir=$(readlink -f $SPDKCLI_BUILD_DIR)
+rbd_setup 127.0.0.1
+$spdkcli_job "/bdevs/rbd create rbd foo 512" "Ceph0" True
+timing_exit spdkcli_create_rbd_config
+
+timing_enter spdkcli_check_match
+check_match
+timing_exit spdkcli_check_match
+
+timing_enter spdkcli_clear_rbd_config
+$spdkcli_job "/bdevs/rbd delete Ceph0" "Ceph0"
+rbd_cleanup
+timing_exit spdkcli_clear_rbd_config
+
+killprocess $spdk_tgt_pid
+
+timing_exit spdk_cli_rbd
+report_test_completion spdk_cli_rbd
diff --git a/src/spdk/test/spdkcli/spdkcli_job.py b/src/spdk/test/spdkcli/spdkcli_job.py
new file mode 100755
index 00000000..a2677f6a
--- /dev/null
+++ b/src/spdk/test/spdkcli/spdkcli_job.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+import pexpect
+import os
+import sys
+
+
+def execute_command(cmd, element=None, element_exists=False):
+ child.sendline(cmd)
+ child.expect("/>")
+ if "error response" in child.before.decode():
+ print("Error in cmd: %s" % cmd)
+ exit(1)
+ ls_tree = cmd.split(" ")[0]
+ if ls_tree and element:
+ child.sendline("ls %s" % ls_tree)
+ child.expect("/>")
+ print("child: %s" % child.before.decode())
+ if element_exists:
+ if element not in child.before.decode():
+ print("Element %s not in list" % element)
+ exit(1)
+ else:
+ if element in child.before.decode():
+ print("Element %s is in list" % element)
+ exit(1)
+
+
+if __name__ == "__main__":
+ socket = "/var/tmp/spdk.sock"
+ if len(sys.argv) == 5:
+ socket = sys.argv[4]
+ testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
+ child = pexpect.spawn(os.path.join(testdir, "../../scripts/spdkcli.py") + " -s %s" % socket)
+ child.expect(">")
+ child.sendline("cd /")
+ child.expect("/>")
+
+ execute_command(*sys.argv[1:4])
diff --git a/src/spdk/test/spdkcli/vhost.sh b/src/spdk/test/spdkcli/vhost.sh
new file mode 100755
index 00000000..c479857e
--- /dev/null
+++ b/src/spdk/test/spdkcli/vhost.sh
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+set -xe
+
+MATCH_FILE="spdkcli_vhost.test"
+SPDKCLI_BRANCH="/"
+testdir=$(readlink -f $(dirname $0))
+. $testdir/../json_config/common.sh
+. $testdir/common.sh
+
+timing_enter spdk_cli_vhost
+trap 'on_error_exit' ERR
+timing_enter run_spdk_tgt
+run_spdk_tgt
+timing_exit run_spdk_tgt
+
+timing_enter spdkcli_create_bdevs_config
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc0" "Malloc0" True
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc1" "Malloc1" True
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc2" "Malloc2" True
+$spdkcli_job "/bdevs/malloc create 32 4096 Malloc3" "Malloc3" True
+$spdkcli_job "/bdevs/error create Malloc1" "EE_Malloc1" True
+$spdkcli_job "/bdevs/null create null_bdev 32 512" "null_bdev" True
+dd if=/dev/zero of=/tmp/sample_aio bs=2048 count=5000
+$spdkcli_job "/bdevs/aio create sample /tmp/sample_aio 512" "sample" True
+trtype=$($SPDKCLI_BUILD_DIR/scripts/gen_nvme.sh --json | jq -r '.config[].params | select(.name=="Nvme0").trtype')
+traddr=$($SPDKCLI_BUILD_DIR/scripts/gen_nvme.sh --json | jq -r '.config[].params | select(.name=="Nvme0").traddr')
+$spdkcli_job "/bdevs/nvme create Nvme0 $trtype $traddr" "Nvme0" True
+$spdkcli_job "/bdevs/split_disk split_bdev Nvme0n1 4" "Nvme0n1p0" True
+timing_exit spdkcli_create_bdevs_config
+
+timing_enter spdkcli_create_lvols_config
+$spdkcli_job "/lvol_stores create lvs Malloc0" "lvs" True
+$spdkcli_job "/bdevs/logical_volume create lvol 16 lvs" "lvs/lvol" True
+timing_exit spdkcli_create_lvols_config
+
+timing_enter spdkcli_create_vhosts_config
+$spdkcli_job "vhost/block create vhost_blk1 Nvme0n1p0" "Nvme0n1p0" True
+$spdkcli_job "vhost/block create vhost_blk2 Nvme0n1p1 0x2 readonly" "Nvme0n1p1" True
+$spdkcli_job "vhost/scsi create vhost_scsi1" "vhost_scsi1" True
+$spdkcli_job "vhost/scsi create vhost_scsi2" "vhost_scsi2" True
+$spdkcli_job "vhost/scsi/vhost_scsi1 add_lun 0 Malloc2" "Malloc2" True
+$spdkcli_job "vhost/scsi/vhost_scsi2 add_lun 0 Malloc3" "Malloc3" True
+$spdkcli_job "vhost/scsi/vhost_scsi2 add_lun 1 Nvme0n1p2" "Nvme0n1p2" True
+$spdkcli_job "vhost/scsi/vhost_scsi2 add_lun 2 Nvme0n1p3" "Nvme0n1p3" True
+timing_exit spdkcli_create_vhosts_config
+
+timing_enter spdkcli_check_match
+check_match
+timing_exit spdkcli_check_match
+
+timing_enter spdkcli_save_config
+$spdkcli_job "save_config $testdir/config.json"
+$spdkcli_job "save_subsystem_config $testdir/config_bdev.json bdev"
+$spdkcli_job "save_subsystem_config $testdir/config_vhost.json vhost"
+timing_exit spdkcli_save_config
+
+timing_enter spdkcli_check_match_details
+$SPDKCLI_BUILD_DIR/scripts/spdkcli.py bdevs/split_disk/Nvme0n1p0 show_details | jq -r -S '.' > $testdir/match_files/spdkcli_details_vhost.test
+$SPDKCLI_BUILD_DIR/test/app/match/match -v $testdir/match_files/spdkcli_details_vhost.test.match
+rm -f $testdir/match_files/spdkcli_details_vhost.test
+timing_exit spdkcli_check_match_details
+
+timing_enter spdkcli_clear_config
+$spdkcli_job "vhost/scsi/vhost_scsi2 remove_target 2" "Nvme0n1p3"
+$spdkcli_job "vhost/scsi/vhost_scsi2 remove_target 1" "Nvme0n1p2"
+$spdkcli_job "vhost/scsi/vhost_scsi2 remove_target 0" "Malloc3"
+$spdkcli_job "vhost/scsi/vhost_scsi1 remove_target 0" "Malloc2"
+$spdkcli_job "vhost/scsi delete vhost_scsi2" "vhost_scsi2"
+$spdkcli_job "vhost/scsi delete vhost_scsi1" "vhost_scsi1"
+$spdkcli_job "vhost/block delete vhost_blk2" "vhost_blk2"
+$spdkcli_job "vhost/block delete vhost_blk1" "vhost_blk1"
+$spdkcli_job "/bdevs/split_disk destruct_split_bdev Nvme0n1" "Nvme0n1p0"
+$spdkcli_job "/bdevs/aio delete sample" "sample"
+$spdkcli_job "/bdevs/nvme delete Nvme0" "Nvme0"
+$spdkcli_job "/bdevs/null delete null_bdev" "null_bdev"
+$spdkcli_job "/bdevs/logical_volume delete lvs/lvol" "lvs/lvol"
+$spdkcli_job "/lvol_stores delete lvs" "lvs"
+$spdkcli_job "/bdevs/malloc delete Malloc0" "Malloc0"
+$spdkcli_job "/bdevs/malloc delete Malloc1" "Malloc1"
+$spdkcli_job "/bdevs/malloc delete Malloc2" "Malloc2"
+$spdkcli_job "/bdevs/malloc delete Malloc3" "Malloc3"
+timing_exit spdkcli_clear_config
+
+timing_enter spdkcli_load_config
+$spdkcli_job "load_config $testdir/config.json"
+$spdkcli_job "/lvol_stores create lvs Malloc0" "lvs" True
+$spdkcli_job "/bdevs/logical_volume create lvol 16 lvs" "lvs/lvol" True
+check_match
+$spdk_clear_config_py clear_config
+# FIXME: remove this sleep when NVMe driver will be fixed to wait for reset to complete
+sleep 2
+$spdkcli_job "load_subsystem_config $testdir/config_bdev.json"
+$spdkcli_job "load_subsystem_config $testdir/config_vhost.json"
+$spdkcli_job "/lvol_stores create lvs Malloc0" "lvs" True
+$spdkcli_job "/bdevs/logical_volume create lvol 16 lvs" "lvs/lvol" True
+check_match
+rm -f $testdir/config.json
+rm -f $testdir/config_bdev.json
+rm -f $testdir/config_vhost.json
+rm -f /tmp/sample_aio
+timing_exit spdkcli_load_config
+
+killprocess $spdk_tgt_pid
+
+timing_exit spdk_cli_vhost
+report_test_completion spdk_cli_vhost
diff --git a/src/spdk/test/spdkcli/virtio.sh b/src/spdk/test/spdkcli/virtio.sh
new file mode 100755
index 00000000..fd8260be
--- /dev/null
+++ b/src/spdk/test/spdkcli/virtio.sh
@@ -0,0 +1,74 @@
+#!/usr/bin/env bash
+set -xe
+
+testdir=$(readlink -f $(dirname $0))
+. $testdir/common.sh
+
+trap 'killprocess $virtio_pid; on_error_exit' ERR
+timing_enter spdk_cli_vhost
+
+timing_enter run_spdk_tgt
+run_spdk_tgt
+timing_exit run_spdk_tgt
+
+timing_enter run_spdk_virtio
+$SPDKCLI_BUILD_DIR/app/spdk_tgt/spdk_tgt -m 0x4 -p 0 -g -u -s 1024 -r /var/tmp/virtio.sock &
+virtio_pid=$!
+waitforlisten $virtio_pid /var/tmp/virtio.sock
+timing_exit run_spdk_virtio
+
+timing_enter spdkcli_create_virtio_pci_config
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc0" "Malloc0" True
+$spdkcli_job "/bdevs/malloc create 32 512 Malloc1" "Malloc1" True
+pci_blk=$(lspci -nn -D | grep '1af4:1001' | head -1 | awk '{print $1;}')
+if [ ! -z $pci_blk ]; then
+ $spdkcli_job "/bdevs/virtioblk_disk create virtioblk_pci pci $pci_blk" "virtioblk_pci" True
+fi
+pci_scsi=$(lspci -nn -D | grep '1af4:1004' | head -1 | awk '{print $1;}')
+if [ ! -z $pci_scsi ]; then
+ $spdkcli_job "/bdevs/virtioscsi_disk create virtioscsi_pci pci $pci_scsi" "virtioscsi_pci" True
+fi
+$spdkcli_job "/vhost/scsi create sample_scsi" "sample_scsi" True
+$spdkcli_job "/vhost/scsi/sample_scsi add_lun 0 Malloc0" "Malloc0" True
+$spdkcli_job "/vhost/block create sample_block Malloc1" "Malloc1" True
+timing_exit spdkcli_create_virtio_pci_config
+
+timing_enter spdkcli_check_match
+if [ ! -z $pci_blk ] && [ ! -z $pci_scsi ]; then
+ MATCH_FILE="spdkcli_virtio_pci.test"
+ SPDKCLI_BRANCH="/bdevs"
+ check_match
+fi
+timing_exit spdkcli_check_match
+
+timing_exit spdkcli_create_virtio_user_config
+$spdkcli_job "/bdevs/virtioblk_disk create virtioblk_user user $testdir/../../sample_block" "virtioblk_user" True "/var/tmp/virtio.sock"
+$spdkcli_job "/bdevs/virtioscsi_disk create virtioscsi_user user $testdir/../../sample_scsi" "virtioscsi_user" True "/var/tmp/virtio.sock"
+timing_exit spdkcli_create_virtio_user_config
+
+timing_enter spdkcli_check_match
+MATCH_FILE="spdkcli_virtio_user.test"
+SPDKCLI_BRANCH="/vhost"
+check_match
+timing_exit spdkcli_check_match
+
+timing_enter spdkcli_clear_virtio_config
+$spdkcli_job "/bdevs/virtioscsi_disk delete virtioscsi_user" "" False "/var/tmp/virtio.sock"
+$spdkcli_job "/bdevs/virtioblk_disk delete virtioblk_user" "" False "/var/tmp/virtio.sock"
+$spdkcli_job "/vhost/block delete sample_block" "sample_block"
+$spdkcli_job "/vhost/scsi/sample_scsi remove_target 0" "Malloc0"
+$spdkcli_job "/vhost/scsi delete sample_scsi" " sample_scsi"
+if [ ! -z $pci_blk ]; then
+ $spdkcli_job "/bdevs/virtioblk_disk delete virtioblk_pci" "virtioblk_pci"
+fi
+if [ ! -z $pci_scsi ]; then
+ $spdkcli_job "/bdevs/virtioscsi_disk delete virtioscsi_pci" "virtioscsi_pci"
+fi
+$spdkcli_job "/bdevs/malloc delete Malloc0" "Malloc0"
+$spdkcli_job "/bdevs/malloc delete Malloc1" "Malloc1"
+timing_exit spdkcli_clear_virtio_config
+
+killprocess $virtio_pid
+killprocess $spdk_tgt_pid
+
+timing_exit spdk_cli_vhost
diff --git a/src/spdk/test/unit/Makefile b/src/spdk/test/unit/Makefile
new file mode 100644
index 00000000..dbe663cb
--- /dev/null
+++ b/src/spdk/test/unit/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 = include lib
+
+.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/unit/include/Makefile b/src/spdk/test/unit/include/Makefile
new file mode 100644
index 00000000..0ddc1524
--- /dev/null
+++ b/src/spdk/test/unit/include/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 = spdk
+
+.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/unit/include/spdk/Makefile b/src/spdk/test/unit/include/spdk/Makefile
new file mode 100644
index 00000000..d99c7d63
--- /dev/null
+++ b/src/spdk/test/unit/include/spdk/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 = histogram_data.h
+
+.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/unit/include/spdk/histogram_data.h/.gitignore b/src/spdk/test/unit/include/spdk/histogram_data.h/.gitignore
new file mode 100644
index 00000000..b2b36ff7
--- /dev/null
+++ b/src/spdk/test/unit/include/spdk/histogram_data.h/.gitignore
@@ -0,0 +1 @@
+histogram_ut
diff --git a/src/spdk/test/unit/include/spdk/histogram_data.h/Makefile b/src/spdk/test/unit/include/spdk/histogram_data.h/Makefile
new file mode 100644
index 00000000..08d4f052
--- /dev/null
+++ b/src/spdk/test/unit/include/spdk/histogram_data.h/Makefile
@@ -0,0 +1,39 @@
+#
+# BSD LICENSE
+#
+# Copyright (c) 2016 FUJITSU LIMITED, 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 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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+TEST_FILE = histogram_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/include/spdk/histogram_data.h/histogram_ut.c b/src/spdk/test/unit/include/spdk/histogram_data.h/histogram_ut.c
new file mode 100644
index 00000000..da67e2ef
--- /dev/null
+++ b/src/spdk/test/unit/include/spdk/histogram_data.h/histogram_ut.c
@@ -0,0 +1,135 @@
+/*-
+ * 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_cunit.h"
+
+#include "spdk/histogram_data.h"
+#include "spdk/util.h"
+
+uint64_t g_values[] = {
+ 1,
+ 10,
+ 1000,
+ 50000,
+ (1ULL << 63),
+ UINT64_MAX
+};
+
+uint64_t *g_values_end = &g_values[SPDK_COUNTOF(g_values)];
+uint64_t g_total;
+
+static void
+check_values(void *ctx, uint64_t start, uint64_t end, uint64_t count,
+ uint64_t total, uint64_t so_far)
+{
+ uint64_t **values = ctx;
+
+ if (count == 0) {
+ return;
+ }
+
+ CU_ASSERT(so_far == (g_total + count));
+
+ /*
+ * The bucket for this iteration does not include end, but
+ * subtract one anyways to account for the last bucket
+ * which will have end = 0x0 (UINT64_MAX + 1).
+ */
+ end--;
+
+ while (1) {
+ CU_ASSERT(**values >= start);
+ /*
+ * We subtracted one from end above, so it's OK here for
+ * **values to equal end.
+ */
+ CU_ASSERT(**values <= end);
+ g_total++;
+ count--;
+ (*values)++;
+ if (*values == g_values_end || **values > end) {
+ break;
+ }
+ }
+
+ CU_ASSERT(count == 0);
+}
+
+static void
+histogram_test(void)
+{
+ struct spdk_histogram_data *h;
+ uint64_t *values = g_values;
+ uint32_t i;
+
+ h = spdk_histogram_data_alloc();
+
+ for (i = 0; i < SPDK_COUNTOF(g_values); i++) {
+ spdk_histogram_data_tally(h, g_values[i]);
+ }
+
+ g_total = 0;
+ spdk_histogram_data_iterate(h, check_values, &values);
+
+ spdk_histogram_data_free(h);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("histogram", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "histogram_test", histogram_test) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/Makefile b/src/spdk/test/unit/lib/Makefile
new file mode 100644
index 00000000..205835ee
--- /dev/null
+++ b/src/spdk/test/unit/lib/Makefile
@@ -0,0 +1,47 @@
+#
+# 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 blob blobfs event ioat iscsi json jsonrpc log lvol nvme nvmf scsi sock thread util
+ifeq ($(OS),Linux)
+DIRS-$(CONFIG_VHOST) += vhost
+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/unit/lib/bdev/Makefile b/src/spdk/test/unit/lib/bdev/Makefile
new file mode 100644
index 00000000..61efba78
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/Makefile
@@ -0,0 +1,50 @@
+#
+# 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.c part.c scsi_nvme.c gpt vbdev_lvol.c mt bdev_raid.c
+
+ifeq ($(CONFIG_CRYPTO),y)
+DIRS-y += crypto.c
+endif
+
+DIRS-$(CONFIG_PMDK) += pmem
+
+.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/unit/lib/bdev/bdev.c/.gitignore b/src/spdk/test/unit/lib/bdev/bdev.c/.gitignore
new file mode 100644
index 00000000..a5a22d0d
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/bdev.c/.gitignore
@@ -0,0 +1 @@
+bdev_ut
diff --git a/src/spdk/test/unit/lib/bdev/bdev.c/Makefile b/src/spdk/test/unit/lib/bdev/bdev.c/Makefile
new file mode 100644
index 00000000..384fa27a
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/bdev.c/Makefile
@@ -0,0 +1,39 @@
+#
+# BSD LICENSE
+#
+# Copyright (c) 2016 FUJITSU LIMITED, 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 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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+TEST_FILE = bdev_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/bdev/bdev.c/bdev_ut.c b/src/spdk/test/unit/lib/bdev/bdev.c/bdev_ut.c
new file mode 100644
index 00000000..3c14f712
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/bdev.c/bdev_ut.c
@@ -0,0 +1,1214 @@
+/*-
+ * 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_cunit.h"
+
+#include "common/lib/test_env.c"
+#include "unit/lib/json_mock.c"
+
+#include "spdk/config.h"
+/* HACK: disable VTune integration so the unit test doesn't need VTune headers and libs to build */
+#undef SPDK_CONFIG_VTUNE
+
+#include "bdev/bdev.c"
+
+DEFINE_STUB(spdk_conf_find_section, struct spdk_conf_section *, (struct spdk_conf *cp,
+ const char *name), NULL);
+DEFINE_STUB(spdk_conf_section_get_nmval, char *,
+ (struct spdk_conf_section *sp, const char *key, int idx1, int idx2), NULL);
+DEFINE_STUB(spdk_conf_section_get_intval, int, (struct spdk_conf_section *sp, const char *key), -1);
+
+struct spdk_trace_histories *g_trace_histories;
+DEFINE_STUB_V(spdk_trace_add_register_fn, (struct spdk_trace_register_fn *reg_fn));
+DEFINE_STUB_V(spdk_trace_register_owner, (uint8_t type, char id_prefix));
+DEFINE_STUB_V(spdk_trace_register_object, (uint8_t type, char id_prefix));
+DEFINE_STUB_V(spdk_trace_register_description, (const char *name, const char *short_name,
+ uint16_t tpoint_id, uint8_t owner_type,
+ uint8_t object_type, uint8_t new_object,
+ uint8_t arg1_is_ptr, const char *arg1_name));
+DEFINE_STUB_V(_spdk_trace_record, (uint64_t tsc, uint16_t tpoint_id, uint16_t poller_id,
+ uint32_t size, uint64_t object_id, uint64_t arg1));
+
+static void
+_bdev_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx)
+{
+ fn(ctx);
+}
+
+void
+spdk_scsi_nvme_translate(const struct spdk_bdev_io *bdev_io,
+ int *sc, int *sk, int *asc, int *ascq)
+{
+}
+
+static int
+null_init(void)
+{
+ return 0;
+}
+
+static int
+null_clean(void)
+{
+ return 0;
+}
+
+static int
+stub_destruct(void *ctx)
+{
+ return 0;
+}
+
+struct ut_expected_io {
+ uint8_t type;
+ uint64_t offset;
+ uint64_t length;
+ int iovcnt;
+ struct iovec iov[BDEV_IO_NUM_CHILD_IOV];
+ TAILQ_ENTRY(ut_expected_io) link;
+};
+
+struct bdev_ut_channel {
+ TAILQ_HEAD(, spdk_bdev_io) outstanding_io;
+ uint32_t outstanding_io_count;
+ TAILQ_HEAD(, ut_expected_io) expected_io;
+};
+
+static bool g_io_done;
+static enum spdk_bdev_io_status g_io_status;
+static uint32_t g_bdev_ut_io_device;
+static struct bdev_ut_channel *g_bdev_ut_channel;
+
+static struct ut_expected_io *
+ut_alloc_expected_io(uint8_t type, uint64_t offset, uint64_t length, int iovcnt)
+{
+ struct ut_expected_io *expected_io;
+
+ expected_io = calloc(1, sizeof(*expected_io));
+ SPDK_CU_ASSERT_FATAL(expected_io != NULL);
+
+ expected_io->type = type;
+ expected_io->offset = offset;
+ expected_io->length = length;
+ expected_io->iovcnt = iovcnt;
+
+ return expected_io;
+}
+
+static void
+ut_expected_io_set_iov(struct ut_expected_io *expected_io, int pos, void *base, size_t len)
+{
+ expected_io->iov[pos].iov_base = base;
+ expected_io->iov[pos].iov_len = len;
+}
+
+static void
+stub_submit_request(struct spdk_io_channel *_ch, struct spdk_bdev_io *bdev_io)
+{
+ struct bdev_ut_channel *ch = spdk_io_channel_get_ctx(_ch);
+ struct ut_expected_io *expected_io;
+ struct iovec *iov, *expected_iov;
+ int i;
+
+ TAILQ_INSERT_TAIL(&ch->outstanding_io, bdev_io, module_link);
+ ch->outstanding_io_count++;
+
+ expected_io = TAILQ_FIRST(&ch->expected_io);
+ if (expected_io == NULL) {
+ return;
+ }
+ TAILQ_REMOVE(&ch->expected_io, expected_io, link);
+
+ if (expected_io->type != SPDK_BDEV_IO_TYPE_INVALID) {
+ CU_ASSERT(bdev_io->type == expected_io->type);
+ }
+
+ if (expected_io->length == 0) {
+ free(expected_io);
+ return;
+ }
+
+ CU_ASSERT(expected_io->offset == bdev_io->u.bdev.offset_blocks);
+ CU_ASSERT(expected_io->length = bdev_io->u.bdev.num_blocks);
+
+ if (expected_io->iovcnt == 0) {
+ free(expected_io);
+ /* UNMAP, WRITE_ZEROES and FLUSH don't have iovs, so we can just return now. */
+ return;
+ }
+
+ CU_ASSERT(expected_io->iovcnt == bdev_io->u.bdev.iovcnt);
+ for (i = 0; i < expected_io->iovcnt; i++) {
+ iov = &bdev_io->u.bdev.iovs[i];
+ expected_iov = &expected_io->iov[i];
+ CU_ASSERT(iov->iov_len == expected_iov->iov_len);
+ CU_ASSERT(iov->iov_base == expected_iov->iov_base);
+ }
+
+ free(expected_io);
+}
+
+static uint32_t
+stub_complete_io(uint32_t num_to_complete)
+{
+ struct bdev_ut_channel *ch = g_bdev_ut_channel;
+ struct spdk_bdev_io *bdev_io;
+ uint32_t num_completed = 0;
+
+ while (num_completed < num_to_complete) {
+ if (TAILQ_EMPTY(&ch->outstanding_io)) {
+ break;
+ }
+ bdev_io = TAILQ_FIRST(&ch->outstanding_io);
+ TAILQ_REMOVE(&ch->outstanding_io, bdev_io, module_link);
+ ch->outstanding_io_count--;
+ spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_SUCCESS);
+ num_completed++;
+ }
+
+ return num_completed;
+}
+
+static struct spdk_io_channel *
+bdev_ut_get_io_channel(void *ctx)
+{
+ return spdk_get_io_channel(&g_bdev_ut_io_device);
+}
+
+static bool
+stub_io_type_supported(void *_bdev, enum spdk_bdev_io_type io_type)
+{
+ return true;
+}
+
+static struct spdk_bdev_fn_table fn_table = {
+ .destruct = stub_destruct,
+ .submit_request = stub_submit_request,
+ .get_io_channel = bdev_ut_get_io_channel,
+ .io_type_supported = stub_io_type_supported,
+};
+
+static int
+bdev_ut_create_ch(void *io_device, void *ctx_buf)
+{
+ struct bdev_ut_channel *ch = ctx_buf;
+
+ CU_ASSERT(g_bdev_ut_channel == NULL);
+ g_bdev_ut_channel = ch;
+
+ TAILQ_INIT(&ch->outstanding_io);
+ ch->outstanding_io_count = 0;
+ TAILQ_INIT(&ch->expected_io);
+ return 0;
+}
+
+static void
+bdev_ut_destroy_ch(void *io_device, void *ctx_buf)
+{
+ CU_ASSERT(g_bdev_ut_channel != NULL);
+ g_bdev_ut_channel = NULL;
+}
+
+static int
+bdev_ut_module_init(void)
+{
+ spdk_io_device_register(&g_bdev_ut_io_device, bdev_ut_create_ch, bdev_ut_destroy_ch,
+ sizeof(struct bdev_ut_channel), NULL);
+ return 0;
+}
+
+static void
+bdev_ut_module_fini(void)
+{
+ spdk_io_device_unregister(&g_bdev_ut_io_device, NULL);
+}
+
+struct spdk_bdev_module bdev_ut_if = {
+ .name = "bdev_ut",
+ .module_init = bdev_ut_module_init,
+ .module_fini = bdev_ut_module_fini,
+};
+
+static void vbdev_ut_examine(struct spdk_bdev *bdev);
+
+static int
+vbdev_ut_module_init(void)
+{
+ return 0;
+}
+
+static void
+vbdev_ut_module_fini(void)
+{
+}
+
+struct spdk_bdev_module vbdev_ut_if = {
+ .name = "vbdev_ut",
+ .module_init = vbdev_ut_module_init,
+ .module_fini = vbdev_ut_module_fini,
+ .examine_config = vbdev_ut_examine,
+};
+
+SPDK_BDEV_MODULE_REGISTER(&bdev_ut_if)
+SPDK_BDEV_MODULE_REGISTER(&vbdev_ut_if)
+
+static void
+vbdev_ut_examine(struct spdk_bdev *bdev)
+{
+ spdk_bdev_module_examine_done(&vbdev_ut_if);
+}
+
+static struct spdk_bdev *
+allocate_bdev(char *name)
+{
+ struct spdk_bdev *bdev;
+ int rc;
+
+ bdev = calloc(1, sizeof(*bdev));
+ SPDK_CU_ASSERT_FATAL(bdev != NULL);
+
+ bdev->name = name;
+ bdev->fn_table = &fn_table;
+ bdev->module = &bdev_ut_if;
+ bdev->blockcnt = 1024;
+ bdev->blocklen = 512;
+
+ rc = spdk_bdev_register(bdev);
+ CU_ASSERT(rc == 0);
+
+ return bdev;
+}
+
+static struct spdk_bdev *
+allocate_vbdev(char *name, struct spdk_bdev *base1, struct spdk_bdev *base2)
+{
+ struct spdk_bdev *bdev;
+ struct spdk_bdev *array[2];
+ int rc;
+
+ bdev = calloc(1, sizeof(*bdev));
+ SPDK_CU_ASSERT_FATAL(bdev != NULL);
+
+ bdev->name = name;
+ bdev->fn_table = &fn_table;
+ bdev->module = &vbdev_ut_if;
+
+ /* vbdev must have at least one base bdev */
+ CU_ASSERT(base1 != NULL);
+
+ array[0] = base1;
+ array[1] = base2;
+
+ rc = spdk_vbdev_register(bdev, array, base2 == NULL ? 1 : 2);
+ CU_ASSERT(rc == 0);
+
+ return bdev;
+}
+
+static void
+free_bdev(struct spdk_bdev *bdev)
+{
+ spdk_bdev_unregister(bdev, NULL, NULL);
+ memset(bdev, 0xFF, sizeof(*bdev));
+ free(bdev);
+}
+
+static void
+free_vbdev(struct spdk_bdev *bdev)
+{
+ spdk_bdev_unregister(bdev, NULL, NULL);
+ memset(bdev, 0xFF, sizeof(*bdev));
+ free(bdev);
+}
+
+static void
+get_device_stat_cb(struct spdk_bdev *bdev, struct spdk_bdev_io_stat *stat, void *cb_arg, int rc)
+{
+ const char *bdev_name;
+
+ CU_ASSERT(bdev != NULL);
+ CU_ASSERT(rc == 0);
+ bdev_name = spdk_bdev_get_name(bdev);
+ CU_ASSERT_STRING_EQUAL(bdev_name, "bdev0");
+
+ free(stat);
+ free_bdev(bdev);
+}
+
+static void
+get_device_stat_test(void)
+{
+ struct spdk_bdev *bdev;
+ struct spdk_bdev_io_stat *stat;
+
+ bdev = allocate_bdev("bdev0");
+ stat = calloc(1, sizeof(struct spdk_bdev_io_stat));
+ if (stat == NULL) {
+ free_bdev(bdev);
+ return;
+ }
+ spdk_bdev_get_device_stat(bdev, stat, get_device_stat_cb, NULL);
+}
+
+static void
+open_write_test(void)
+{
+ struct spdk_bdev *bdev[9];
+ struct spdk_bdev_desc *desc[9] = {};
+ int rc;
+
+ /*
+ * Create a tree of bdevs to test various open w/ write cases.
+ *
+ * bdev0 through bdev3 are physical block devices, such as NVMe
+ * namespaces or Ceph block devices.
+ *
+ * bdev4 is a virtual bdev with multiple base bdevs. This models
+ * caching or RAID use cases.
+ *
+ * bdev5 through bdev7 are all virtual bdevs with the same base
+ * bdev (except bdev7). This models partitioning or logical volume
+ * use cases.
+ *
+ * bdev7 is a virtual bdev with multiple base bdevs. One of base bdevs
+ * (bdev2) is shared with other virtual bdevs: bdev5 and bdev6. This
+ * models caching, RAID, partitioning or logical volumes use cases.
+ *
+ * bdev8 is a virtual bdev with multiple base bdevs, but these
+ * base bdevs are themselves virtual bdevs.
+ *
+ * bdev8
+ * |
+ * +----------+
+ * | |
+ * bdev4 bdev5 bdev6 bdev7
+ * | | | |
+ * +---+---+ +---+ + +---+---+
+ * | | \ | / \
+ * bdev0 bdev1 bdev2 bdev3
+ */
+
+ bdev[0] = allocate_bdev("bdev0");
+ rc = spdk_bdev_module_claim_bdev(bdev[0], NULL, &bdev_ut_if);
+ CU_ASSERT(rc == 0);
+
+ bdev[1] = allocate_bdev("bdev1");
+ rc = spdk_bdev_module_claim_bdev(bdev[1], NULL, &bdev_ut_if);
+ CU_ASSERT(rc == 0);
+
+ bdev[2] = allocate_bdev("bdev2");
+ rc = spdk_bdev_module_claim_bdev(bdev[2], NULL, &bdev_ut_if);
+ CU_ASSERT(rc == 0);
+
+ bdev[3] = allocate_bdev("bdev3");
+ rc = spdk_bdev_module_claim_bdev(bdev[3], NULL, &bdev_ut_if);
+ CU_ASSERT(rc == 0);
+
+ bdev[4] = allocate_vbdev("bdev4", bdev[0], bdev[1]);
+ rc = spdk_bdev_module_claim_bdev(bdev[4], NULL, &bdev_ut_if);
+ CU_ASSERT(rc == 0);
+
+ bdev[5] = allocate_vbdev("bdev5", bdev[2], NULL);
+ rc = spdk_bdev_module_claim_bdev(bdev[5], NULL, &bdev_ut_if);
+ CU_ASSERT(rc == 0);
+
+ bdev[6] = allocate_vbdev("bdev6", bdev[2], NULL);
+
+ bdev[7] = allocate_vbdev("bdev7", bdev[2], bdev[3]);
+
+ bdev[8] = allocate_vbdev("bdev8", bdev[4], bdev[5]);
+
+ /* Open bdev0 read-only. This should succeed. */
+ rc = spdk_bdev_open(bdev[0], false, NULL, NULL, &desc[0]);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(desc[0] != NULL);
+ spdk_bdev_close(desc[0]);
+
+ /*
+ * Open bdev1 read/write. This should fail since bdev1 has been claimed
+ * by a vbdev module.
+ */
+ rc = spdk_bdev_open(bdev[1], true, NULL, NULL, &desc[1]);
+ CU_ASSERT(rc == -EPERM);
+
+ /*
+ * Open bdev4 read/write. This should fail since bdev3 has been claimed
+ * by a vbdev module.
+ */
+ rc = spdk_bdev_open(bdev[4], true, NULL, NULL, &desc[4]);
+ CU_ASSERT(rc == -EPERM);
+
+ /* Open bdev4 read-only. This should succeed. */
+ rc = spdk_bdev_open(bdev[4], false, NULL, NULL, &desc[4]);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(desc[4] != NULL);
+ spdk_bdev_close(desc[4]);
+
+ /*
+ * Open bdev8 read/write. This should succeed since it is a leaf
+ * bdev.
+ */
+ rc = spdk_bdev_open(bdev[8], true, NULL, NULL, &desc[8]);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(desc[8] != NULL);
+ spdk_bdev_close(desc[8]);
+
+ /*
+ * Open bdev5 read/write. This should fail since bdev4 has been claimed
+ * by a vbdev module.
+ */
+ rc = spdk_bdev_open(bdev[5], true, NULL, NULL, &desc[5]);
+ CU_ASSERT(rc == -EPERM);
+
+ /* Open bdev4 read-only. This should succeed. */
+ rc = spdk_bdev_open(bdev[5], false, NULL, NULL, &desc[5]);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(desc[5] != NULL);
+ spdk_bdev_close(desc[5]);
+
+ free_vbdev(bdev[8]);
+
+ free_vbdev(bdev[5]);
+ free_vbdev(bdev[6]);
+ free_vbdev(bdev[7]);
+
+ free_vbdev(bdev[4]);
+
+ free_bdev(bdev[0]);
+ free_bdev(bdev[1]);
+ free_bdev(bdev[2]);
+ free_bdev(bdev[3]);
+}
+
+static void
+bytes_to_blocks_test(void)
+{
+ struct spdk_bdev bdev;
+ uint64_t offset_blocks, num_blocks;
+
+ memset(&bdev, 0, sizeof(bdev));
+
+ bdev.blocklen = 512;
+
+ /* All parameters valid */
+ offset_blocks = 0;
+ num_blocks = 0;
+ CU_ASSERT(spdk_bdev_bytes_to_blocks(&bdev, 512, &offset_blocks, 1024, &num_blocks) == 0);
+ CU_ASSERT(offset_blocks == 1);
+ CU_ASSERT(num_blocks == 2);
+
+ /* Offset not a block multiple */
+ CU_ASSERT(spdk_bdev_bytes_to_blocks(&bdev, 3, &offset_blocks, 512, &num_blocks) != 0);
+
+ /* Length not a block multiple */
+ CU_ASSERT(spdk_bdev_bytes_to_blocks(&bdev, 512, &offset_blocks, 3, &num_blocks) != 0);
+}
+
+static void
+num_blocks_test(void)
+{
+ struct spdk_bdev bdev;
+ struct spdk_bdev_desc *desc = NULL;
+ int rc;
+
+ memset(&bdev, 0, sizeof(bdev));
+ bdev.name = "num_blocks";
+ bdev.fn_table = &fn_table;
+ bdev.module = &bdev_ut_if;
+ spdk_bdev_register(&bdev);
+ spdk_bdev_notify_blockcnt_change(&bdev, 50);
+
+ /* Growing block number */
+ CU_ASSERT(spdk_bdev_notify_blockcnt_change(&bdev, 70) == 0);
+ /* Shrinking block number */
+ CU_ASSERT(spdk_bdev_notify_blockcnt_change(&bdev, 30) == 0);
+
+ /* In case bdev opened */
+ rc = spdk_bdev_open(&bdev, false, NULL, NULL, &desc);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(desc != NULL);
+
+ /* Growing block number */
+ CU_ASSERT(spdk_bdev_notify_blockcnt_change(&bdev, 80) == 0);
+ /* Shrinking block number */
+ CU_ASSERT(spdk_bdev_notify_blockcnt_change(&bdev, 20) != 0);
+
+ spdk_bdev_close(desc);
+ spdk_bdev_unregister(&bdev, NULL, NULL);
+}
+
+static void
+io_valid_test(void)
+{
+ struct spdk_bdev bdev;
+
+ memset(&bdev, 0, sizeof(bdev));
+
+ bdev.blocklen = 512;
+ spdk_bdev_notify_blockcnt_change(&bdev, 100);
+
+ /* All parameters valid */
+ CU_ASSERT(spdk_bdev_io_valid_blocks(&bdev, 1, 2) == true);
+
+ /* Last valid block */
+ CU_ASSERT(spdk_bdev_io_valid_blocks(&bdev, 99, 1) == true);
+
+ /* Offset past end of bdev */
+ CU_ASSERT(spdk_bdev_io_valid_blocks(&bdev, 100, 1) == false);
+
+ /* Offset + length past end of bdev */
+ CU_ASSERT(spdk_bdev_io_valid_blocks(&bdev, 99, 2) == false);
+
+ /* Offset near end of uint64_t range (2^64 - 1) */
+ CU_ASSERT(spdk_bdev_io_valid_blocks(&bdev, 18446744073709551615ULL, 1) == false);
+}
+
+static void
+alias_add_del_test(void)
+{
+ struct spdk_bdev *bdev[3];
+ int rc;
+
+ /* Creating and registering bdevs */
+ bdev[0] = allocate_bdev("bdev0");
+ SPDK_CU_ASSERT_FATAL(bdev[0] != 0);
+
+ bdev[1] = allocate_bdev("bdev1");
+ SPDK_CU_ASSERT_FATAL(bdev[1] != 0);
+
+ bdev[2] = allocate_bdev("bdev2");
+ SPDK_CU_ASSERT_FATAL(bdev[2] != 0);
+
+ /*
+ * Trying adding an alias identical to name.
+ * Alias is identical to name, so it can not be added to aliases list
+ */
+ rc = spdk_bdev_alias_add(bdev[0], bdev[0]->name);
+ CU_ASSERT(rc == -EEXIST);
+
+ /*
+ * Trying to add empty alias,
+ * this one should fail
+ */
+ rc = spdk_bdev_alias_add(bdev[0], NULL);
+ CU_ASSERT(rc == -EINVAL);
+
+ /* Trying adding same alias to two different registered bdevs */
+
+ /* Alias is used first time, so this one should pass */
+ rc = spdk_bdev_alias_add(bdev[0], "proper alias 0");
+ CU_ASSERT(rc == 0);
+
+ /* Alias was added to another bdev, so this one should fail */
+ rc = spdk_bdev_alias_add(bdev[1], "proper alias 0");
+ CU_ASSERT(rc == -EEXIST);
+
+ /* Alias is used first time, so this one should pass */
+ rc = spdk_bdev_alias_add(bdev[1], "proper alias 1");
+ CU_ASSERT(rc == 0);
+
+ /* Trying removing an alias from registered bdevs */
+
+ /* Alias is not on a bdev aliases list, so this one should fail */
+ rc = spdk_bdev_alias_del(bdev[0], "not existing");
+ CU_ASSERT(rc == -ENOENT);
+
+ /* Alias is present on a bdev aliases list, so this one should pass */
+ rc = spdk_bdev_alias_del(bdev[0], "proper alias 0");
+ CU_ASSERT(rc == 0);
+
+ /* Alias is present on a bdev aliases list, so this one should pass */
+ rc = spdk_bdev_alias_del(bdev[1], "proper alias 1");
+ CU_ASSERT(rc == 0);
+
+ /* Trying to remove name instead of alias, so this one should fail, name cannot be changed or removed */
+ rc = spdk_bdev_alias_del(bdev[0], bdev[0]->name);
+ CU_ASSERT(rc != 0);
+
+ /* Trying to del all alias from empty alias list */
+ spdk_bdev_alias_del_all(bdev[2]);
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&bdev[2]->aliases));
+
+ /* Trying to del all alias from non-empty alias list */
+ rc = spdk_bdev_alias_add(bdev[2], "alias0");
+ CU_ASSERT(rc == 0);
+ rc = spdk_bdev_alias_add(bdev[2], "alias1");
+ CU_ASSERT(rc == 0);
+ spdk_bdev_alias_del_all(bdev[2]);
+ CU_ASSERT(TAILQ_EMPTY(&bdev[2]->aliases));
+
+ /* Unregister and free bdevs */
+ spdk_bdev_unregister(bdev[0], NULL, NULL);
+ spdk_bdev_unregister(bdev[1], NULL, NULL);
+ spdk_bdev_unregister(bdev[2], NULL, NULL);
+
+ free(bdev[0]);
+ free(bdev[1]);
+ free(bdev[2]);
+}
+
+static void
+io_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
+{
+ g_io_done = true;
+ g_io_status = bdev_io->internal.status;
+ spdk_bdev_free_io(bdev_io);
+}
+
+static void
+bdev_init_cb(void *arg, int rc)
+{
+ CU_ASSERT(rc == 0);
+}
+
+static void
+bdev_fini_cb(void *arg)
+{
+}
+
+struct bdev_ut_io_wait_entry {
+ struct spdk_bdev_io_wait_entry entry;
+ struct spdk_io_channel *io_ch;
+ struct spdk_bdev_desc *desc;
+ bool submitted;
+};
+
+static void
+io_wait_cb(void *arg)
+{
+ struct bdev_ut_io_wait_entry *entry = arg;
+ int rc;
+
+ rc = spdk_bdev_read_blocks(entry->desc, entry->io_ch, NULL, 0, 1, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ entry->submitted = true;
+}
+
+static void
+bdev_io_wait_test(void)
+{
+ struct spdk_bdev *bdev;
+ struct spdk_bdev_desc *desc = NULL;
+ struct spdk_io_channel *io_ch;
+ struct spdk_bdev_opts bdev_opts = {
+ .bdev_io_pool_size = 4,
+ .bdev_io_cache_size = 2,
+ };
+ struct bdev_ut_io_wait_entry io_wait_entry;
+ struct bdev_ut_io_wait_entry io_wait_entry2;
+ int rc;
+
+ rc = spdk_bdev_set_opts(&bdev_opts);
+ CU_ASSERT(rc == 0);
+ spdk_bdev_initialize(bdev_init_cb, NULL);
+
+ bdev = allocate_bdev("bdev0");
+
+ rc = spdk_bdev_open(bdev, true, NULL, NULL, &desc);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(desc != NULL);
+ io_ch = spdk_bdev_get_io_channel(desc);
+ CU_ASSERT(io_ch != NULL);
+
+ rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 4);
+
+ rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL);
+ CU_ASSERT(rc == -ENOMEM);
+
+ io_wait_entry.entry.bdev = bdev;
+ io_wait_entry.entry.cb_fn = io_wait_cb;
+ io_wait_entry.entry.cb_arg = &io_wait_entry;
+ io_wait_entry.io_ch = io_ch;
+ io_wait_entry.desc = desc;
+ io_wait_entry.submitted = false;
+ /* Cannot use the same io_wait_entry for two different calls. */
+ memcpy(&io_wait_entry2, &io_wait_entry, sizeof(io_wait_entry));
+ io_wait_entry2.entry.cb_arg = &io_wait_entry2;
+
+ /* Queue two I/O waits. */
+ rc = spdk_bdev_queue_io_wait(bdev, io_ch, &io_wait_entry.entry);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(io_wait_entry.submitted == false);
+ rc = spdk_bdev_queue_io_wait(bdev, io_ch, &io_wait_entry2.entry);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(io_wait_entry2.submitted == false);
+
+ stub_complete_io(1);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 4);
+ CU_ASSERT(io_wait_entry.submitted == true);
+ CU_ASSERT(io_wait_entry2.submitted == false);
+
+ stub_complete_io(1);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 4);
+ CU_ASSERT(io_wait_entry2.submitted == true);
+
+ stub_complete_io(4);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 0);
+
+ spdk_put_io_channel(io_ch);
+ spdk_bdev_close(desc);
+ free_bdev(bdev);
+ spdk_bdev_finish(bdev_fini_cb, NULL);
+}
+
+static void
+bdev_io_spans_boundary_test(void)
+{
+ struct spdk_bdev bdev;
+ struct spdk_bdev_io bdev_io;
+
+ memset(&bdev, 0, sizeof(bdev));
+
+ bdev.optimal_io_boundary = 0;
+ bdev_io.bdev = &bdev;
+
+ /* bdev has no optimal_io_boundary set - so this should return false. */
+ CU_ASSERT(_spdk_bdev_io_should_split(&bdev_io) == false);
+
+ bdev.optimal_io_boundary = 32;
+ bdev_io.type = SPDK_BDEV_IO_TYPE_RESET;
+
+ /* RESETs are not based on LBAs - so this should return false. */
+ CU_ASSERT(_spdk_bdev_io_should_split(&bdev_io) == false);
+
+ bdev_io.type = SPDK_BDEV_IO_TYPE_READ;
+ bdev_io.u.bdev.offset_blocks = 0;
+ bdev_io.u.bdev.num_blocks = 32;
+
+ /* This I/O run right up to, but does not cross, the boundary - so this should return false. */
+ CU_ASSERT(_spdk_bdev_io_should_split(&bdev_io) == false);
+
+ bdev_io.u.bdev.num_blocks = 33;
+
+ /* This I/O spans a boundary. */
+ CU_ASSERT(_spdk_bdev_io_should_split(&bdev_io) == true);
+}
+
+static void
+bdev_io_split(void)
+{
+ struct spdk_bdev *bdev;
+ struct spdk_bdev_desc *desc = NULL;
+ struct spdk_io_channel *io_ch;
+ struct spdk_bdev_opts bdev_opts = {
+ .bdev_io_pool_size = 512,
+ .bdev_io_cache_size = 64,
+ };
+ struct iovec iov[BDEV_IO_NUM_CHILD_IOV * 2];
+ struct ut_expected_io *expected_io;
+ uint64_t i;
+ int rc;
+
+ rc = spdk_bdev_set_opts(&bdev_opts);
+ CU_ASSERT(rc == 0);
+ spdk_bdev_initialize(bdev_init_cb, NULL);
+
+ bdev = allocate_bdev("bdev0");
+
+ rc = spdk_bdev_open(bdev, true, NULL, NULL, &desc);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(desc != NULL);
+ io_ch = spdk_bdev_get_io_channel(desc);
+ CU_ASSERT(io_ch != NULL);
+
+ bdev->optimal_io_boundary = 16;
+ bdev->split_on_optimal_io_boundary = false;
+
+ g_io_done = false;
+
+ /* First test that the I/O does not get split if split_on_optimal_io_boundary == false. */
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 14, 8, 1);
+ ut_expected_io_set_iov(expected_io, 0, (void *)0xF000, 8 * 512);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ rc = spdk_bdev_read_blocks(desc, io_ch, (void *)0xF000, 14, 8, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_io_done == false);
+
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+ stub_complete_io(1);
+ CU_ASSERT(g_io_done == true);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 0);
+
+ bdev->split_on_optimal_io_boundary = true;
+
+ /* Now test that a single-vector command is split correctly.
+ * Offset 14, length 8, payload 0xF000
+ * Child - Offset 14, length 2, payload 0xF000
+ * Child - Offset 16, length 6, payload 0xF000 + 2 * 512
+ *
+ * Set up the expected values before calling spdk_bdev_read_blocks
+ */
+ g_io_done = false;
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 14, 2, 1);
+ ut_expected_io_set_iov(expected_io, 0, (void *)0xF000, 2 * 512);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 16, 6, 1);
+ ut_expected_io_set_iov(expected_io, 0, (void *)(0xF000 + 2 * 512), 6 * 512);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ /* spdk_bdev_read_blocks will submit the first child immediately. */
+ rc = spdk_bdev_read_blocks(desc, io_ch, (void *)0xF000, 14, 8, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_io_done == false);
+
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 2);
+ stub_complete_io(2);
+ CU_ASSERT(g_io_done == true);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 0);
+
+ /* Now set up a more complex, multi-vector command that needs to be split,
+ * including splitting iovecs.
+ */
+ iov[0].iov_base = (void *)0x10000;
+ iov[0].iov_len = 512;
+ iov[1].iov_base = (void *)0x20000;
+ iov[1].iov_len = 20 * 512;
+ iov[2].iov_base = (void *)0x30000;
+ iov[2].iov_len = 11 * 512;
+
+ g_io_done = false;
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 14, 2, 2);
+ ut_expected_io_set_iov(expected_io, 0, (void *)0x10000, 512);
+ ut_expected_io_set_iov(expected_io, 1, (void *)0x20000, 512);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 16, 16, 1);
+ ut_expected_io_set_iov(expected_io, 0, (void *)(0x20000 + 512), 16 * 512);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 32, 14, 2);
+ ut_expected_io_set_iov(expected_io, 0, (void *)(0x20000 + 17 * 512), 3 * 512);
+ ut_expected_io_set_iov(expected_io, 1, (void *)0x30000, 11 * 512);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ rc = spdk_bdev_writev_blocks(desc, io_ch, iov, 3, 14, 32, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_io_done == false);
+
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 3);
+ stub_complete_io(3);
+ CU_ASSERT(g_io_done == true);
+
+ /* Test multi vector command that needs to be split by strip and then needs to be
+ * split further due to the capacity of child iovs.
+ */
+ for (i = 0; i < BDEV_IO_NUM_CHILD_IOV * 2; i++) {
+ iov[i].iov_base = (void *)((i + 1) * 0x10000);
+ iov[i].iov_len = 512;
+ }
+
+ bdev->optimal_io_boundary = BDEV_IO_NUM_CHILD_IOV;
+ g_io_done = false;
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 0, BDEV_IO_NUM_CHILD_IOV,
+ BDEV_IO_NUM_CHILD_IOV);
+ for (i = 0; i < BDEV_IO_NUM_CHILD_IOV; i++) {
+ ut_expected_io_set_iov(expected_io, i, (void *)((i + 1) * 0x10000), 512);
+ }
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, BDEV_IO_NUM_CHILD_IOV,
+ BDEV_IO_NUM_CHILD_IOV, BDEV_IO_NUM_CHILD_IOV);
+ for (i = 0; i < BDEV_IO_NUM_CHILD_IOV; i++) {
+ ut_expected_io_set_iov(expected_io, i,
+ (void *)((i + 1 + BDEV_IO_NUM_CHILD_IOV) * 0x10000), 512);
+ }
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ rc = spdk_bdev_readv_blocks(desc, io_ch, iov, BDEV_IO_NUM_CHILD_IOV * 2, 0,
+ BDEV_IO_NUM_CHILD_IOV * 2, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_io_done == false);
+
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+ stub_complete_io(1);
+ CU_ASSERT(g_io_done == false);
+
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+ stub_complete_io(1);
+ CU_ASSERT(g_io_done == true);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 0);
+
+ /* Test multi vector command that needs to be split by strip and then needs to be
+ * split further due to the capacity of child iovs, but fails to split. The cause
+ * of failure of split is that the length of an iovec is not multiple of block size.
+ */
+ for (i = 0; i < BDEV_IO_NUM_CHILD_IOV - 1; i++) {
+ iov[i].iov_base = (void *)((i + 1) * 0x10000);
+ iov[i].iov_len = 512;
+ }
+ iov[BDEV_IO_NUM_CHILD_IOV - 1].iov_base = (void *)(BDEV_IO_NUM_CHILD_IOV * 0x10000);
+ iov[BDEV_IO_NUM_CHILD_IOV - 1].iov_len = 256;
+
+ bdev->optimal_io_boundary = BDEV_IO_NUM_CHILD_IOV;
+ g_io_done = false;
+ g_io_status = 0;
+
+ rc = spdk_bdev_readv_blocks(desc, io_ch, iov, BDEV_IO_NUM_CHILD_IOV * 2, 0,
+ BDEV_IO_NUM_CHILD_IOV * 2, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_io_done == true);
+ CU_ASSERT(g_io_status == SPDK_BDEV_IO_STATUS_FAILED);
+
+ /* Test a WRITE_ZEROES that would span an I/O boundary. WRITE_ZEROES should not be
+ * split, so test that.
+ */
+ bdev->optimal_io_boundary = 15;
+ g_io_done = false;
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE_ZEROES, 9, 36, 0);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ rc = spdk_bdev_write_zeroes_blocks(desc, io_ch, 9, 36, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_io_done == false);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+ stub_complete_io(1);
+ CU_ASSERT(g_io_done == true);
+
+ /* Test an UNMAP. This should also not be split. */
+ bdev->optimal_io_boundary = 16;
+ g_io_done = false;
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_UNMAP, 15, 2, 0);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ rc = spdk_bdev_unmap_blocks(desc, io_ch, 15, 2, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_io_done == false);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+ stub_complete_io(1);
+ CU_ASSERT(g_io_done == true);
+
+ /* Test a FLUSH. This should also not be split. */
+ bdev->optimal_io_boundary = 16;
+ g_io_done = false;
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_FLUSH, 15, 2, 0);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ rc = spdk_bdev_flush_blocks(desc, io_ch, 15, 2, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_io_done == false);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+ stub_complete_io(1);
+ CU_ASSERT(g_io_done == true);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_bdev_ut_channel->expected_io));
+
+ spdk_put_io_channel(io_ch);
+ spdk_bdev_close(desc);
+ free_bdev(bdev);
+ spdk_bdev_finish(bdev_fini_cb, NULL);
+}
+
+static void
+bdev_io_split_with_io_wait(void)
+{
+ struct spdk_bdev *bdev;
+ struct spdk_bdev_desc *desc;
+ struct spdk_io_channel *io_ch;
+ struct spdk_bdev_channel *channel;
+ struct spdk_bdev_mgmt_channel *mgmt_ch;
+ struct spdk_bdev_opts bdev_opts = {
+ .bdev_io_pool_size = 2,
+ .bdev_io_cache_size = 1,
+ };
+ struct iovec iov[3];
+ struct ut_expected_io *expected_io;
+ int rc;
+
+ rc = spdk_bdev_set_opts(&bdev_opts);
+ CU_ASSERT(rc == 0);
+ spdk_bdev_initialize(bdev_init_cb, NULL);
+
+ bdev = allocate_bdev("bdev0");
+
+ rc = spdk_bdev_open(bdev, true, NULL, NULL, &desc);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(desc != NULL);
+ io_ch = spdk_bdev_get_io_channel(desc);
+ CU_ASSERT(io_ch != NULL);
+ channel = spdk_io_channel_get_ctx(io_ch);
+ mgmt_ch = channel->shared_resource->mgmt_ch;
+
+ bdev->optimal_io_boundary = 16;
+ bdev->split_on_optimal_io_boundary = true;
+
+ rc = spdk_bdev_read_blocks(desc, io_ch, NULL, 0, 1, io_done, NULL);
+ CU_ASSERT(rc == 0);
+
+ /* Now test that a single-vector command is split correctly.
+ * Offset 14, length 8, payload 0xF000
+ * Child - Offset 14, length 2, payload 0xF000
+ * Child - Offset 16, length 6, payload 0xF000 + 2 * 512
+ *
+ * Set up the expected values before calling spdk_bdev_read_blocks
+ */
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 14, 2, 1);
+ ut_expected_io_set_iov(expected_io, 0, (void *)0xF000, 2 * 512);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_READ, 16, 6, 1);
+ ut_expected_io_set_iov(expected_io, 0, (void *)(0xF000 + 2 * 512), 6 * 512);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ /* The following children will be submitted sequentially due to the capacity of
+ * spdk_bdev_io.
+ */
+
+ /* The first child I/O will be queued to wait until an spdk_bdev_io becomes available */
+ rc = spdk_bdev_read_blocks(desc, io_ch, (void *)0xF000, 14, 8, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(!TAILQ_EMPTY(&mgmt_ch->io_wait_queue));
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+
+ /* Completing the first read I/O will submit the first child */
+ stub_complete_io(1);
+ CU_ASSERT(TAILQ_EMPTY(&mgmt_ch->io_wait_queue));
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+
+ /* Completing the first child will submit the second child */
+ stub_complete_io(1);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+
+ /* Complete the second child I/O. This should result in our callback getting
+ * invoked since the parent I/O is now complete.
+ */
+ stub_complete_io(1);
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 0);
+
+ /* Now set up a more complex, multi-vector command that needs to be split,
+ * including splitting iovecs.
+ */
+ iov[0].iov_base = (void *)0x10000;
+ iov[0].iov_len = 512;
+ iov[1].iov_base = (void *)0x20000;
+ iov[1].iov_len = 20 * 512;
+ iov[2].iov_base = (void *)0x30000;
+ iov[2].iov_len = 11 * 512;
+
+ g_io_done = false;
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 14, 2, 2);
+ ut_expected_io_set_iov(expected_io, 0, (void *)0x10000, 512);
+ ut_expected_io_set_iov(expected_io, 1, (void *)0x20000, 512);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 16, 16, 1);
+ ut_expected_io_set_iov(expected_io, 0, (void *)(0x20000 + 512), 16 * 512);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ expected_io = ut_alloc_expected_io(SPDK_BDEV_IO_TYPE_WRITE, 32, 14, 2);
+ ut_expected_io_set_iov(expected_io, 0, (void *)(0x20000 + 17 * 512), 3 * 512);
+ ut_expected_io_set_iov(expected_io, 1, (void *)0x30000, 11 * 512);
+ TAILQ_INSERT_TAIL(&g_bdev_ut_channel->expected_io, expected_io, link);
+
+ rc = spdk_bdev_writev_blocks(desc, io_ch, iov, 3, 14, 32, io_done, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_io_done == false);
+
+ /* The following children will be submitted sequentially due to the capacity of
+ * spdk_bdev_io.
+ */
+
+ /* Completing the first child will submit the second child */
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+ stub_complete_io(1);
+ CU_ASSERT(g_io_done == false);
+
+ /* Completing the second child will submit the third child */
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+ stub_complete_io(1);
+ CU_ASSERT(g_io_done == false);
+
+ /* Completing the third child will result in our callback getting invoked
+ * since the parent I/O is now complete.
+ */
+ CU_ASSERT(g_bdev_ut_channel->outstanding_io_count == 1);
+ stub_complete_io(1);
+ CU_ASSERT(g_io_done == true);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_bdev_ut_channel->expected_io));
+
+ spdk_put_io_channel(io_ch);
+ spdk_bdev_close(desc);
+ free_bdev(bdev);
+ spdk_bdev_finish(bdev_fini_cb, NULL);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("bdev", null_init, null_clean);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "bytes_to_blocks_test", bytes_to_blocks_test) == NULL ||
+ CU_add_test(suite, "num_blocks_test", num_blocks_test) == NULL ||
+ CU_add_test(suite, "io_valid", io_valid_test) == NULL ||
+ CU_add_test(suite, "open_write", open_write_test) == NULL ||
+ CU_add_test(suite, "alias_add_del", alias_add_del_test) == NULL ||
+ CU_add_test(suite, "get_device_stat", get_device_stat_test) == NULL ||
+ CU_add_test(suite, "bdev_io_wait", bdev_io_wait_test) == NULL ||
+ CU_add_test(suite, "bdev_io_spans_boundary", bdev_io_spans_boundary_test) == NULL ||
+ CU_add_test(suite, "bdev_io_split", bdev_io_split) == NULL ||
+ CU_add_test(suite, "bdev_io_split_with_io_wait", bdev_io_split_with_io_wait) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ spdk_allocate_thread(_bdev_send_msg, NULL, NULL, NULL, "thread0");
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ spdk_free_thread();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/bdev/bdev_raid.c/.gitignore b/src/spdk/test/unit/lib/bdev/bdev_raid.c/.gitignore
new file mode 100644
index 00000000..98d1a166
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/bdev_raid.c/.gitignore
@@ -0,0 +1 @@
+bdev_raid_ut
diff --git a/src/spdk/test/unit/lib/bdev/bdev_raid.c/Makefile b/src/spdk/test/unit/lib/bdev/bdev_raid.c/Makefile
new file mode 100644
index 00000000..9739cb44
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/bdev_raid.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = bdev_raid_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/bdev/bdev_raid.c/bdev_raid_ut.c b/src/spdk/test/unit/lib/bdev/bdev_raid.c/bdev_raid_ut.c
new file mode 100644
index 00000000..ffa466da
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/bdev_raid.c/bdev_raid_ut.c
@@ -0,0 +1,2236 @@
+/*-
+ * 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_cunit.h"
+#include "spdk/env.h"
+#include "spdk_internal/mock.h"
+#include "bdev/raid/bdev_raid.c"
+#include "bdev/raid/bdev_raid_rpc.c"
+
+#define MAX_BASE_DRIVES 255
+#define MAX_RAIDS 31
+#define INVALID_IO_SUBMIT 0xFFFF
+
+/* Data structure to capture the output of IO for verification */
+struct io_output {
+ struct spdk_bdev_desc *desc;
+ struct spdk_io_channel *ch;
+ uint64_t offset_blocks;
+ uint64_t num_blocks;
+ spdk_bdev_io_completion_cb cb;
+ void *cb_arg;
+ enum spdk_bdev_io_type iotype;
+};
+
+/* Different test options, more options to test can be added here */
+uint32_t g_blklen_opts[] = {512, 4096};
+uint32_t g_strip_opts[] = {64, 128, 256, 512, 1024, 2048};
+uint32_t g_iosize_opts[] = {256, 512, 1024};
+uint32_t g_max_qd_opts[] = {64, 128, 256, 512, 1024, 2048};
+
+/* Globals */
+int g_bdev_io_submit_status;
+struct io_output *g_io_output = NULL;
+uint32_t g_io_output_index;
+uint32_t g_io_comp_status;
+bool g_child_io_status_flag;
+void *rpc_req;
+uint32_t rpc_req_size;
+TAILQ_HEAD(bdev, spdk_bdev);
+struct bdev g_bdev_list;
+TAILQ_HEAD(waitq, spdk_bdev_io_wait_entry);
+struct waitq g_io_waitq;
+uint32_t g_block_len;
+uint32_t g_strip_size;
+uint32_t g_max_io_size;
+uint32_t g_max_qd;
+uint8_t g_max_base_drives;
+uint8_t g_max_raids;
+uint8_t g_ignore_io_output;
+uint8_t g_rpc_err;
+char *g_get_raids_output[MAX_RAIDS];
+uint32_t g_get_raids_count;
+uint8_t g_json_beg_res_ret_err;
+uint8_t g_json_decode_obj_err;
+uint8_t g_json_decode_obj_construct;
+uint8_t g_config_level_create = 0;
+uint8_t g_test_multi_raids;
+
+/* Set randomly test options, in every run it is different */
+static void
+set_test_opts(void)
+{
+ uint32_t seed = time(0);
+
+ /* Generate random test options */
+ srand(seed);
+ g_max_base_drives = (rand() % MAX_BASE_DRIVES) + 1;
+ g_max_raids = (rand() % MAX_RAIDS) + 1;
+ g_block_len = g_blklen_opts[rand() % SPDK_COUNTOF(g_blklen_opts)];
+ g_strip_size = g_strip_opts[rand() % SPDK_COUNTOF(g_strip_opts)];
+ g_max_io_size = g_iosize_opts[rand() % SPDK_COUNTOF(g_iosize_opts)];
+ g_max_qd = g_max_qd_opts[rand() % SPDK_COUNTOF(g_max_qd_opts)];
+
+ printf("Test Options, seed = %u\n", seed);
+ printf("blocklen = %u, strip_size = %u, max_io_size = %u, max_qd = %u, g_max_base_drives = %u, g_max_raids = %u\n",
+ g_block_len, g_strip_size, g_max_io_size, g_max_qd, g_max_base_drives, g_max_raids);
+}
+
+/* Set globals before every test run */
+static void
+set_globals(void)
+{
+ uint32_t max_splits;
+
+ g_bdev_io_submit_status = 0;
+ if (g_max_io_size < g_strip_size) {
+ max_splits = 2;
+ } else {
+ max_splits = (g_max_io_size / g_strip_size) + 1;
+ }
+ g_io_output = calloc(max_splits, sizeof(struct io_output));
+ SPDK_CU_ASSERT_FATAL(g_io_output != NULL);
+ g_io_output_index = 0;
+ memset(g_get_raids_output, 0, sizeof(g_get_raids_output));
+ g_get_raids_count = 0;
+ g_io_comp_status = 0;
+ g_ignore_io_output = 0;
+ g_config_level_create = 0;
+ g_rpc_err = 0;
+ g_test_multi_raids = 0;
+ g_child_io_status_flag = true;
+ TAILQ_INIT(&g_bdev_list);
+ TAILQ_INIT(&g_io_waitq);
+ rpc_req = NULL;
+ rpc_req_size = 0;
+ g_json_beg_res_ret_err = 0;
+ g_json_decode_obj_err = 0;
+ g_json_decode_obj_construct = 0;
+}
+
+static void
+base_bdevs_cleanup(void)
+{
+ struct spdk_bdev *bdev;
+ struct spdk_bdev *bdev_next;
+
+ if (!TAILQ_EMPTY(&g_bdev_list)) {
+ TAILQ_FOREACH_SAFE(bdev, &g_bdev_list, internal.link, bdev_next) {
+ free(bdev->name);
+ TAILQ_REMOVE(&g_bdev_list, bdev, internal.link);
+ free(bdev);
+ }
+ }
+}
+
+static void
+check_and_remove_raid_bdev(struct raid_bdev_config *raid_cfg)
+{
+ struct raid_bdev *raid_bdev;
+
+ /* Get the raid structured allocated if exists */
+ raid_bdev = raid_cfg->raid_bdev;
+ if (raid_bdev == NULL) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < raid_bdev->num_base_bdevs; i++) {
+ assert(raid_bdev->base_bdev_info != NULL);
+ if (raid_bdev->base_bdev_info[i].bdev) {
+ raid_bdev_free_base_bdev_resource(raid_bdev, i);
+ }
+ }
+ assert(raid_bdev->num_base_bdevs_discovered == 0);
+ raid_bdev_cleanup(raid_bdev);
+}
+
+/* Reset globals */
+static void
+reset_globals(void)
+{
+ if (g_io_output) {
+ free(g_io_output);
+ g_io_output = NULL;
+ }
+ rpc_req = NULL;
+ rpc_req_size = 0;
+}
+
+void
+spdk_bdev_io_get_buf(struct spdk_bdev_io *bdev_io, spdk_bdev_io_get_buf_cb cb,
+ uint64_t len)
+{
+ CU_ASSERT(false);
+}
+
+/* Store the IO completion status in global variable to verify by various tests */
+void
+spdk_bdev_io_complete(struct spdk_bdev_io *bdev_io, enum spdk_bdev_io_status status)
+{
+ g_io_comp_status = ((status == SPDK_BDEV_IO_STATUS_SUCCESS) ? true : false);
+}
+
+/* It will cache the split IOs for verification */
+int
+spdk_bdev_writev_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ struct iovec *iov, int iovcnt,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ struct io_output *p = &g_io_output[g_io_output_index];
+ struct spdk_bdev_io *child_io;
+
+ if (g_ignore_io_output) {
+ return 0;
+ }
+
+ if (g_max_io_size < g_strip_size) {
+ SPDK_CU_ASSERT_FATAL(g_io_output_index < 2);
+ } else {
+ SPDK_CU_ASSERT_FATAL(g_io_output_index < (g_max_io_size / g_strip_size) + 1);
+ }
+ if (g_bdev_io_submit_status == 0) {
+ p->desc = desc;
+ p->ch = ch;
+ p->offset_blocks = offset_blocks;
+ p->num_blocks = num_blocks;
+ p->cb = cb;
+ p->cb_arg = cb_arg;
+ p->iotype = SPDK_BDEV_IO_TYPE_WRITE;
+ g_io_output_index++;
+ child_io = calloc(1, sizeof(struct spdk_bdev_io));
+ SPDK_CU_ASSERT_FATAL(child_io != NULL);
+ cb(child_io, g_child_io_status_flag, cb_arg);
+ }
+
+ return g_bdev_io_submit_status;
+}
+
+int
+spdk_bdev_reset(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return 0;
+}
+
+void
+spdk_bdev_unregister(struct spdk_bdev *bdev, spdk_bdev_unregister_cb cb_fn, void *cb_arg)
+{
+ bdev->fn_table->destruct(bdev->ctxt);
+}
+
+int
+spdk_bdev_open(struct spdk_bdev *bdev, bool write, spdk_bdev_remove_cb_t remove_cb,
+ void *remove_ctx, struct spdk_bdev_desc **_desc)
+{
+ *_desc = (void *)0x1;
+ return 0;
+}
+
+void
+spdk_put_io_channel(struct spdk_io_channel *ch)
+{
+ CU_ASSERT(ch == (void *)1);
+}
+
+struct spdk_io_channel *
+spdk_get_io_channel(void *io_device)
+{
+ return NULL;
+}
+
+void
+spdk_poller_unregister(struct spdk_poller **ppoller)
+{
+}
+
+struct spdk_poller *
+spdk_poller_register(spdk_poller_fn fn,
+ void *arg,
+ uint64_t period_microseconds)
+{
+ return (void *)1;
+}
+
+void
+spdk_io_device_unregister(void *io_device, spdk_io_device_unregister_cb unregister_cb)
+{
+}
+
+char *
+spdk_sprintf_alloc(const char *format, ...)
+{
+ return strdup(format);
+}
+
+void
+spdk_io_device_register(void *io_device, spdk_io_channel_create_cb create_cb,
+ spdk_io_channel_destroy_cb destroy_cb, uint32_t ctx_size,
+ const char *name)
+{
+}
+
+int
+spdk_json_write_name(struct spdk_json_write_ctx *w, const char *name)
+{
+ return 0;
+}
+
+int spdk_json_write_named_uint32(struct spdk_json_write_ctx *w, const char *name, uint32_t val)
+{
+ struct rpc_construct_raid_bdev *req = rpc_req;
+ if (strcmp(name, "strip_size") == 0) {
+ CU_ASSERT(req->strip_size * 1024 / g_block_len == val);
+ } else if (strcmp(name, "blocklen_shift") == 0) {
+ CU_ASSERT(spdk_u32log2(g_block_len) == val);
+ } else if (strcmp(name, "raid_level") == 0) {
+ CU_ASSERT(req->raid_level == val);
+ } else if (strcmp(name, "num_base_bdevs") == 0) {
+ CU_ASSERT(req->base_bdevs.num_base_bdevs == val);
+ } else if (strcmp(name, "state") == 0) {
+ CU_ASSERT(val == RAID_BDEV_STATE_ONLINE);
+ } else if (strcmp(name, "destruct_called") == 0) {
+ CU_ASSERT(val == 0);
+ } else if (strcmp(name, "num_base_bdevs_discovered") == 0) {
+ CU_ASSERT(req->base_bdevs.num_base_bdevs == val);
+ }
+ return 0;
+}
+
+int spdk_json_write_named_string(struct spdk_json_write_ctx *w, const char *name, const char *val)
+{
+ return 0;
+}
+
+int
+spdk_json_write_object_begin(struct spdk_json_write_ctx *w)
+{
+ return 0;
+}
+
+int
+spdk_json_write_named_object_begin(struct spdk_json_write_ctx *w, const char *name)
+{
+ return 0;
+}
+
+int
+spdk_json_write_named_array_begin(struct spdk_json_write_ctx *w, const char *name)
+{
+ return 0;
+}
+
+int
+spdk_json_write_array_end(struct spdk_json_write_ctx *w)
+{
+ return 0;
+}
+
+int
+spdk_json_write_object_end(struct spdk_json_write_ctx *w)
+{
+ return 0;
+}
+
+int
+spdk_json_write_bool(struct spdk_json_write_ctx *w, bool val)
+{
+ return 0;
+}
+
+int spdk_json_write_null(struct spdk_json_write_ctx *w)
+{
+ return 0;
+}
+
+struct spdk_io_channel *
+spdk_bdev_get_io_channel(struct spdk_bdev_desc *desc)
+{
+ return (void *)1;
+}
+
+void
+spdk_for_each_thread(spdk_thread_fn fn, void *ctx, spdk_thread_fn cpl)
+{
+ fn(ctx);
+ cpl(ctx);
+}
+
+struct spdk_thread *
+spdk_get_thread(void)
+{
+ return NULL;
+}
+
+void
+spdk_thread_send_msg(const struct spdk_thread *thread, spdk_thread_fn fn, void *ctx)
+{
+ fn(ctx);
+}
+
+uint32_t
+spdk_env_get_current_core(void)
+{
+ return 0;
+}
+
+void
+spdk_bdev_free_io(struct spdk_bdev_io *bdev_io)
+{
+ if (bdev_io) {
+ free(bdev_io);
+ }
+}
+
+/* It will cache split IOs for verification */
+int
+spdk_bdev_readv_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ struct iovec *iov, int iovcnt,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ struct io_output *p = &g_io_output[g_io_output_index];
+ struct spdk_bdev_io *child_io;
+
+ if (g_ignore_io_output) {
+ return 0;
+ }
+
+ SPDK_CU_ASSERT_FATAL(g_io_output_index <= (g_max_io_size / g_strip_size) + 1);
+ if (g_bdev_io_submit_status == 0) {
+ p->desc = desc;
+ p->ch = ch;
+ p->offset_blocks = offset_blocks;
+ p->num_blocks = num_blocks;
+ p->cb = cb;
+ p->cb_arg = cb_arg;
+ p->iotype = SPDK_BDEV_IO_TYPE_READ;
+ g_io_output_index++;
+ child_io = calloc(1, sizeof(struct spdk_bdev_io));
+ SPDK_CU_ASSERT_FATAL(child_io != NULL);
+ cb(child_io, g_child_io_status_flag, cb_arg);
+ }
+
+ return g_bdev_io_submit_status;
+}
+
+void
+spdk_bdev_module_release_bdev(struct spdk_bdev *bdev)
+{
+ CU_ASSERT(bdev->internal.claim_module != NULL);
+ bdev->internal.claim_module = NULL;
+}
+
+void
+spdk_bdev_module_examine_done(struct spdk_bdev_module *module)
+{
+}
+
+struct spdk_conf_section *
+spdk_conf_first_section(struct spdk_conf *cp)
+{
+ if (g_config_level_create) {
+ return (void *) 0x1;
+ }
+
+ return NULL;
+}
+
+bool
+spdk_conf_section_match_prefix(const struct spdk_conf_section *sp, const char *name_prefix)
+{
+ if (g_config_level_create) {
+ return true;
+ }
+
+ return false;
+}
+
+char *
+spdk_conf_section_get_val(struct spdk_conf_section *sp, const char *key)
+{
+ struct rpc_construct_raid_bdev *req = rpc_req;
+
+ if (g_config_level_create) {
+ if (strcmp(key, "Name") == 0) {
+ return req->name;
+ }
+ }
+
+ return NULL;
+}
+
+int
+spdk_conf_section_get_intval(struct spdk_conf_section *sp, const char *key)
+{
+ struct rpc_construct_raid_bdev *req = rpc_req;
+
+ if (g_config_level_create) {
+ if (strcmp(key, "StripSize") == 0) {
+ return req->strip_size;
+ } else if (strcmp(key, "NumDevices") == 0) {
+ return req->base_bdevs.num_base_bdevs;
+ } else if (strcmp(key, "RaidLevel") == 0) {
+ return req->raid_level;
+ }
+ }
+
+ return 0;
+}
+
+struct spdk_conf_section *
+spdk_conf_next_section(struct spdk_conf_section *sp)
+{
+ return NULL;
+}
+
+char *
+spdk_conf_section_get_nmval(struct spdk_conf_section *sp, const char *key, int idx1, int idx2)
+{
+ struct rpc_construct_raid_bdev *req = rpc_req;
+
+ if (g_config_level_create) {
+ if (strcmp(key, "Devices") == 0) {
+ if (idx2 >= g_max_base_drives) {
+ return NULL;
+ }
+ return req->base_bdevs.base_bdevs[idx2];
+ }
+ }
+
+ return NULL;
+}
+
+void
+spdk_bdev_close(struct spdk_bdev_desc *desc)
+{
+}
+
+int
+spdk_bdev_module_claim_bdev(struct spdk_bdev *bdev, struct spdk_bdev_desc *desc,
+ struct spdk_bdev_module *module)
+{
+ if (bdev->internal.claim_module != NULL) {
+ return -1;
+ }
+ bdev->internal.claim_module = module;
+ return 0;
+}
+
+int
+spdk_bdev_register(struct spdk_bdev *bdev)
+{
+ return 0;
+}
+
+uint32_t
+spdk_env_get_last_core(void)
+{
+ return 0;
+}
+
+int
+spdk_json_decode_string(const struct spdk_json_val *val, void *out)
+{
+ return 0;
+}
+
+int
+spdk_json_decode_object(const struct spdk_json_val *values,
+ const struct spdk_json_object_decoder *decoders, size_t num_decoders, void *out)
+{
+ struct rpc_construct_raid_bdev *req, *_out;
+ size_t i;
+
+ if (g_json_decode_obj_err) {
+ return -1;
+ } else if (g_json_decode_obj_construct) {
+ req = rpc_req;
+ _out = out;
+
+ _out->name = strdup(req->name);
+ SPDK_CU_ASSERT_FATAL(_out->name != NULL);
+ _out->strip_size = req->strip_size;
+ _out->raid_level = req->raid_level;
+ _out->base_bdevs.num_base_bdevs = req->base_bdevs.num_base_bdevs;
+ for (i = 0; i < req->base_bdevs.num_base_bdevs; i++) {
+ _out->base_bdevs.base_bdevs[i] = strdup(req->base_bdevs.base_bdevs[i]);
+ SPDK_CU_ASSERT_FATAL(_out->base_bdevs.base_bdevs[i]);
+ }
+ } else {
+ memcpy(out, rpc_req, rpc_req_size);
+ }
+
+ return 0;
+}
+
+struct spdk_json_write_ctx *
+spdk_jsonrpc_begin_result(struct spdk_jsonrpc_request *request)
+{
+ if (g_json_beg_res_ret_err) {
+ return NULL;
+ } else {
+ return (void *)1;
+ }
+}
+
+int
+spdk_json_write_array_begin(struct spdk_json_write_ctx *w)
+{
+ return 0;
+}
+
+int
+spdk_json_write_string(struct spdk_json_write_ctx *w, const char *val)
+{
+ if (g_test_multi_raids) {
+ g_get_raids_output[g_get_raids_count] = strdup(val);
+ SPDK_CU_ASSERT_FATAL(g_get_raids_output[g_get_raids_count] != NULL);
+ g_get_raids_count++;
+ }
+
+ return 0;
+}
+
+void
+spdk_jsonrpc_send_error_response(struct spdk_jsonrpc_request *request,
+ int error_code, const char *msg)
+{
+ g_rpc_err = 1;
+}
+
+void
+spdk_jsonrpc_send_error_response_fmt(struct spdk_jsonrpc_request *request,
+ int error_code, const char *fmt, ...)
+{
+ g_rpc_err = 1;
+}
+
+void
+spdk_jsonrpc_end_result(struct spdk_jsonrpc_request *request, struct spdk_json_write_ctx *w)
+{
+}
+
+struct spdk_bdev *
+spdk_bdev_get_by_name(const char *bdev_name)
+{
+ struct spdk_bdev *bdev;
+
+ if (!TAILQ_EMPTY(&g_bdev_list)) {
+ TAILQ_FOREACH(bdev, &g_bdev_list, internal.link) {
+ if (strcmp(bdev_name, bdev->name) == 0) {
+ return bdev;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+const char *
+spdk_strerror(int errnum)
+{
+ return NULL;
+}
+
+int
+spdk_json_decode_array(const struct spdk_json_val *values, spdk_json_decode_fn decode_func,
+ void *out, size_t max_size, size_t *out_size, size_t stride)
+{
+ return 0;
+}
+
+void
+spdk_rpc_register_method(const char *method, spdk_rpc_method_handler func, uint32_t state_mask)
+{
+}
+
+int
+spdk_json_decode_uint32(const struct spdk_json_val *val, void *out)
+{
+ return 0;
+}
+
+
+void
+spdk_bdev_module_list_add(struct spdk_bdev_module *bdev_module)
+{
+}
+
+static void
+bdev_io_cleanup(struct spdk_bdev_io *bdev_io)
+{
+ if (bdev_io->u.bdev.iovs) {
+ if (bdev_io->u.bdev.iovs->iov_base) {
+ free(bdev_io->u.bdev.iovs->iov_base);
+ bdev_io->u.bdev.iovs->iov_base = NULL;
+ }
+ free(bdev_io->u.bdev.iovs);
+ bdev_io->u.bdev.iovs = NULL;
+ }
+}
+
+static void
+bdev_io_initialize(struct spdk_bdev_io *bdev_io, struct spdk_bdev *bdev,
+ uint64_t lba, uint64_t blocks, int16_t iotype)
+{
+ bdev_io->bdev = bdev;
+ bdev_io->u.bdev.offset_blocks = lba;
+ bdev_io->u.bdev.num_blocks = blocks;
+ bdev_io->type = iotype;
+ bdev_io->u.bdev.iovcnt = 1;
+ bdev_io->u.bdev.iovs = calloc(1, sizeof(struct iovec));
+ SPDK_CU_ASSERT_FATAL(bdev_io->u.bdev.iovs != NULL);
+ bdev_io->u.bdev.iovs->iov_base = calloc(1, bdev_io->u.bdev.num_blocks * g_block_len);
+ SPDK_CU_ASSERT_FATAL(bdev_io->u.bdev.iovs->iov_base != NULL);
+ bdev_io->u.bdev.iovs->iov_len = bdev_io->u.bdev.num_blocks * g_block_len;
+ bdev_io->u.bdev.iovs = bdev_io->u.bdev.iovs;
+}
+
+static void
+verify_io(struct spdk_bdev_io *bdev_io, uint8_t num_base_drives,
+ struct raid_bdev_io_channel *ch_ctx, struct raid_bdev *raid_bdev, uint32_t io_status)
+{
+ uint32_t strip_shift = spdk_u32log2(g_strip_size);
+ uint64_t start_strip = bdev_io->u.bdev.offset_blocks >> strip_shift;
+ uint64_t end_strip = (bdev_io->u.bdev.offset_blocks + bdev_io->u.bdev.num_blocks - 1) >>
+ strip_shift;
+ uint32_t splits_reqd = (end_strip - start_strip + 1);
+ uint32_t strip;
+ uint64_t pd_strip;
+ uint64_t pd_idx;
+ uint32_t offset_in_strip;
+ uint64_t pd_lba;
+ uint64_t pd_blocks;
+ uint32_t index = 0;
+ uint8_t *buf = bdev_io->u.bdev.iovs->iov_base;
+
+ if (io_status == INVALID_IO_SUBMIT) {
+ CU_ASSERT(g_io_comp_status == false);
+ return;
+ }
+ SPDK_CU_ASSERT_FATAL(raid_bdev != NULL);
+ SPDK_CU_ASSERT_FATAL(num_base_drives != 0);
+
+ CU_ASSERT(splits_reqd == g_io_output_index);
+ for (strip = start_strip; strip <= end_strip; strip++, index++) {
+ pd_strip = strip / num_base_drives;
+ pd_idx = strip % num_base_drives;
+ if (strip == start_strip) {
+ offset_in_strip = bdev_io->u.bdev.offset_blocks & (g_strip_size - 1);
+ pd_lba = (pd_strip << strip_shift) + offset_in_strip;
+ if (strip == end_strip) {
+ pd_blocks = bdev_io->u.bdev.num_blocks;
+ } else {
+ pd_blocks = g_strip_size - offset_in_strip;
+ }
+ } else if (strip == end_strip) {
+ pd_lba = pd_strip << strip_shift;
+ pd_blocks = ((bdev_io->u.bdev.offset_blocks + bdev_io->u.bdev.num_blocks - 1) &
+ (g_strip_size - 1)) + 1;
+ } else {
+ pd_lba = pd_strip << raid_bdev->strip_size_shift;
+ pd_blocks = raid_bdev->strip_size;
+ }
+ CU_ASSERT(pd_lba == g_io_output[index].offset_blocks);
+ CU_ASSERT(pd_blocks == g_io_output[index].num_blocks);
+ CU_ASSERT(ch_ctx->base_channel[pd_idx] == g_io_output[index].ch);
+ CU_ASSERT(raid_bdev->base_bdev_info[pd_idx].desc == g_io_output[index].desc);
+ CU_ASSERT(bdev_io->type == g_io_output[index].iotype);
+ buf += (pd_blocks << spdk_u32log2(g_block_len));
+ }
+ CU_ASSERT(g_io_comp_status == io_status);
+}
+
+static void
+verify_raid_config_present(const char *name, bool presence)
+{
+ struct raid_bdev_config *raid_cfg;
+ bool cfg_found;
+
+ cfg_found = false;
+
+ TAILQ_FOREACH(raid_cfg, &g_spdk_raid_config.raid_bdev_config_head, link) {
+ if (raid_cfg->name != NULL) {
+ if (strcmp(name, raid_cfg->name) == 0) {
+ cfg_found = true;
+ break;
+ }
+ }
+ }
+
+ if (presence == true) {
+ CU_ASSERT(cfg_found == true);
+ } else {
+ CU_ASSERT(cfg_found == false);
+ }
+}
+
+static void
+verify_raid_bdev_present(const char *name, bool presence)
+{
+ struct raid_bdev *pbdev;
+ bool pbdev_found;
+
+ pbdev_found = false;
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) {
+ if (strcmp(pbdev->bdev.name, name) == 0) {
+ pbdev_found = true;
+ break;
+ }
+ }
+ if (presence == true) {
+ CU_ASSERT(pbdev_found == true);
+ } else {
+ CU_ASSERT(pbdev_found == false);
+ }
+}
+static void
+verify_raid_config(struct rpc_construct_raid_bdev *r, bool presence)
+{
+ struct raid_bdev_config *raid_cfg = NULL;
+ uint32_t i;
+ int val;
+
+ TAILQ_FOREACH(raid_cfg, &g_spdk_raid_config.raid_bdev_config_head, link) {
+ if (strcmp(r->name, raid_cfg->name) == 0) {
+ if (presence == false) {
+ break;
+ }
+ CU_ASSERT(raid_cfg->raid_bdev != NULL);
+ CU_ASSERT(raid_cfg->strip_size == r->strip_size);
+ CU_ASSERT(raid_cfg->num_base_bdevs == r->base_bdevs.num_base_bdevs);
+ CU_ASSERT(raid_cfg->raid_level == r->raid_level);
+ if (raid_cfg->base_bdev != NULL) {
+ for (i = 0; i < raid_cfg->num_base_bdevs; i++) {
+ val = strcmp(raid_cfg->base_bdev[i].name, r->base_bdevs.base_bdevs[i]);
+ CU_ASSERT(val == 0);
+ }
+ }
+ break;
+ }
+ }
+
+ if (presence == true) {
+ CU_ASSERT(raid_cfg != NULL);
+ } else {
+ CU_ASSERT(raid_cfg == NULL);
+ }
+}
+
+static void
+verify_raid_bdev(struct rpc_construct_raid_bdev *r, bool presence, uint32_t raid_state)
+{
+ struct raid_bdev *pbdev;
+ uint32_t i;
+ struct spdk_bdev *bdev = NULL;
+ bool pbdev_found;
+ uint64_t min_blockcnt = 0xFFFFFFFFFFFFFFFF;
+
+ pbdev_found = false;
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) {
+ if (strcmp(pbdev->bdev.name, r->name) == 0) {
+ pbdev_found = true;
+ if (presence == false) {
+ break;
+ }
+ CU_ASSERT(pbdev->config->raid_bdev == pbdev);
+ CU_ASSERT(pbdev->base_bdev_info != NULL);
+ CU_ASSERT(pbdev->strip_size == ((r->strip_size * 1024) / g_block_len));
+ CU_ASSERT(pbdev->strip_size_shift == spdk_u32log2(((r->strip_size * 1024) / g_block_len)));
+ CU_ASSERT(pbdev->blocklen_shift == spdk_u32log2(g_block_len));
+ CU_ASSERT(pbdev->state == raid_state);
+ CU_ASSERT(pbdev->num_base_bdevs == r->base_bdevs.num_base_bdevs);
+ CU_ASSERT(pbdev->num_base_bdevs_discovered == r->base_bdevs.num_base_bdevs);
+ CU_ASSERT(pbdev->raid_level == r->raid_level);
+ CU_ASSERT(pbdev->destruct_called == false);
+ for (i = 0; i < pbdev->num_base_bdevs; i++) {
+ if (pbdev->base_bdev_info && pbdev->base_bdev_info[i].bdev) {
+ bdev = spdk_bdev_get_by_name(pbdev->base_bdev_info[i].bdev->name);
+ CU_ASSERT(bdev != NULL);
+ CU_ASSERT(pbdev->base_bdev_info[i].remove_scheduled == false);
+ } else {
+ CU_ASSERT(0);
+ }
+
+ if (bdev && bdev->blockcnt < min_blockcnt) {
+ min_blockcnt = bdev->blockcnt;
+ }
+ }
+ CU_ASSERT((((min_blockcnt / (r->strip_size * 1024 / g_block_len)) * (r->strip_size * 1024 /
+ g_block_len)) * r->base_bdevs.num_base_bdevs) == pbdev->bdev.blockcnt);
+ CU_ASSERT(strcmp(pbdev->bdev.product_name, "Pooled Device") == 0);
+ CU_ASSERT(pbdev->bdev.write_cache == 0);
+ CU_ASSERT(pbdev->bdev.blocklen == g_block_len);
+ if (pbdev->num_base_bdevs > 1) {
+ CU_ASSERT(pbdev->bdev.optimal_io_boundary == pbdev->strip_size);
+ CU_ASSERT(pbdev->bdev.split_on_optimal_io_boundary == true);
+ } else {
+ CU_ASSERT(pbdev->bdev.optimal_io_boundary == 0);
+ CU_ASSERT(pbdev->bdev.split_on_optimal_io_boundary == false);
+ }
+ CU_ASSERT(pbdev->bdev.ctxt == pbdev);
+ CU_ASSERT(pbdev->bdev.fn_table == &g_raid_bdev_fn_table);
+ CU_ASSERT(pbdev->bdev.module == &g_raid_if);
+ break;
+ }
+ }
+ if (presence == true) {
+ CU_ASSERT(pbdev_found == true);
+ } else {
+ CU_ASSERT(pbdev_found == false);
+ }
+ pbdev_found = false;
+ if (raid_state == RAID_BDEV_STATE_ONLINE) {
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_configured_list, state_link) {
+ if (strcmp(pbdev->bdev.name, r->name) == 0) {
+ pbdev_found = true;
+ break;
+ }
+ }
+ } else if (raid_state == RAID_BDEV_STATE_CONFIGURING) {
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_configuring_list, state_link) {
+ if (strcmp(pbdev->bdev.name, r->name) == 0) {
+ pbdev_found = true;
+ break;
+ }
+ }
+ } else if (raid_state == RAID_BDEV_STATE_OFFLINE) {
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_offline_list, state_link) {
+ if (strcmp(pbdev->bdev.name, r->name) == 0) {
+ pbdev_found = true;
+ break;
+ }
+ }
+ }
+ if (presence == true) {
+ CU_ASSERT(pbdev_found == true);
+ } else {
+ CU_ASSERT(pbdev_found == false);
+ }
+}
+
+int
+spdk_bdev_queue_io_wait(struct spdk_bdev *bdev, struct spdk_io_channel *ch,
+ struct spdk_bdev_io_wait_entry *entry)
+{
+ CU_ASSERT(bdev == entry->bdev);
+ CU_ASSERT(entry->cb_fn != NULL);
+ CU_ASSERT(entry->cb_arg != NULL);
+ TAILQ_INSERT_TAIL(&g_io_waitq, entry, link);
+ return 0;
+}
+
+
+static uint32_t
+get_num_elts_in_waitq(void)
+{
+ struct spdk_bdev_io_wait_entry *ele;
+ uint32_t count = 0;
+
+ TAILQ_FOREACH(ele, &g_io_waitq, link) {
+ count++;
+ }
+
+ return count;
+}
+
+static void
+process_io_waitq(void)
+{
+ struct spdk_bdev_io_wait_entry *ele;
+ struct spdk_bdev_io_wait_entry *next_ele;
+
+ TAILQ_FOREACH_SAFE(ele, &g_io_waitq, link, next_ele) {
+ TAILQ_REMOVE(&g_io_waitq, ele, link);
+ ele->cb_fn(ele->cb_arg);
+ }
+}
+
+static void
+verify_get_raids(struct rpc_construct_raid_bdev *construct_req,
+ uint8_t g_max_raids,
+ char **g_get_raids_output, uint32_t g_get_raids_count)
+{
+ uint32_t i, j;
+ bool found;
+
+ CU_ASSERT(g_max_raids == g_get_raids_count);
+ if (g_max_raids == g_get_raids_count) {
+ for (i = 0; i < g_max_raids; i++) {
+ found = false;
+ for (j = 0; j < g_max_raids; j++) {
+ if (construct_req[i].name && strcmp(construct_req[i].name, g_get_raids_output[i]) == 0) {
+ found = true;
+ break;
+ }
+ }
+ CU_ASSERT(found == true);
+ }
+ }
+}
+
+static void
+create_base_bdevs(uint32_t bbdev_start_idx)
+{
+ uint32_t i;
+ struct spdk_bdev *base_bdev;
+ char name[16];
+ uint16_t num_chars;
+
+ for (i = 0; i < g_max_base_drives; i++, bbdev_start_idx++) {
+ num_chars = snprintf(name, 16, "%s%u%s", "Nvme", bbdev_start_idx, "n1");
+ name[num_chars] = '\0';
+ base_bdev = calloc(1, sizeof(struct spdk_bdev));
+ SPDK_CU_ASSERT_FATAL(base_bdev != NULL);
+ base_bdev->name = strdup(name);
+ SPDK_CU_ASSERT_FATAL(base_bdev->name != NULL);
+ base_bdev->blocklen = g_block_len;
+ base_bdev->blockcnt = (uint64_t)1024 * 1024 * 1024 * 1024;
+ TAILQ_INSERT_TAIL(&g_bdev_list, base_bdev, internal.link);
+ }
+}
+
+static void
+create_test_req(struct rpc_construct_raid_bdev *r, const char *raid_name, uint32_t bbdev_start_idx,
+ bool create_base_bdev)
+{
+ uint32_t i;
+ char name[16];
+ uint16_t num_chars;
+ uint32_t bbdev_idx = bbdev_start_idx;
+
+ r->name = strdup(raid_name);
+ SPDK_CU_ASSERT_FATAL(r->name != NULL);
+ r->strip_size = (g_strip_size * g_block_len) / 1024;
+ r->raid_level = 0;
+ r->base_bdevs.num_base_bdevs = g_max_base_drives;
+ for (i = 0; i < g_max_base_drives; i++, bbdev_idx++) {
+ num_chars = snprintf(name, 16, "%s%u%s", "Nvme", bbdev_idx, "n1");
+ name[num_chars] = '\0';
+ r->base_bdevs.base_bdevs[i] = strdup(name);
+ SPDK_CU_ASSERT_FATAL(r->base_bdevs.base_bdevs[i] != NULL);
+ }
+ if (create_base_bdev == true) {
+ create_base_bdevs(bbdev_start_idx);
+ }
+}
+
+static void
+free_test_req(struct rpc_construct_raid_bdev *r)
+{
+ uint8_t i;
+
+ free(r->name);
+ for (i = 0; i < r->base_bdevs.num_base_bdevs; i++) {
+ free(r->base_bdevs.base_bdevs[i]);
+ }
+}
+
+static void
+test_construct_raid(void)
+{
+ struct rpc_construct_raid_bdev req;
+ struct rpc_destroy_raid_bdev destroy_req;
+
+ set_globals();
+ create_test_req(&req, "raid1", 0, true);
+ rpc_req = &req;
+ rpc_req_size = sizeof(req);
+ CU_ASSERT(raid_bdev_init() == 0);
+
+ verify_raid_config_present(req.name, false);
+ verify_raid_bdev_present(req.name, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&req, true);
+ verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE);
+ free_test_req(&req);
+
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+static void
+test_destroy_raid(void)
+{
+ struct rpc_construct_raid_bdev construct_req;
+ struct rpc_destroy_raid_bdev destroy_req;
+
+ set_globals();
+ create_test_req(&construct_req, "raid1", 0, true);
+ rpc_req = &construct_req;
+ rpc_req_size = sizeof(construct_req);
+ CU_ASSERT(raid_bdev_init() == 0);
+ verify_raid_config_present(construct_req.name, false);
+ verify_raid_bdev_present(construct_req.name, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&construct_req, true);
+ verify_raid_bdev(&construct_req, true, RAID_BDEV_STATE_ONLINE);
+ free_test_req(&construct_req);
+
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+static void
+test_construct_raid_invalid_args(void)
+{
+ struct rpc_construct_raid_bdev req;
+ struct rpc_destroy_raid_bdev destroy_req;
+ struct raid_bdev_config *raid_cfg;
+
+ set_globals();
+ rpc_req = &req;
+ rpc_req_size = sizeof(req);
+ CU_ASSERT(raid_bdev_init() == 0);
+
+ create_test_req(&req, "raid1", 0, true);
+ verify_raid_config_present(req.name, false);
+ verify_raid_bdev_present(req.name, false);
+ req.raid_level = 1;
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 1);
+ free_test_req(&req);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ create_test_req(&req, "raid1", 0, false);
+ verify_raid_config_present(req.name, false);
+ verify_raid_bdev_present(req.name, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_err = 1;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 1);
+ g_json_decode_obj_err = 0;
+ free_test_req(&req);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ create_test_req(&req, "raid1", 0, false);
+ req.strip_size = 1231;
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 1);
+ free_test_req(&req);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ create_test_req(&req, "raid1", 0, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&req, true);
+ verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE);
+ free_test_req(&req);
+
+ create_test_req(&req, "raid1", 0, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 1);
+ free_test_req(&req);
+
+ create_test_req(&req, "raid2", 0, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 1);
+ free_test_req(&req);
+ verify_raid_config_present("raid2", false);
+ verify_raid_bdev_present("raid2", false);
+
+ create_test_req(&req, "raid2", g_max_base_drives, true);
+ free(req.base_bdevs.base_bdevs[g_max_base_drives - 1]);
+ req.base_bdevs.base_bdevs[g_max_base_drives - 1] = strdup("Nvme0n1");
+ SPDK_CU_ASSERT_FATAL(req.base_bdevs.base_bdevs[g_max_base_drives - 1] != NULL);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 1);
+ free_test_req(&req);
+ verify_raid_config_present("raid2", false);
+ verify_raid_bdev_present("raid2", false);
+
+ create_test_req(&req, "raid2", g_max_base_drives, true);
+ free(req.base_bdevs.base_bdevs[g_max_base_drives - 1]);
+ req.base_bdevs.base_bdevs[g_max_base_drives - 1] = strdup("Nvme100000n1");
+ SPDK_CU_ASSERT_FATAL(req.base_bdevs.base_bdevs[g_max_base_drives - 1] != NULL);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ free_test_req(&req);
+ verify_raid_config_present("raid2", true);
+ verify_raid_bdev_present("raid2", true);
+ raid_cfg = raid_bdev_config_find_by_name("raid2");
+ SPDK_CU_ASSERT_FATAL(raid_cfg != NULL);
+ check_and_remove_raid_bdev(raid_cfg);
+ raid_bdev_config_cleanup(raid_cfg);
+
+ create_test_req(&req, "raid2", g_max_base_drives, false);
+ g_rpc_err = 0;
+ g_json_beg_res_ret_err = 1;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ free_test_req(&req);
+ verify_raid_config_present("raid2", true);
+ verify_raid_bdev_present("raid2", true);
+ verify_raid_config_present("raid1", true);
+ verify_raid_bdev_present("raid1", true);
+ g_json_beg_res_ret_err = 0;
+
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ destroy_req.name = strdup("raid2");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+static void
+test_destroy_raid_invalid_args(void)
+{
+ struct rpc_construct_raid_bdev construct_req;
+ struct rpc_destroy_raid_bdev destroy_req;
+
+ set_globals();
+ create_test_req(&construct_req, "raid1", 0, true);
+ rpc_req = &construct_req;
+ rpc_req_size = sizeof(construct_req);
+ CU_ASSERT(raid_bdev_init() == 0);
+ verify_raid_config_present(construct_req.name, false);
+ verify_raid_bdev_present(construct_req.name, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&construct_req, true);
+ verify_raid_bdev(&construct_req, true, RAID_BDEV_STATE_ONLINE);
+ free_test_req(&construct_req);
+
+ destroy_req.name = strdup("raid2");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 1);
+
+ destroy_req.name = strdup("raid1");
+ g_rpc_err = 0;
+ g_json_decode_obj_err = 1;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 1);
+ g_json_decode_obj_err = 0;
+ g_rpc_err = 0;
+ free(destroy_req.name);
+ verify_raid_config_present("raid1", true);
+ verify_raid_bdev_present("raid1", true);
+
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+static void
+test_io_channel(void)
+{
+ struct rpc_construct_raid_bdev req;
+ struct rpc_destroy_raid_bdev destroy_req;
+ struct raid_bdev *pbdev;
+ struct raid_bdev_io_channel *ch_ctx;
+ uint32_t i;
+
+ set_globals();
+ create_test_req(&req, "raid1", 0, true);
+ rpc_req = &req;
+ rpc_req_size = sizeof(req);
+ CU_ASSERT(raid_bdev_init() == 0);
+
+ verify_raid_config_present(req.name, false);
+ verify_raid_bdev_present(req.name, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&req, true);
+ verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE);
+
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) {
+ if (strcmp(pbdev->bdev.name, req.name) == 0) {
+ break;
+ }
+ }
+ CU_ASSERT(pbdev != NULL);
+ ch_ctx = calloc(1, sizeof(struct raid_bdev_io_channel));
+ SPDK_CU_ASSERT_FATAL(ch_ctx != NULL);
+
+ CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0);
+ for (i = 0; i < req.base_bdevs.num_base_bdevs; i++) {
+ CU_ASSERT(ch_ctx->base_channel && ch_ctx->base_channel[i] == (void *)0x1);
+ }
+ raid_bdev_destroy_cb(pbdev, ch_ctx);
+ CU_ASSERT(ch_ctx->base_channel == NULL);
+ free_test_req(&req);
+
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ free(ch_ctx);
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+static void
+test_write_io(void)
+{
+ struct rpc_construct_raid_bdev req;
+ struct rpc_destroy_raid_bdev destroy_req;
+ struct raid_bdev *pbdev;
+ struct spdk_io_channel *ch;
+ struct raid_bdev_io_channel *ch_ctx;
+ uint32_t i;
+ struct spdk_bdev_io *bdev_io;
+ uint32_t count;
+ uint64_t io_len;
+ uint64_t lba;
+
+ set_globals();
+ create_test_req(&req, "raid1", 0, true);
+ rpc_req = &req;
+ rpc_req_size = sizeof(req);
+ CU_ASSERT(raid_bdev_init() == 0);
+ verify_raid_config_present(req.name, false);
+ verify_raid_bdev_present(req.name, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&req, true);
+ verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE);
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) {
+ if (strcmp(pbdev->bdev.name, req.name) == 0) {
+ break;
+ }
+ }
+ CU_ASSERT(pbdev != NULL);
+ ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct raid_bdev_io_channel));
+ SPDK_CU_ASSERT_FATAL(ch != NULL);
+ ch_ctx = spdk_io_channel_get_ctx(ch);
+ SPDK_CU_ASSERT_FATAL(ch_ctx != NULL);
+
+ CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0);
+ for (i = 0; i < req.base_bdevs.num_base_bdevs; i++) {
+ CU_ASSERT(ch_ctx->base_channel && ch_ctx->base_channel[i] == (void *)0x1);
+ }
+
+ lba = 0;
+ for (count = 0; count < g_max_qd; count++) {
+ bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io));
+ SPDK_CU_ASSERT_FATAL(bdev_io != NULL);
+ io_len = (rand() % g_strip_size) + 1;
+ bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, SPDK_BDEV_IO_TYPE_WRITE);
+ lba += g_strip_size;
+ memset(g_io_output, 0, (g_max_io_size / g_strip_size) + 1 * sizeof(struct io_output));
+ g_io_output_index = 0;
+ raid_bdev_submit_request(ch, bdev_io);
+ verify_io(bdev_io, req.base_bdevs.num_base_bdevs, ch_ctx, pbdev,
+ g_child_io_status_flag);
+ bdev_io_cleanup(bdev_io);
+ free(bdev_io);
+ }
+ free_test_req(&req);
+
+ raid_bdev_destroy_cb(pbdev, ch_ctx);
+ CU_ASSERT(ch_ctx->base_channel == NULL);
+ free(ch);
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+static void
+test_read_io(void)
+{
+ struct rpc_construct_raid_bdev req;
+ struct rpc_destroy_raid_bdev destroy_req;
+ struct raid_bdev *pbdev;
+ struct spdk_io_channel *ch;
+ struct raid_bdev_io_channel *ch_ctx;
+ uint32_t i;
+ struct spdk_bdev_io *bdev_io;
+ uint32_t count;
+ uint64_t io_len;
+ uint64_t lba;
+
+ set_globals();
+ create_test_req(&req, "raid1", 0, true);
+ rpc_req = &req;
+ rpc_req_size = sizeof(req);
+ CU_ASSERT(raid_bdev_init() == 0);
+ verify_raid_config_present(req.name, false);
+ verify_raid_bdev_present(req.name, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&req, true);
+ verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE);
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) {
+ if (strcmp(pbdev->bdev.name, req.name) == 0) {
+ break;
+ }
+ }
+ CU_ASSERT(pbdev != NULL);
+ ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct raid_bdev_io_channel));
+ SPDK_CU_ASSERT_FATAL(ch != NULL);
+ ch_ctx = spdk_io_channel_get_ctx(ch);
+ SPDK_CU_ASSERT_FATAL(ch_ctx != NULL);
+
+ CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0);
+ for (i = 0; i < req.base_bdevs.num_base_bdevs; i++) {
+ CU_ASSERT(ch_ctx->base_channel && ch_ctx->base_channel[i] == (void *)0x1);
+ }
+ free_test_req(&req);
+
+ lba = 0;
+ for (count = 0; count < g_max_qd; count++) {
+ bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io));
+ SPDK_CU_ASSERT_FATAL(bdev_io != NULL);
+ io_len = (rand() % g_strip_size) + 1;
+ bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, SPDK_BDEV_IO_TYPE_READ);
+ lba += g_strip_size;
+ memset(g_io_output, 0, (g_max_io_size / g_strip_size) + 1 * sizeof(struct io_output));
+ g_io_output_index = 0;
+ raid_bdev_submit_request(ch, bdev_io);
+ verify_io(bdev_io, req.base_bdevs.num_base_bdevs, ch_ctx, pbdev,
+ g_child_io_status_flag);
+ bdev_io_cleanup(bdev_io);
+ free(bdev_io);
+ }
+
+ raid_bdev_destroy_cb(pbdev, ch_ctx);
+ CU_ASSERT(ch_ctx->base_channel == NULL);
+ free(ch);
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+/* Test IO failures */
+static void
+test_io_failure(void)
+{
+ struct rpc_construct_raid_bdev req;
+ struct rpc_destroy_raid_bdev destroy_req;
+ struct raid_bdev *pbdev;
+ struct spdk_io_channel *ch;
+ struct raid_bdev_io_channel *ch_ctx;
+ uint32_t i;
+ struct spdk_bdev_io *bdev_io;
+ uint32_t count;
+ uint64_t io_len;
+ uint64_t lba;
+
+ set_globals();
+ create_test_req(&req, "raid1", 0, true);
+ rpc_req = &req;
+ rpc_req_size = sizeof(req);
+ CU_ASSERT(raid_bdev_init() == 0);
+ verify_raid_config_present(req.name, false);
+ verify_raid_bdev_present(req.name, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&req, true);
+ verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE);
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) {
+ if (strcmp(pbdev->bdev.name, req.name) == 0) {
+ break;
+ }
+ }
+ CU_ASSERT(pbdev != NULL);
+ ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct raid_bdev_io_channel));
+ SPDK_CU_ASSERT_FATAL(ch != NULL);
+ ch_ctx = spdk_io_channel_get_ctx(ch);
+ SPDK_CU_ASSERT_FATAL(ch_ctx != NULL);
+
+ CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0);
+ for (i = 0; i < req.base_bdevs.num_base_bdevs; i++) {
+ CU_ASSERT(ch_ctx->base_channel && ch_ctx->base_channel[i] == (void *)0x1);
+ }
+ free_test_req(&req);
+
+ lba = 0;
+ for (count = 0; count < 1; count++) {
+ bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io));
+ SPDK_CU_ASSERT_FATAL(bdev_io != NULL);
+ io_len = (rand() % g_strip_size) + 1;
+ bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, SPDK_BDEV_IO_TYPE_INVALID);
+ lba += g_strip_size;
+ memset(g_io_output, 0, (g_max_io_size / g_strip_size) + 1 * sizeof(struct io_output));
+ g_io_output_index = 0;
+ raid_bdev_submit_request(ch, bdev_io);
+ verify_io(bdev_io, req.base_bdevs.num_base_bdevs, ch_ctx, pbdev,
+ INVALID_IO_SUBMIT);
+ bdev_io_cleanup(bdev_io);
+ free(bdev_io);
+ }
+
+
+ lba = 0;
+ g_child_io_status_flag = false;
+ for (count = 0; count < 1; count++) {
+ bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io));
+ SPDK_CU_ASSERT_FATAL(bdev_io != NULL);
+ io_len = (rand() % g_strip_size) + 1;
+ bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, SPDK_BDEV_IO_TYPE_WRITE);
+ lba += g_strip_size;
+ memset(g_io_output, 0, (g_max_io_size / g_strip_size) + 1 * sizeof(struct io_output));
+ g_io_output_index = 0;
+ raid_bdev_submit_request(ch, bdev_io);
+ verify_io(bdev_io, req.base_bdevs.num_base_bdevs, ch_ctx, pbdev,
+ g_child_io_status_flag);
+ bdev_io_cleanup(bdev_io);
+ free(bdev_io);
+ }
+
+ raid_bdev_destroy_cb(pbdev, ch_ctx);
+ CU_ASSERT(ch_ctx->base_channel == NULL);
+ free(ch);
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+/* Test waitq logic */
+static void
+test_io_waitq(void)
+{
+ struct rpc_construct_raid_bdev req;
+ struct rpc_destroy_raid_bdev destroy_req;
+ struct raid_bdev *pbdev;
+ struct spdk_io_channel *ch;
+ struct raid_bdev_io_channel *ch_ctx;
+ uint32_t i;
+ struct spdk_bdev_io *bdev_io;
+ struct spdk_bdev_io *bdev_io_next;
+ uint32_t count;
+ uint64_t io_len;
+ uint64_t lba;
+ TAILQ_HEAD(, spdk_bdev_io) head_io;
+
+ set_globals();
+ create_test_req(&req, "raid1", 0, true);
+ rpc_req = &req;
+ rpc_req_size = sizeof(req);
+ CU_ASSERT(raid_bdev_init() == 0);
+ verify_raid_config_present(req.name, false);
+ verify_raid_bdev_present(req.name, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&req, true);
+ verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE);
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) {
+ if (strcmp(pbdev->bdev.name, req.name) == 0) {
+ break;
+ }
+ }
+ SPDK_CU_ASSERT_FATAL(pbdev != NULL);
+ ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct raid_bdev_io_channel));
+ SPDK_CU_ASSERT_FATAL(ch != NULL);
+ ch_ctx = spdk_io_channel_get_ctx(ch);
+ SPDK_CU_ASSERT_FATAL(ch_ctx != NULL);
+
+ CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0);
+ SPDK_CU_ASSERT_FATAL(ch_ctx->base_channel != NULL);
+ for (i = 0; i < req.base_bdevs.num_base_bdevs; i++) {
+ CU_ASSERT(ch_ctx->base_channel[i] == (void *)0x1);
+ }
+ free_test_req(&req);
+
+ lba = 0;
+ TAILQ_INIT(&head_io);
+ for (count = 0; count < g_max_qd; count++) {
+ bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io));
+ SPDK_CU_ASSERT_FATAL(bdev_io != NULL);
+ TAILQ_INSERT_TAIL(&head_io, bdev_io, module_link);
+ io_len = (rand() % g_strip_size) + 1;
+ bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, SPDK_BDEV_IO_TYPE_WRITE);
+ g_bdev_io_submit_status = -ENOMEM;
+ lba += g_strip_size;
+ raid_bdev_submit_request(ch, bdev_io);
+ }
+
+ g_ignore_io_output = 1;
+
+ count = get_num_elts_in_waitq();
+ CU_ASSERT(count == g_max_qd);
+ g_bdev_io_submit_status = 0;
+ process_io_waitq();
+ CU_ASSERT(TAILQ_EMPTY(&g_io_waitq));
+
+ TAILQ_FOREACH_SAFE(bdev_io, &head_io, module_link, bdev_io_next) {
+ bdev_io_cleanup(bdev_io);
+ free(bdev_io);
+ }
+
+ raid_bdev_destroy_cb(pbdev, ch_ctx);
+ CU_ASSERT(ch_ctx->base_channel == NULL);
+ g_ignore_io_output = 0;
+ free(ch);
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+/* Create multiple raids, destroy raids without IO, get_raids related tests */
+static void
+test_multi_raid_no_io(void)
+{
+ struct rpc_construct_raid_bdev *construct_req;
+ struct rpc_destroy_raid_bdev destroy_req;
+ struct rpc_get_raid_bdevs get_raids_req;
+ uint32_t i;
+ char name[16];
+ uint32_t count;
+ uint32_t bbdev_idx = 0;
+
+ set_globals();
+ construct_req = calloc(MAX_RAIDS, sizeof(struct rpc_construct_raid_bdev));
+ SPDK_CU_ASSERT_FATAL(construct_req != NULL);
+ CU_ASSERT(raid_bdev_init() == 0);
+ for (i = 0; i < g_max_raids; i++) {
+ count = snprintf(name, 16, "%s%u", "raid", i);
+ name[count] = '\0';
+ create_test_req(&construct_req[i], name, bbdev_idx, true);
+ verify_raid_config_present(name, false);
+ verify_raid_bdev_present(name, false);
+ bbdev_idx += g_max_base_drives;
+ rpc_req = &construct_req[i];
+ rpc_req_size = sizeof(construct_req[0]);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&construct_req[i], true);
+ verify_raid_bdev(&construct_req[i], true, RAID_BDEV_STATE_ONLINE);
+ }
+
+ get_raids_req.category = strdup("all");
+ rpc_req = &get_raids_req;
+ rpc_req_size = sizeof(get_raids_req);
+ g_rpc_err = 0;
+ g_test_multi_raids = 1;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_get_raid_bdevs(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_get_raids(construct_req, g_max_raids, g_get_raids_output, g_get_raids_count);
+ for (i = 0; i < g_get_raids_count; i++) {
+ free(g_get_raids_output[i]);
+ }
+ g_get_raids_count = 0;
+
+ get_raids_req.category = strdup("online");
+ rpc_req = &get_raids_req;
+ rpc_req_size = sizeof(get_raids_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_get_raid_bdevs(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_get_raids(construct_req, g_max_raids, g_get_raids_output, g_get_raids_count);
+ for (i = 0; i < g_get_raids_count; i++) {
+ free(g_get_raids_output[i]);
+ }
+ g_get_raids_count = 0;
+
+ get_raids_req.category = strdup("configuring");
+ rpc_req = &get_raids_req;
+ rpc_req_size = sizeof(get_raids_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_get_raid_bdevs(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ CU_ASSERT(g_get_raids_count == 0);
+
+ get_raids_req.category = strdup("offline");
+ rpc_req = &get_raids_req;
+ rpc_req_size = sizeof(get_raids_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_get_raid_bdevs(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ CU_ASSERT(g_get_raids_count == 0);
+
+ get_raids_req.category = strdup("invalid_category");
+ rpc_req = &get_raids_req;
+ rpc_req_size = sizeof(get_raids_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_get_raid_bdevs(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 1);
+ CU_ASSERT(g_get_raids_count == 0);
+
+ get_raids_req.category = strdup("all");
+ rpc_req = &get_raids_req;
+ rpc_req_size = sizeof(get_raids_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_err = 1;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_get_raid_bdevs(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 1);
+ g_json_decode_obj_err = 0;
+ free(get_raids_req.category);
+ CU_ASSERT(g_get_raids_count == 0);
+
+ get_raids_req.category = strdup("all");
+ rpc_req = &get_raids_req;
+ rpc_req_size = sizeof(get_raids_req);
+ g_rpc_err = 0;
+ g_json_beg_res_ret_err = 1;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_get_raid_bdevs(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ g_json_beg_res_ret_err = 0;
+ CU_ASSERT(g_get_raids_count == 0);
+
+ for (i = 0; i < g_max_raids; i++) {
+ SPDK_CU_ASSERT_FATAL(construct_req[i].name != NULL);
+ destroy_req.name = strdup(construct_req[i].name);
+ count = snprintf(name, 16, "%s", destroy_req.name);
+ name[count] = '\0';
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present(name, false);
+ verify_raid_bdev_present(name, false);
+ }
+ g_test_multi_raids = 0;
+ raid_bdev_exit();
+ for (i = 0; i < g_max_raids; i++) {
+ free_test_req(&construct_req[i]);
+ }
+ free(construct_req);
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+/* Create multiple raids, fire IOs randomly on various raids */
+static void
+test_multi_raid_with_io(void)
+{
+ struct rpc_construct_raid_bdev *construct_req;
+ struct rpc_destroy_raid_bdev destroy_req;
+ uint32_t i, j;
+ char name[16];
+ uint32_t count;
+ uint32_t bbdev_idx = 0;
+ struct raid_bdev *pbdev;
+ struct spdk_io_channel *ch;
+ struct raid_bdev_io_channel *ch_ctx;
+ struct spdk_bdev_io *bdev_io;
+ uint64_t io_len;
+ uint64_t lba;
+ struct spdk_io_channel *ch_random;
+ struct raid_bdev_io_channel *ch_ctx_random;
+ int16_t iotype;
+ uint32_t raid_random;
+
+ set_globals();
+ construct_req = calloc(g_max_raids, sizeof(struct rpc_construct_raid_bdev));
+ SPDK_CU_ASSERT_FATAL(construct_req != NULL);
+ CU_ASSERT(raid_bdev_init() == 0);
+ ch = calloc(g_max_raids, sizeof(struct spdk_io_channel) + sizeof(struct raid_bdev_io_channel));
+ SPDK_CU_ASSERT_FATAL(ch != NULL);
+ for (i = 0; i < g_max_raids; i++) {
+ count = snprintf(name, 16, "%s%u", "raid", i);
+ name[count] = '\0';
+ create_test_req(&construct_req[i], name, bbdev_idx, true);
+ verify_raid_config_present(name, false);
+ verify_raid_bdev_present(name, false);
+ bbdev_idx += g_max_base_drives;
+ rpc_req = &construct_req[i];
+ rpc_req_size = sizeof(construct_req[0]);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&construct_req[i], true);
+ verify_raid_bdev(&construct_req[i], true, RAID_BDEV_STATE_ONLINE);
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) {
+ if (strcmp(pbdev->bdev.name, construct_req[i].name) == 0) {
+ break;
+ }
+ }
+ CU_ASSERT(pbdev != NULL);
+ ch_ctx = spdk_io_channel_get_ctx(&ch[i]);
+ SPDK_CU_ASSERT_FATAL(ch_ctx != NULL);
+ CU_ASSERT(raid_bdev_create_cb(pbdev, ch_ctx) == 0);
+ CU_ASSERT(ch_ctx->base_channel != NULL);
+ for (j = 0; j < construct_req[i].base_bdevs.num_base_bdevs; j++) {
+ CU_ASSERT(ch_ctx->base_channel[j] == (void *)0x1);
+ }
+ }
+
+ lba = 0;
+ for (count = 0; count < g_max_qd; count++) {
+ bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct raid_bdev_io));
+ SPDK_CU_ASSERT_FATAL(bdev_io != NULL);
+ io_len = (rand() % g_strip_size) + 1;
+ iotype = (rand() % 2) ? SPDK_BDEV_IO_TYPE_WRITE : SPDK_BDEV_IO_TYPE_READ;
+ memset(g_io_output, 0, (g_max_io_size / g_strip_size) + 1 * sizeof(struct io_output));
+ g_io_output_index = 0;
+ raid_random = rand() % g_max_raids;
+ ch_random = &ch[raid_random];
+ ch_ctx_random = spdk_io_channel_get_ctx(ch_random);
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) {
+ if (strcmp(pbdev->bdev.name, construct_req[raid_random].name) == 0) {
+ break;
+ }
+ }
+ bdev_io_initialize(bdev_io, &pbdev->bdev, lba, io_len, iotype);
+ lba += g_strip_size;
+ CU_ASSERT(pbdev != NULL);
+ raid_bdev_submit_request(ch_random, bdev_io);
+ verify_io(bdev_io, g_max_base_drives, ch_ctx_random, pbdev,
+ g_child_io_status_flag);
+ bdev_io_cleanup(bdev_io);
+ free(bdev_io);
+ }
+
+ for (i = 0; i < g_max_raids; i++) {
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) {
+ if (strcmp(pbdev->bdev.name, construct_req[i].name) == 0) {
+ break;
+ }
+ }
+ CU_ASSERT(pbdev != NULL);
+ ch_ctx = spdk_io_channel_get_ctx(&ch[i]);
+ SPDK_CU_ASSERT_FATAL(ch_ctx != NULL);
+ raid_bdev_destroy_cb(pbdev, ch_ctx);
+ CU_ASSERT(ch_ctx->base_channel == NULL);
+ destroy_req.name = strdup(construct_req[i].name);
+ count = snprintf(name, 16, "%s", destroy_req.name);
+ name[count] = '\0';
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present(name, false);
+ verify_raid_bdev_present(name, false);
+ }
+ raid_bdev_exit();
+ for (i = 0; i < g_max_raids; i++) {
+ free_test_req(&construct_req[i]);
+ }
+ free(construct_req);
+ free(ch);
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+static void
+test_io_type_supported(void)
+{
+ CU_ASSERT(raid_bdev_io_type_supported(NULL, SPDK_BDEV_IO_TYPE_READ) == true);
+ CU_ASSERT(raid_bdev_io_type_supported(NULL, SPDK_BDEV_IO_TYPE_WRITE) == true);
+ CU_ASSERT(raid_bdev_io_type_supported(NULL, SPDK_BDEV_IO_TYPE_FLUSH) == true);
+ CU_ASSERT(raid_bdev_io_type_supported(NULL, SPDK_BDEV_IO_TYPE_INVALID) == false);
+}
+
+static void
+test_create_raid_from_config(void)
+{
+ struct rpc_construct_raid_bdev req;
+ struct spdk_bdev *bdev;
+ struct rpc_destroy_raid_bdev destroy_req;
+ bool can_claim;
+ struct raid_bdev_config *raid_cfg;
+ uint32_t base_bdev_slot;
+
+ set_globals();
+ create_test_req(&req, "raid1", 0, true);
+ rpc_req = &req;
+ rpc_req_size = sizeof(req);
+ g_config_level_create = 1;
+ CU_ASSERT(raid_bdev_init() == 0);
+ g_config_level_create = 0;
+
+ verify_raid_config_present("raid1", true);
+ verify_raid_bdev_present("raid1", true);
+
+ TAILQ_FOREACH(bdev, &g_bdev_list, internal.link) {
+ raid_bdev_examine(bdev);
+ }
+
+ can_claim = raid_bdev_can_claim_bdev("Invalid", &raid_cfg, &base_bdev_slot);
+ CU_ASSERT(can_claim == false);
+
+ verify_raid_config(&req, true);
+ verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE);
+
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ raid_bdev_exit();
+ free_test_req(&req);
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+static void
+test_create_raid_from_config_invalid_params(void)
+{
+ struct rpc_construct_raid_bdev req;
+ uint8_t count;
+
+ set_globals();
+ rpc_req = &req;
+ rpc_req_size = sizeof(req);
+ g_config_level_create = 1;
+
+ create_test_req(&req, "raid1", 0, true);
+ free(req.name);
+ req.name = NULL;
+ CU_ASSERT(raid_bdev_init() != 0);
+ free_test_req(&req);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ create_test_req(&req, "raid1", 0, false);
+ req.strip_size = 1234;
+ CU_ASSERT(raid_bdev_init() != 0);
+ free_test_req(&req);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ create_test_req(&req, "raid1", 0, false);
+ req.raid_level = 1;
+ CU_ASSERT(raid_bdev_init() != 0);
+ free_test_req(&req);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ create_test_req(&req, "raid1", 0, false);
+ req.raid_level = 1;
+ CU_ASSERT(raid_bdev_init() != 0);
+ free_test_req(&req);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ create_test_req(&req, "raid1", 0, false);
+ req.base_bdevs.num_base_bdevs++;
+ CU_ASSERT(raid_bdev_init() != 0);
+ req.base_bdevs.num_base_bdevs--;
+ free_test_req(&req);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ create_test_req(&req, "raid1", 0, false);
+ req.base_bdevs.num_base_bdevs--;
+ CU_ASSERT(raid_bdev_init() != 0);
+ req.base_bdevs.num_base_bdevs++;
+ free_test_req(&req);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ if (g_max_base_drives > 1) {
+ create_test_req(&req, "raid1", 0, false);
+ count = snprintf(req.base_bdevs.base_bdevs[g_max_base_drives - 1], 15, "%s", "Nvme0n1");
+ req.base_bdevs.base_bdevs[g_max_base_drives - 1][count] = '\0';
+ CU_ASSERT(raid_bdev_init() != 0);
+ free_test_req(&req);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+ }
+
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+static void
+test_raid_json_dump_info(void)
+{
+ struct rpc_construct_raid_bdev req;
+ struct rpc_destroy_raid_bdev destroy_req;
+ struct raid_bdev *pbdev;
+
+ set_globals();
+ create_test_req(&req, "raid1", 0, true);
+ rpc_req = &req;
+ rpc_req_size = sizeof(req);
+ CU_ASSERT(raid_bdev_init() == 0);
+
+ verify_raid_config_present(req.name, false);
+ verify_raid_bdev_present(req.name, false);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_bdev(&req, true, RAID_BDEV_STATE_ONLINE);
+
+ TAILQ_FOREACH(pbdev, &g_spdk_raid_bdev_list, global_link) {
+ if (strcmp(pbdev->bdev.name, req.name) == 0) {
+ break;
+ }
+ }
+ CU_ASSERT(pbdev != NULL);
+
+ CU_ASSERT(raid_bdev_dump_info_json(pbdev, NULL) == 0);
+
+ free_test_req(&req);
+
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+static void
+test_context_size(void)
+{
+ CU_ASSERT(raid_bdev_get_ctx_size() == sizeof(struct raid_bdev_io));
+}
+
+static void
+test_asym_base_drives_blockcnt(void)
+{
+ struct rpc_construct_raid_bdev construct_req;
+ struct rpc_destroy_raid_bdev destroy_req;
+ struct spdk_bdev *bbdev;
+ uint32_t i;
+
+ set_globals();
+ create_test_req(&construct_req, "raid1", 0, true);
+ rpc_req = &construct_req;
+ rpc_req_size = sizeof(construct_req);
+ CU_ASSERT(raid_bdev_init() == 0);
+ verify_raid_config_present(construct_req.name, false);
+ verify_raid_bdev_present(construct_req.name, false);
+ g_rpc_err = 0;
+ for (i = 0; i < construct_req.base_bdevs.num_base_bdevs; i++) {
+ bbdev = spdk_bdev_get_by_name(construct_req.base_bdevs.base_bdevs[i]);
+ SPDK_CU_ASSERT_FATAL(bbdev != NULL);
+ bbdev->blockcnt = rand() + 1;
+ }
+ g_json_decode_obj_construct = 1;
+ spdk_rpc_construct_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config(&construct_req, true);
+ verify_raid_bdev(&construct_req, true, RAID_BDEV_STATE_ONLINE);
+ free_test_req(&construct_req);
+
+ destroy_req.name = strdup("raid1");
+ rpc_req = &destroy_req;
+ rpc_req_size = sizeof(destroy_req);
+ g_rpc_err = 0;
+ g_json_decode_obj_construct = 0;
+ spdk_rpc_destroy_raid_bdev(NULL, NULL);
+ CU_ASSERT(g_rpc_err == 0);
+ verify_raid_config_present("raid1", false);
+ verify_raid_bdev_present("raid1", false);
+
+ raid_bdev_exit();
+ base_bdevs_cleanup();
+ reset_globals();
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("raid", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test_construct_raid", test_construct_raid) == NULL ||
+ CU_add_test(suite, "test_destroy_raid", test_destroy_raid) == NULL ||
+ CU_add_test(suite, "test_construct_raid_invalid_args", test_construct_raid_invalid_args) == NULL ||
+ CU_add_test(suite, "test_destroy_raid_invalid_args", test_destroy_raid_invalid_args) == NULL ||
+ CU_add_test(suite, "test_io_channel", test_io_channel) == NULL ||
+ CU_add_test(suite, "test_write_io", test_write_io) == NULL ||
+ CU_add_test(suite, "test_read_io", test_read_io) == NULL ||
+ CU_add_test(suite, "test_io_failure", test_io_failure) == NULL ||
+ CU_add_test(suite, "test_io_waitq", test_io_waitq) == NULL ||
+ CU_add_test(suite, "test_multi_raid_no_io", test_multi_raid_no_io) == NULL ||
+ CU_add_test(suite, "test_multi_raid_with_io", test_multi_raid_with_io) == NULL ||
+ CU_add_test(suite, "test_io_type_supported", test_io_type_supported) == NULL ||
+ CU_add_test(suite, "test_create_raid_from_config", test_create_raid_from_config) == NULL ||
+ CU_add_test(suite, "test_create_raid_from_config_invalid_params",
+ test_create_raid_from_config_invalid_params) == NULL ||
+ CU_add_test(suite, "test_raid_json_dump_info", test_raid_json_dump_info) == NULL ||
+ CU_add_test(suite, "test_context_size", test_context_size) == NULL ||
+ CU_add_test(suite, "test_asym_base_drives_blockcnt", test_asym_base_drives_blockcnt) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ set_test_opts();
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/.gitignore b/src/spdk/test/unit/lib/bdev/crypto.c/.gitignore
new file mode 100644
index 00000000..b2777562
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/crypto.c/.gitignore
@@ -0,0 +1 @@
+crypto_ut
diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/Makefile b/src/spdk/test/unit/lib/bdev/crypto.c/Makefile
new file mode 100644
index 00000000..3241464b
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/crypto.c/Makefile
@@ -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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+TEST_FILE = crypto_ut.c
+CFLAGS += $(ENV_CFLAGS)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/crypto_ut.c b/src/spdk/test/unit/lib/bdev/crypto.c/crypto_ut.c
new file mode 100644
index 00000000..f01aba19
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/crypto.c/crypto_ut.c
@@ -0,0 +1,908 @@
+/*-
+ * 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_cunit.h"
+
+#include "common/lib/test_env.c"
+#include "spdk_internal/mock.h"
+#include "unit/lib/json_mock.c"
+
+/* these rte_ headers are our local copies of the DPDK headers hacked to mock some functions
+ * included in them that can't be done with our mock library.
+ */
+#include "rte_crypto.h"
+#include "rte_cryptodev.h"
+DEFINE_STUB_V(rte_crypto_op_free, (struct rte_crypto_op *op));
+#include "bdev/crypto/vbdev_crypto.c"
+
+/* SPDK stubs */
+DEFINE_STUB(spdk_conf_find_section, struct spdk_conf_section *,
+ (struct spdk_conf *cp, const char *name), NULL);
+DEFINE_STUB(spdk_conf_section_get_nval, char *,
+ (struct spdk_conf_section *sp, const char *key, int idx), NULL);
+DEFINE_STUB(spdk_conf_section_get_nmval, char *,
+ (struct spdk_conf_section *sp, const char *key, int idx1, int idx2), NULL);
+
+DEFINE_STUB_V(spdk_bdev_module_list_add, (struct spdk_bdev_module *bdev_module));
+DEFINE_STUB_V(spdk_bdev_free_io, (struct spdk_bdev_io *g_bdev_io));
+DEFINE_STUB(spdk_bdev_io_type_supported, bool, (struct spdk_bdev *bdev,
+ enum spdk_bdev_io_type io_type), 0);
+DEFINE_STUB_V(spdk_bdev_module_release_bdev, (struct spdk_bdev *bdev));
+DEFINE_STUB_V(spdk_bdev_close, (struct spdk_bdev_desc *desc));
+DEFINE_STUB(spdk_bdev_get_name, const char *, (const struct spdk_bdev *bdev), 0);
+DEFINE_STUB(spdk_env_get_current_core, uint32_t, (void), 0);
+DEFINE_STUB(spdk_bdev_get_io_channel, struct spdk_io_channel *, (struct spdk_bdev_desc *desc), 0);
+DEFINE_STUB_V(spdk_bdev_unregister, (struct spdk_bdev *bdev, spdk_bdev_unregister_cb cb_fn,
+ void *cb_arg));
+DEFINE_STUB(spdk_bdev_open, int, (struct spdk_bdev *bdev, bool write,
+ spdk_bdev_remove_cb_t remove_cb,
+ void *remove_ctx, struct spdk_bdev_desc **_desc), 0);
+DEFINE_STUB(spdk_bdev_module_claim_bdev, int, (struct spdk_bdev *bdev, struct spdk_bdev_desc *desc,
+ struct spdk_bdev_module *module), 0);
+DEFINE_STUB_V(spdk_bdev_module_examine_done, (struct spdk_bdev_module *module));
+DEFINE_STUB(spdk_vbdev_register, int, (struct spdk_bdev *vbdev, struct spdk_bdev **base_bdevs,
+ int base_bdev_count), 0);
+DEFINE_STUB(spdk_bdev_get_by_name, struct spdk_bdev *, (const char *bdev_name), NULL);
+DEFINE_STUB(spdk_env_get_socket_id, uint32_t, (uint32_t core), 0);
+
+/* DPDK stubs */
+DEFINE_STUB(rte_cryptodev_count, uint8_t, (void), 0);
+DEFINE_STUB(rte_eal_get_configuration, struct rte_config *, (void), NULL);
+DEFINE_STUB_V(rte_mempool_free, (struct rte_mempool *mp));
+DEFINE_STUB(rte_socket_id, unsigned, (void), 0);
+DEFINE_STUB(rte_crypto_op_pool_create, struct rte_mempool *,
+ (const char *name, enum rte_crypto_op_type type, unsigned nb_elts,
+ unsigned cache_size, uint16_t priv_size, int socket_id), (struct rte_mempool *)1);
+DEFINE_STUB(rte_cryptodev_device_count_by_driver, uint8_t, (uint8_t driver_id), 0);
+DEFINE_STUB(rte_cryptodev_socket_id, int, (uint8_t dev_id), 0);
+DEFINE_STUB(rte_cryptodev_configure, int, (uint8_t dev_id, struct rte_cryptodev_config *config), 0);
+DEFINE_STUB(rte_cryptodev_queue_pair_setup, int, (uint8_t dev_id, uint16_t queue_pair_id,
+ const struct rte_cryptodev_qp_conf *qp_conf,
+ int socket_id, struct rte_mempool *session_pool), 0);
+DEFINE_STUB(rte_cryptodev_start, int, (uint8_t dev_id), 0)
+DEFINE_STUB_V(rte_cryptodev_stop, (uint8_t dev_id));
+DEFINE_STUB(rte_cryptodev_sym_session_create, struct rte_cryptodev_sym_session *,
+ (struct rte_mempool *mempool), (struct rte_cryptodev_sym_session *)1);
+DEFINE_STUB(rte_cryptodev_sym_session_clear, int, (uint8_t dev_id,
+ struct rte_cryptodev_sym_session *sess), 0);
+DEFINE_STUB(rte_cryptodev_sym_session_free, int, (struct rte_cryptodev_sym_session *sess), 0);
+DEFINE_STUB(rte_cryptodev_sym_session_init, int, (uint8_t dev_id,
+ struct rte_cryptodev_sym_session *sess,
+ struct rte_crypto_sym_xform *xforms, struct rte_mempool *mempool), 0);
+DEFINE_STUB(rte_vdev_init, int, (const char *name, const char *args), 0);
+void __attribute__((noreturn)) __rte_panic(const char *funcname, const char *format, ...)
+{
+ abort();
+}
+struct rte_mempool_ops_table rte_mempool_ops_table;
+struct rte_cryptodev *rte_cryptodevs;
+__thread unsigned per_lcore__lcore_id = 0;
+
+/* global vars and setup/cleanup functions used for all test functions */
+struct spdk_bdev_io *g_bdev_io;
+struct crypto_bdev_io *g_io_ctx;
+struct crypto_io_channel *g_crypto_ch;
+struct spdk_io_channel *g_io_ch;
+struct vbdev_dev g_device;
+struct vbdev_crypto g_crypto_bdev;
+struct rte_config *g_test_config;
+struct device_qp g_dev_qp;
+
+#define MAX_TEST_BLOCKS 8192
+struct rte_crypto_op *g_test_crypto_ops[MAX_TEST_BLOCKS];
+struct rte_crypto_op *g_test_dequeued_ops[MAX_TEST_BLOCKS];
+struct rte_crypto_op *g_test_dev_full_ops[MAX_TEST_BLOCKS];
+
+/* These globals are externs in our local rte_ header files so we can control
+ * specific functions for mocking.
+ */
+uint16_t g_dequeue_mock;
+uint16_t g_enqueue_mock;
+unsigned ut_rte_crypto_op_bulk_alloc;
+int ut_rte_crypto_op_attach_sym_session = 0;
+
+int ut_rte_cryptodev_info_get = 0;
+bool ut_rte_cryptodev_info_get_mocked = false;
+void
+rte_cryptodev_info_get(uint8_t dev_id, struct rte_cryptodev_info *dev_info)
+{
+ dev_info->max_nb_queue_pairs = ut_rte_cryptodev_info_get;
+}
+
+unsigned int
+rte_cryptodev_sym_get_private_session_size(uint8_t dev_id)
+{
+ return (unsigned int)dev_id;
+}
+
+void
+spdk_bdev_io_get_buf(struct spdk_bdev_io *bdev_io, spdk_bdev_io_get_buf_cb cb, uint64_t len)
+{
+ cb(g_io_ch, g_bdev_io);
+}
+
+/* Mock these functions to call the callback and then return the value we require */
+int ut_spdk_bdev_readv_blocks = 0;
+bool ut_spdk_bdev_readv_blocks_mocked = false;
+int
+spdk_bdev_readv_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ struct iovec *iov, int iovcnt,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ cb(g_bdev_io, !ut_spdk_bdev_readv_blocks, cb_arg);
+ return ut_spdk_bdev_readv_blocks;
+}
+
+int ut_spdk_bdev_writev_blocks = 0;
+bool ut_spdk_bdev_writev_blocks_mocked = false;
+int
+spdk_bdev_writev_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ struct iovec *iov, int iovcnt,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ cb(g_bdev_io, !ut_spdk_bdev_writev_blocks, cb_arg);
+ return ut_spdk_bdev_writev_blocks;
+}
+
+int ut_spdk_bdev_unmap_blocks = 0;
+bool ut_spdk_bdev_unmap_blocks_mocked = false;
+int
+spdk_bdev_unmap_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ cb(g_bdev_io, !ut_spdk_bdev_unmap_blocks, cb_arg);
+ return ut_spdk_bdev_unmap_blocks;
+}
+
+int ut_spdk_bdev_flush_blocks = 0;
+bool ut_spdk_bdev_flush_blocks_mocked = false;
+int
+spdk_bdev_flush_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb,
+ void *cb_arg)
+{
+ cb(g_bdev_io, !ut_spdk_bdev_flush_blocks, cb_arg);
+ return ut_spdk_bdev_flush_blocks;
+}
+
+int ut_spdk_bdev_reset = 0;
+bool ut_spdk_bdev_reset_mocked = false;
+int
+spdk_bdev_reset(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ cb(g_bdev_io, !ut_spdk_bdev_reset, cb_arg);
+ return ut_spdk_bdev_reset;
+}
+
+bool g_completion_called = false;
+void
+spdk_bdev_io_complete(struct spdk_bdev_io *bdev_io, enum spdk_bdev_io_status status)
+{
+ bdev_io->internal.status = status;
+ g_completion_called = true;
+}
+
+/* Used in testing device full condition */
+static inline uint16_t
+rte_cryptodev_enqueue_burst(uint8_t dev_id, uint16_t qp_id,
+ struct rte_crypto_op **ops, uint16_t nb_ops)
+{
+ int i;
+
+ CU_ASSERT(nb_ops > 0);
+
+ for (i = 0; i < nb_ops; i++) {
+ /* Use this empty (til now) array of pointers to store
+ * enqueued operations for assertion in dev_full test.
+ */
+ g_test_dev_full_ops[i] = *ops++;
+ }
+
+ return g_enqueue_mock;
+}
+
+/* This is pretty ugly but in order to complete an IO via the
+ * poller in the submit path, we need to first call to this func
+ * to return the dequeued value and also decrement it. On the subsequent
+ * call it needs to return 0 to indicate to the caller that there are
+ * no more IOs to drain.
+ */
+int g_test_overflow = 0;
+static inline uint16_t
+rte_cryptodev_dequeue_burst(uint8_t dev_id, uint16_t qp_id,
+ struct rte_crypto_op **ops, uint16_t nb_ops)
+{
+ CU_ASSERT(nb_ops > 0);
+
+ /* A crypto device can be full on enqueue, the driver is designed to drain
+ * the device at the time by calling the poller until it's empty, then
+ * submitting the remaining crypto ops.
+ */
+ if (g_test_overflow) {
+ if (g_dequeue_mock == 0) {
+ return 0;
+ }
+ *ops = g_test_crypto_ops[g_enqueue_mock];
+ (*ops)->status = RTE_CRYPTO_OP_STATUS_SUCCESS;
+ g_dequeue_mock -= 1;
+ }
+ return (g_dequeue_mock + 1);
+}
+
+/* Instead of allocating real memory, assign the allocations to our
+ * test array for assertion in tests.
+ */
+static inline unsigned
+rte_crypto_op_bulk_alloc(struct rte_mempool *mempool,
+ enum rte_crypto_op_type type,
+ struct rte_crypto_op **ops, uint16_t nb_ops)
+{
+ int i;
+
+ for (i = 0; i < nb_ops; i++) {
+ *ops++ = g_test_crypto_ops[i];
+ }
+ return ut_rte_crypto_op_bulk_alloc;
+}
+
+static __rte_always_inline void
+rte_mempool_put_bulk(struct rte_mempool *mp, void *const *obj_table,
+ unsigned int n)
+{
+ return;
+}
+
+static inline void *rte_mempool_get_priv(struct rte_mempool *mp)
+{
+ return NULL;
+}
+
+
+static inline int
+rte_crypto_op_attach_sym_session(struct rte_crypto_op *op,
+ struct rte_cryptodev_sym_session *sess)
+{
+ return ut_rte_crypto_op_attach_sym_session;
+}
+
+/* Global setup for all tests that share a bunch of preparation... */
+static int
+test_setup(void)
+{
+ int i;
+
+ /* Prepare essential variables for test routines */
+ g_bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct crypto_bdev_io));
+ g_bdev_io->u.bdev.iovs = calloc(1, sizeof(struct iovec) * 128);
+ g_bdev_io->bdev = &g_crypto_bdev.crypto_bdev;
+ g_io_ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct crypto_io_channel));
+ g_crypto_ch = (struct crypto_io_channel *)((uint8_t *)g_io_ch + sizeof(struct spdk_io_channel));
+ g_io_ctx = (struct crypto_bdev_io *)g_bdev_io->driver_ctx;
+ memset(&g_device, 0, sizeof(struct vbdev_dev));
+ memset(&g_crypto_bdev, 0, sizeof(struct vbdev_crypto));
+ g_dev_qp.device = &g_device;
+ g_io_ctx->crypto_ch = g_crypto_ch;
+ g_io_ctx->crypto_bdev = &g_crypto_bdev;
+ g_crypto_ch->device_qp = &g_dev_qp;
+ g_test_config = calloc(1, sizeof(struct rte_config));
+ g_test_config->lcore_count = 1;
+
+ /* Allocate a real mbuf pool so we can test error paths */
+ g_mbuf_mp = spdk_mempool_create("mbuf_mp", NUM_MBUFS, sizeof(struct rte_mbuf),
+ SPDK_MEMPOOL_DEFAULT_CACHE_SIZE,
+ SPDK_ENV_SOCKET_ID_ANY);
+
+ /* Instead of allocating real rte mempools for these, it's easier and provides the
+ * same coverage just calloc them here.
+ */
+ for (i = 0; i < MAX_TEST_BLOCKS; i++) {
+ g_test_crypto_ops[i] = calloc(1, sizeof(struct rte_crypto_op) +
+ sizeof(struct rte_crypto_sym_op));
+ g_test_dequeued_ops[i] = calloc(1, sizeof(struct rte_crypto_op) +
+ sizeof(struct rte_crypto_sym_op));
+ }
+ return 0;
+}
+
+/* Global teardown for all tests */
+static int
+test_cleanup(void)
+{
+ int i;
+
+ free(g_test_config);
+ spdk_mempool_free(g_mbuf_mp);
+ for (i = 0; i < MAX_TEST_BLOCKS; i++) {
+ free(g_test_crypto_ops[i]);
+ free(g_test_dequeued_ops[i]);
+ }
+ free(g_bdev_io->u.bdev.iovs);
+ free(g_bdev_io);
+ free(g_io_ch);
+ return 0;
+}
+
+static void
+test_error_paths(void)
+{
+ /* Single element block size write, just to test error paths
+ * in vbdev_crypto_submit_request().
+ */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->u.bdev.iovcnt = 1;
+ g_bdev_io->u.bdev.num_blocks = 1;
+ g_bdev_io->u.bdev.iovs[0].iov_len = 512;
+ g_crypto_bdev.crypto_bdev.blocklen = 512;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE;
+ g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = 1;
+
+ /* test failure of spdk_mempool_get_bulk() */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ MOCK_SET(spdk_mempool_get, NULL);
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+
+ /* same thing but switch to reads to test error path in _crypto_complete_io() */
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ;
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ /* Now with the read_blocks failing */
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ;
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ MOCK_SET(spdk_bdev_readv_blocks, -1);
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ MOCK_SET(spdk_bdev_readv_blocks, 0);
+ MOCK_CLEAR(spdk_mempool_get);
+
+ /* test failure of rte_crypto_op_bulk_alloc() */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ ut_rte_crypto_op_bulk_alloc = 0;
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ ut_rte_crypto_op_bulk_alloc = 1;
+
+ /* test failure of rte_cryptodev_sym_session_create() */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ MOCK_SET(rte_cryptodev_sym_session_create, NULL);
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ MOCK_SET(rte_cryptodev_sym_session_create, (struct rte_cryptodev_sym_session *)1);
+
+ /* test failure of rte_cryptodev_sym_session_init() */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ MOCK_SET(rte_cryptodev_sym_session_init, -1);
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ MOCK_SET(rte_cryptodev_sym_session_init, 0);
+
+ /* test failure of rte_crypto_op_attach_sym_session() */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ ut_rte_crypto_op_attach_sym_session = -1;
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ ut_rte_crypto_op_attach_sym_session = 0;
+}
+
+static void
+test_simple_write(void)
+{
+ /* Single element block size write */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->u.bdev.iovcnt = 1;
+ g_bdev_io->u.bdev.num_blocks = 1;
+ g_bdev_io->u.bdev.offset_blocks = 0;
+ g_bdev_io->u.bdev.iovs[0].iov_len = 512;
+ g_bdev_io->u.bdev.iovs[0].iov_base = &test_simple_write;
+ g_crypto_bdev.crypto_bdev.blocklen = 512;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE;
+ g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = 1;
+
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(g_io_ctx->cryop_cnt_remaining == 1);
+ CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_ENCRYPT);
+ CU_ASSERT(g_io_ctx->cry_iov.iov_len == 512);
+ CU_ASSERT(g_io_ctx->cry_iov.iov_base != NULL);
+ CU_ASSERT(g_io_ctx->cry_offset_blocks == 0);
+ CU_ASSERT(g_io_ctx->cry_num_blocks == 1);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->buf_addr == &test_simple_write);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->data_len == 512);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->next == NULL);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->cipher.data.length == 512);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->cipher.data.offset == 0);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->userdata == g_bdev_io);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->m_dst->buf_addr != NULL);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->m_dst->data_len == 512);
+
+ spdk_dma_free(g_io_ctx->cry_iov.iov_base);
+ spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[0]->sym->m_src);
+ spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[0]->sym->m_dst);
+}
+
+static void
+test_simple_read(void)
+{
+ /* Single element block size read */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->u.bdev.iovcnt = 1;
+ g_bdev_io->u.bdev.num_blocks = 1;
+ g_bdev_io->u.bdev.iovs[0].iov_len = 512;
+ g_bdev_io->u.bdev.iovs[0].iov_base = &test_simple_read;
+ g_crypto_bdev.crypto_bdev.blocklen = 512;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ;
+ g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = 1;
+
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(g_io_ctx->cryop_cnt_remaining == 1);
+ CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_DECRYPT);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->buf_addr == &test_simple_read);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->data_len == 512);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->next == NULL);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->cipher.data.length == 512);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->cipher.data.offset == 0);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->m_src->userdata == g_bdev_io);
+ CU_ASSERT(g_test_crypto_ops[0]->sym->m_dst == NULL);
+
+ spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[0]->sym->m_src);
+}
+
+static void
+test_large_rw(void)
+{
+ unsigned block_len = 512;
+ unsigned num_blocks = CRYPTO_MAX_IO / block_len;
+ unsigned io_len = block_len * num_blocks;
+ unsigned i;
+
+ /* Multi block size read, multi-element */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->u.bdev.iovcnt = 1;
+ g_bdev_io->u.bdev.num_blocks = num_blocks;
+ g_bdev_io->u.bdev.iovs[0].iov_len = io_len;
+ g_bdev_io->u.bdev.iovs[0].iov_base = &test_large_rw;
+ g_crypto_bdev.crypto_bdev.blocklen = block_len;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ;
+ g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = num_blocks;
+
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(g_io_ctx->cryop_cnt_remaining == (int)num_blocks);
+ CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_DECRYPT);
+
+ for (i = 0; i < num_blocks; i++) {
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->buf_addr == &test_large_rw + (i * block_len));
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->data_len == block_len);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->next == NULL);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.length == block_len);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.offset == 0);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->userdata == g_bdev_io);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_dst == NULL);
+ spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_src);
+ }
+
+ /* Multi block size write, multi-element */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->u.bdev.iovcnt = 1;
+ g_bdev_io->u.bdev.num_blocks = num_blocks;
+ g_bdev_io->u.bdev.iovs[0].iov_len = io_len;
+ g_bdev_io->u.bdev.iovs[0].iov_base = &test_large_rw;
+ g_crypto_bdev.crypto_bdev.blocklen = block_len;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE;
+ g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = num_blocks;
+
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(g_io_ctx->cryop_cnt_remaining == (int)num_blocks);
+ CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_ENCRYPT);
+
+ for (i = 0; i < num_blocks; i++) {
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->buf_addr == &test_large_rw + (i * block_len));
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->data_len == block_len);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->next == NULL);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.length == block_len);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.offset == 0);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->userdata == g_bdev_io);
+ CU_ASSERT(g_io_ctx->cry_iov.iov_len == io_len);
+ CU_ASSERT(g_io_ctx->cry_iov.iov_base != NULL);
+ CU_ASSERT(g_io_ctx->cry_offset_blocks == 0);
+ CU_ASSERT(g_io_ctx->cry_num_blocks == num_blocks);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_dst->buf_addr != NULL);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_dst->data_len == block_len);
+ spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_src);
+ spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_dst);
+ }
+ spdk_dma_free(g_io_ctx->cry_iov.iov_base);
+}
+
+static void
+test_dev_full(void)
+{
+ unsigned block_len = 512;
+ unsigned num_blocks = 2;
+ unsigned io_len = block_len * num_blocks;
+ unsigned i;
+
+ g_test_overflow = 1;
+
+ /* Multi block size read, multi-element */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->u.bdev.iovcnt = 1;
+ g_bdev_io->u.bdev.num_blocks = num_blocks;
+ g_bdev_io->u.bdev.iovs[0].iov_len = io_len;
+ g_bdev_io->u.bdev.iovs[0].iov_base = &test_dev_full;
+ g_crypto_bdev.crypto_bdev.blocklen = block_len;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ;
+ g_enqueue_mock = g_dequeue_mock = 1;
+ ut_rte_crypto_op_bulk_alloc = num_blocks;
+
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ /* this test only completes one of the 2 IOs (in the drain path) */
+ CU_ASSERT(g_io_ctx->cryop_cnt_remaining == 1);
+ CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_DECRYPT);
+
+ for (i = 0; i < num_blocks; i++) {
+ /* One of the src_mbufs was freed because of the device full condition so
+ * we can't assert its value here.
+ */
+ CU_ASSERT(g_test_dev_full_ops[i]->sym->cipher.data.length == block_len);
+ CU_ASSERT(g_test_dev_full_ops[i]->sym->cipher.data.offset == 0);
+ CU_ASSERT(g_test_dev_full_ops[i]->sym->m_src == g_test_dev_full_ops[i]->sym->m_src);
+ CU_ASSERT(g_test_dev_full_ops[i]->sym->m_dst == NULL);
+ }
+
+ /* Only one of the 2 blocks in the test was freed on completion by design, so
+ * we need to free th other one here.
+ */
+ spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[0]->sym->m_src);
+ g_test_overflow = 0;
+}
+
+static void
+test_crazy_rw(void)
+{
+ unsigned block_len = 512;
+ int num_blocks = 4;
+ int i;
+
+ /* Multi block size read, single element, strange IOV makeup */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->u.bdev.iovcnt = 3;
+ g_bdev_io->u.bdev.num_blocks = num_blocks;
+ g_bdev_io->u.bdev.iovs[0].iov_len = 512;
+ g_bdev_io->u.bdev.iovs[0].iov_base = &test_crazy_rw;
+ g_bdev_io->u.bdev.iovs[1].iov_len = 1024;
+ g_bdev_io->u.bdev.iovs[1].iov_base = &test_crazy_rw + 512;
+ g_bdev_io->u.bdev.iovs[2].iov_len = 512;
+ g_bdev_io->u.bdev.iovs[2].iov_base = &test_crazy_rw + 512 + 1024;
+
+ g_crypto_bdev.crypto_bdev.blocklen = block_len;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ;
+ g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = num_blocks;
+
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(g_io_ctx->cryop_cnt_remaining == num_blocks);
+ CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_DECRYPT);
+
+ for (i = 0; i < num_blocks; i++) {
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->buf_addr == &test_crazy_rw + (i * block_len));
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->data_len == block_len);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->next == NULL);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.length == block_len);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.offset == 0);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->userdata == g_bdev_io);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src == g_test_crypto_ops[i]->sym->m_src);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_dst == NULL);
+ spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_src);
+ }
+
+ /* Multi block size write, single element strange IOV makeup */
+ num_blocks = 8;
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->u.bdev.iovcnt = 4;
+ g_bdev_io->u.bdev.num_blocks = num_blocks;
+ g_bdev_io->u.bdev.iovs[0].iov_len = 2048;
+ g_bdev_io->u.bdev.iovs[0].iov_base = &test_crazy_rw;
+ g_bdev_io->u.bdev.iovs[1].iov_len = 512;
+ g_bdev_io->u.bdev.iovs[1].iov_base = &test_crazy_rw + 2048;
+ g_bdev_io->u.bdev.iovs[2].iov_len = 512;
+ g_bdev_io->u.bdev.iovs[2].iov_base = &test_crazy_rw + 2048 + 512;
+ g_bdev_io->u.bdev.iovs[3].iov_len = 1024;
+ g_bdev_io->u.bdev.iovs[3].iov_base = &test_crazy_rw + 2048 + 512 + 512;
+
+ g_crypto_bdev.crypto_bdev.blocklen = block_len;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE;
+ g_enqueue_mock = g_dequeue_mock = ut_rte_crypto_op_bulk_alloc = num_blocks;
+
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(g_io_ctx->cryop_cnt_remaining == num_blocks);
+ CU_ASSERT(g_io_ctx->crypto_op == RTE_CRYPTO_CIPHER_OP_ENCRYPT);
+
+ for (i = 0; i < num_blocks; i++) {
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->buf_addr == &test_crazy_rw + (i * block_len));
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->data_len == block_len);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->next == NULL);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.length == block_len);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->cipher.data.offset == 0);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src->userdata == g_bdev_io);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_src == g_test_crypto_ops[i]->sym->m_src);
+ CU_ASSERT(g_test_crypto_ops[i]->sym->m_dst == g_test_crypto_ops[i]->sym->m_dst);
+ spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_src);
+ spdk_mempool_put(g_mbuf_mp, g_test_crypto_ops[i]->sym->m_dst);
+ }
+ spdk_dma_free(g_io_ctx->cry_iov.iov_base);
+}
+
+static void
+test_passthru(void)
+{
+ /* Make sure these follow our completion callback, test success & fail. */
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_UNMAP;
+ MOCK_SET(spdk_bdev_unmap_blocks, 0);
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ MOCK_SET(spdk_bdev_unmap_blocks, -1);
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ MOCK_CLEAR(spdk_bdev_unmap_blocks);
+
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_FLUSH;
+ MOCK_SET(spdk_bdev_flush_blocks, 0);
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ MOCK_SET(spdk_bdev_flush_blocks, -1);
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ MOCK_CLEAR(spdk_bdev_flush_blocks);
+
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_RESET;
+ MOCK_SET(spdk_bdev_reset, 0);
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ MOCK_SET(spdk_bdev_reset, -1);
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ MOCK_CLEAR(spdk_bdev_reset);
+
+ /* We should never get a WZ command, we report that we don't support it. */
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE_ZEROES;
+ vbdev_crypto_submit_request(g_io_ch, g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+}
+
+static void
+test_initdrivers(void)
+{
+ int rc;
+ static struct spdk_mempool *orig_mbuf_mp;
+ static struct spdk_mempool *orig_session_mp;
+
+ /* No drivers available, not an error though */
+ MOCK_SET(rte_eal_get_configuration, g_test_config);
+ MOCK_SET(rte_cryptodev_count, 0);
+ rc = vbdev_crypto_init_crypto_drivers();
+ CU_ASSERT(rc == 0);
+
+ /* Test failure of DPDK dev init. */
+ MOCK_SET(rte_cryptodev_count, 2);
+ MOCK_SET(rte_vdev_init, -1);
+ rc = vbdev_crypto_init_crypto_drivers();
+ CU_ASSERT(rc == -EINVAL);
+ MOCK_SET(rte_vdev_init, 0);
+
+ /* Can't create session pool. */
+ MOCK_SET(spdk_mempool_create, NULL);
+ orig_mbuf_mp = g_mbuf_mp;
+ orig_session_mp = g_session_mp;
+ rc = vbdev_crypto_init_crypto_drivers();
+ g_mbuf_mp = orig_mbuf_mp;
+ g_session_mp = orig_session_mp;
+ CU_ASSERT(rc == -ENOMEM);
+ MOCK_CLEAR(spdk_mempool_create);
+
+ /* Can't create op pool. These tests will alloc and free our g_mbuf_mp
+ * so save that off here and restore it after each test is over.
+ */
+ orig_mbuf_mp = g_mbuf_mp;
+ orig_session_mp = g_session_mp;
+ MOCK_SET(rte_crypto_op_pool_create, NULL);
+ rc = vbdev_crypto_init_crypto_drivers();
+ g_mbuf_mp = orig_mbuf_mp;
+ g_session_mp = orig_session_mp;
+ CU_ASSERT(rc == -ENOMEM);
+ MOCK_SET(rte_crypto_op_pool_create, (struct rte_mempool *)1);
+
+ /* Check resources are sufficient failure. */
+ orig_mbuf_mp = g_mbuf_mp;
+ orig_session_mp = g_session_mp;
+ rc = vbdev_crypto_init_crypto_drivers();
+ g_mbuf_mp = orig_mbuf_mp;
+ g_session_mp = orig_session_mp;
+ CU_ASSERT(rc == -EINVAL);
+
+ /* Test crypto dev configure failure. */
+ MOCK_SET(rte_cryptodev_device_count_by_driver, 2);
+ MOCK_SET(rte_cryptodev_info_get, 1);
+ MOCK_SET(rte_cryptodev_configure, -1);
+ orig_mbuf_mp = g_mbuf_mp;
+ orig_session_mp = g_session_mp;
+ rc = vbdev_crypto_init_crypto_drivers();
+ g_mbuf_mp = orig_mbuf_mp;
+ g_session_mp = orig_session_mp;
+ MOCK_SET(rte_cryptodev_configure, 0);
+ CU_ASSERT(rc == -EINVAL);
+
+ /* Test failure of qp setup. */
+ MOCK_SET(rte_cryptodev_queue_pair_setup, -1);
+ orig_mbuf_mp = g_mbuf_mp;
+ orig_session_mp = g_session_mp;
+ rc = vbdev_crypto_init_crypto_drivers();
+ g_mbuf_mp = orig_mbuf_mp;
+ g_session_mp = orig_session_mp;
+ CU_ASSERT(rc == -EINVAL);
+ MOCK_SET(rte_cryptodev_queue_pair_setup, 0);
+
+ /* Test failure of dev start. */
+ MOCK_SET(rte_cryptodev_start, -1);
+ orig_mbuf_mp = g_mbuf_mp;
+ orig_session_mp = g_session_mp;
+ rc = vbdev_crypto_init_crypto_drivers();
+ g_mbuf_mp = orig_mbuf_mp;
+ g_session_mp = orig_session_mp;
+ CU_ASSERT(rc == -EINVAL);
+ MOCK_SET(rte_cryptodev_start, 0);
+
+ /* Test happy path. */
+ rc = vbdev_crypto_init_crypto_drivers();
+ CU_ASSERT(rc == 0);
+}
+
+static void
+test_crypto_op_complete(void)
+{
+ /* Make sure completion code respects failure. */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_FAILED;
+ g_completion_called = false;
+ _crypto_operation_complete(g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ CU_ASSERT(g_completion_called == true);
+
+ /* Test read completion. */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_READ;
+ g_completion_called = false;
+ _crypto_operation_complete(g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(g_completion_called == true);
+
+ /* Test write completion success. */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE;
+ g_completion_called = false;
+ MOCK_SET(spdk_bdev_writev_blocks, 0);
+ /* Code under test will free this, if not ASAN will complain. */
+ g_io_ctx->cry_iov.iov_base = spdk_dma_malloc(16, 0x10, NULL);
+ _crypto_operation_complete(g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(g_completion_called == true);
+
+ /* Test write completion failed. */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_WRITE;
+ g_completion_called = false;
+ MOCK_SET(spdk_bdev_writev_blocks, -1);
+ /* Code under test will free this, if not ASAN will complain. */
+ g_io_ctx->cry_iov.iov_base = spdk_dma_malloc(16, 0x10, NULL);
+ _crypto_operation_complete(g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ CU_ASSERT(g_completion_called == true);
+
+ /* Test bogus type for this completion. */
+ g_bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ g_bdev_io->type = SPDK_BDEV_IO_TYPE_RESET;
+ g_completion_called = false;
+ _crypto_operation_complete(g_bdev_io);
+ CU_ASSERT(g_bdev_io->internal.status == SPDK_BDEV_IO_STATUS_FAILED);
+ CU_ASSERT(g_completion_called == true);
+}
+
+static void
+test_supported_io(void)
+{
+ void *ctx = NULL;
+ bool rc = true;
+
+ /* Make sure we always report false to WZ, we need the bdev layer to
+ * send real 0's so we can encrypt/decrypt them.
+ */
+ rc = vbdev_crypto_io_type_supported(ctx, SPDK_BDEV_IO_TYPE_WRITE_ZEROES);
+ CU_ASSERT(rc == false);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("crypto", test_setup, test_cleanup);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (CU_add_test(suite, "test_error_paths",
+ test_error_paths) == NULL ||
+ CU_add_test(suite, "test_simple_write",
+ test_simple_write) == NULL ||
+ CU_add_test(suite, "test_simple_read",
+ test_simple_read) == NULL ||
+ CU_add_test(suite, "test_large_rw",
+ test_large_rw) == NULL ||
+ CU_add_test(suite, "test_dev_full",
+ test_dev_full) == NULL ||
+ CU_add_test(suite, "test_crazy_rw",
+ test_crazy_rw) == NULL ||
+ CU_add_test(suite, "test_passthru",
+ test_passthru) == NULL ||
+ CU_add_test(suite, "test_initdrivers",
+ test_initdrivers) == NULL ||
+ CU_add_test(suite, "test_crypto_op_complete",
+ test_crypto_op_complete) == NULL ||
+ CU_add_test(suite, "test_supported_io",
+ test_supported_io) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/rte_crypto.h b/src/spdk/test/unit/lib/bdev/crypto.c/rte_crypto.h
new file mode 100644
index 00000000..a53a71df
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/crypto.c/rte_crypto.h
@@ -0,0 +1,95 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright (c) Intel Corporation.
+ * Copyright(c) 2016 6WIND S.A.
+ * 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 INTERRUcryptoION) 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 _RTE_CRYPTO_H_
+#define _RTE_CRYPTO_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* In order to mock some DPDK functions, we place headers here with the name name as the DPDK headers
+ * so these definitions wil be picked up. Only what's mocked is included.
+ */
+
+#include "rte_mbuf.h"
+#include "rte_mempool.h"
+#include "rte_crypto_sym.h"
+
+enum rte_crypto_op_type {
+ RTE_CRYPTO_OP_TYPE_UNDEFINED,
+ RTE_CRYPTO_OP_TYPE_SYMMETRIC,
+};
+
+enum rte_crypto_op_status {
+ RTE_CRYPTO_OP_STATUS_SUCCESS,
+ RTE_CRYPTO_OP_STATUS_NOT_PROCESSED,
+ RTE_CRYPTO_OP_STATUS_AUTH_FAILED,
+ RTE_CRYPTO_OP_STATUS_INVALID_SESSION,
+ RTE_CRYPTO_OP_STATUS_INVALID_ARGS,
+ RTE_CRYPTO_OP_STATUS_ERROR,
+};
+
+struct rte_crypto_op {
+ uint8_t type;
+ uint8_t status;
+ uint8_t sess_type;
+ uint8_t reserved[5];
+ struct rte_mempool *mempool;
+ rte_iova_t phys_addr;
+ __extension__
+ union {
+ struct rte_crypto_sym_op sym[0];
+ };
+};
+
+extern struct rte_mempool *
+rte_crypto_op_pool_create(const char *name, enum rte_crypto_op_type type,
+ unsigned nb_elts, unsigned cache_size, uint16_t priv_size,
+ int socket_id);
+
+static inline unsigned
+rte_crypto_op_bulk_alloc(struct rte_mempool *mempool,
+ enum rte_crypto_op_type type,
+ struct rte_crypto_op **ops, uint16_t nb_ops);
+
+static inline int
+rte_crypto_op_attach_sym_session(struct rte_crypto_op *op,
+ struct rte_cryptodev_sym_session *sess);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/rte_cryptodev.h b/src/spdk/test/unit/lib/bdev/crypto.c/rte_cryptodev.h
new file mode 100644
index 00000000..b941a20d
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/crypto.c/rte_cryptodev.h
@@ -0,0 +1,153 @@
+/*-
+ *
+ * Copyright(c) 2015-2017 Intel Corporation. All rights reserved.
+ * Copyright 2014 6WIND S.A.
+ *
+ * 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 _RTE_CRYPTODEV_H_
+#define _RTE_CRYPTODEV_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* In order to mock some DPDK functions, we place headers here with the name name as the DPDK headers
+ * so these definitions wil be picked up. Only what's mocked is included.
+ */
+
+uint8_t dummy[16];
+#define rte_crypto_op_ctod_offset(c, t, o) &dummy[0]
+
+#define RTE_CRYPTODEV_FF_MBUF_SCATTER_GATHER (1ULL << 9)
+
+struct rte_cryptodev_info {
+ const char *driver_name;
+ uint8_t driver_id;
+ struct rte_pci_device *pci_dev;
+ uint64_t feature_flags;
+ const struct rte_cryptodev_capabilities *capabilities;
+ unsigned max_nb_queue_pairs;
+ struct {
+ unsigned max_nb_sessions;
+ unsigned int max_nb_sessions_per_qp;
+ } sym;
+};
+
+enum rte_cryptodev_event_type {
+ RTE_CRYPTODEV_EVENT_UNKNOWN,
+ RTE_CRYPTODEV_EVENT_ERROR,
+ RTE_CRYPTODEV_EVENT_MAX
+};
+
+struct rte_cryptodev_qp_conf {
+ uint32_t nb_descriptors;
+};
+
+struct rte_cryptodev_stats {
+ uint64_t enqueued_count;
+ uint64_t dequeued_count;
+ uint64_t enqueue_err_count;
+ uint64_t dequeue_err_count;
+};
+
+#define RTE_CRYPTODEV_NAME_MAX_LEN (64)
+
+extern uint8_t
+rte_cryptodev_count(void);
+
+extern uint8_t
+rte_cryptodev_device_count_by_driver(uint8_t driver_id);
+
+extern int
+rte_cryptodev_socket_id(uint8_t dev_id);
+
+struct rte_cryptodev_config {
+ int socket_id;
+ uint16_t nb_queue_pairs;
+};
+
+extern int
+rte_cryptodev_configure(uint8_t dev_id, struct rte_cryptodev_config *config);
+
+extern int
+rte_cryptodev_start(uint8_t dev_id);
+
+extern void
+rte_cryptodev_stop(uint8_t dev_id);
+
+extern int
+rte_cryptodev_queue_pair_setup(uint8_t dev_id, uint16_t queue_pair_id,
+ const struct rte_cryptodev_qp_conf *qp_conf, int socket_id,
+ struct rte_mempool *session_pool);
+
+extern void
+rte_cryptodev_info_get(uint8_t dev_id, struct rte_cryptodev_info *dev_info);
+
+static inline uint16_t
+rte_cryptodev_dequeue_burst(uint8_t dev_id, uint16_t qp_id,
+ struct rte_crypto_op **ops, uint16_t nb_ops);
+
+static inline uint16_t
+rte_cryptodev_enqueue_burst(uint8_t dev_id, uint16_t qp_id,
+ struct rte_crypto_op **ops, uint16_t nb_ops);
+
+struct rte_cryptodev_sym_session {
+ __extension__ void *sess_private_data[0];
+};
+
+struct rte_cryptodev_asym_session {
+ __extension__ void *sess_private_data[0];
+};
+
+struct rte_crypto_asym_xform;
+
+struct rte_cryptodev_sym_session *
+rte_cryptodev_sym_session_create(struct rte_mempool *mempool);
+
+int
+rte_cryptodev_sym_session_free(struct rte_cryptodev_sym_session *sess);
+
+int
+rte_cryptodev_sym_session_init(uint8_t dev_id,
+ struct rte_cryptodev_sym_session *sess,
+ struct rte_crypto_sym_xform *xforms,
+ struct rte_mempool *mempool);
+
+int
+rte_cryptodev_sym_session_clear(uint8_t dev_id,
+ struct rte_cryptodev_sym_session *sess);
+
+unsigned int
+rte_cryptodev_sym_get_private_session_size(uint8_t dev_id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/rte_mbuf.h b/src/spdk/test/unit/lib/bdev/crypto.c/rte_mbuf.h
new file mode 100644
index 00000000..4d69f482
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/crypto.c/rte_mbuf.h
@@ -0,0 +1,148 @@
+/*-
+ *
+ * Copyright(c) 2015-2017 Intel Corporation. All rights reserved.
+ * Copyright 2014 6WIND S.A.
+ *
+ * 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 _RTE_MBUF_H_
+#define _RTE_MBUF_H_
+
+#include "rte_mempool.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* In order to mock some DPDK functions, we place headers here with the name name as the DPDK headers
+ * so these definitions wil be picked up. Only what's mocked is included.
+ */
+
+__extension__
+typedef void *MARKER[0];
+__extension__
+typedef uint8_t MARKER8[0];
+__extension__
+typedef uint64_t MARKER64[0];
+
+struct rte_mbuf {
+ MARKER cacheline0;
+ void *buf_addr;
+ RTE_STD_C11
+ union {
+ rte_iova_t buf_iova;
+ rte_iova_t buf_physaddr;
+ } __rte_aligned(sizeof(rte_iova_t));
+ MARKER64 rearm_data;
+ uint16_t data_off;
+ RTE_STD_C11
+ union {
+ rte_atomic16_t refcnt_atomic;
+ uint16_t refcnt;
+ };
+ uint16_t nb_segs;
+ uint16_t port;
+ uint64_t ol_flags;
+ MARKER rx_descriptor_fields1;
+ RTE_STD_C11
+ union {
+ uint32_t packet_type;
+ struct {
+ uint32_t l2_type: 4;
+ uint32_t l3_type: 4;
+ uint32_t l4_type: 4;
+ uint32_t tun_type: 4;
+ RTE_STD_C11
+ union {
+ uint8_t inner_esp_next_proto;
+ __extension__
+ struct {
+ uint8_t inner_l2_type: 4;
+ uint8_t inner_l3_type: 4;
+ };
+ };
+ uint32_t inner_l4_type: 4;
+ };
+ };
+ uint32_t pkt_len;
+ uint16_t data_len;
+ uint16_t vlan_tci;
+ union {
+ uint32_t rss;
+ struct {
+ RTE_STD_C11
+ union {
+ struct {
+ uint16_t hash;
+ uint16_t id;
+ };
+ uint32_t lo;
+ };
+ uint32_t hi;
+ } fdir;
+ struct {
+ uint32_t lo;
+ uint32_t hi;
+ } sched;
+ uint32_t usr;
+ } hash;
+ uint16_t vlan_tci_outer;
+ uint16_t buf_len;
+ uint64_t timestamp;
+ MARKER cacheline1 __rte_cache_min_aligned;
+ RTE_STD_C11
+ union {
+ void *userdata;
+ uint64_t udata64;
+ };
+ struct rte_mempool *pool;
+ struct rte_mbuf *next;
+ RTE_STD_C11
+ union {
+ uint64_t tx_offload;
+ __extension__
+ struct {
+ uint64_t l2_len: 7;
+ uint64_t l3_len: 9;
+ uint64_t l4_len: 8;
+ uint64_t tso_segsz: 16;
+ uint64_t outer_l3_len: 9;
+ uint64_t outer_l2_len: 7;
+ };
+ };
+ uint16_t priv_size;
+ uint16_t timesync;
+ uint32_t seqn;
+
+} __rte_cache_aligned;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/spdk/test/unit/lib/bdev/crypto.c/rte_mempool.h b/src/spdk/test/unit/lib/bdev/crypto.c/rte_mempool.h
new file mode 100644
index 00000000..5750d30f
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/crypto.c/rte_mempool.h
@@ -0,0 +1,145 @@
+/*-
+ *
+ * Copyright(c) 2015-2017 Intel Corporation. All rights reserved.
+ * Copyright 2014 6WIND S.A.
+ *
+ * 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 _RTE_MEMPOOL_H_
+#define _RTE_MEMPOOL_H_
+
+/**
+ * @file
+ * RTE Mempool.
+ *
+ * A memory pool is an allocator of fixed-size object. It is
+ * identified by its name, and uses a ring to store free objects. It
+ * provides some other optional services, like a per-core object
+ * cache, and an alignment helper to ensure that objects are padded
+ * to spread them equally on all RAM channels, ranks, and so on.
+ *
+ * Objects owned by a mempool should never be added in another
+ * mempool. When an object is freed using rte_mempool_put() or
+ * equivalent, the object data is not modified; the user can save some
+ * meta-data in the object data and retrieve them when allocating a
+ * new object.
+ *
+ * Note: the mempool implementation is not preemptible. An lcore must not be
+ * interrupted by another task that uses the same mempool (because it uses a
+ * ring which is not preemptible). Also, usual mempool functions like
+ * rte_mempool_get() or rte_mempool_put() are designed to be called from an EAL
+ * thread due to the internal per-lcore cache. Due to the lack of caching,
+ * rte_mempool_get() or rte_mempool_put() performance will suffer when called
+ * by non-EAL threads. Instead, non-EAL threads should call
+ * rte_mempool_generic_get() or rte_mempool_generic_put() with a user cache
+ * created with rte_mempool_cache_create().
+ */
+
+#include <rte_config.h>
+#include <rte_spinlock.h>
+#include <rte_debug.h>
+#include <rte_ring.h>
+#include <rte_memcpy.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* In order to mock some DPDK functions, we place headers here with the name name as the DPDK headers
+ * so these definitions wil be picked up. Only what's mocked is included.
+ */
+
+STAILQ_HEAD(rte_mempool_objhdr_list, rte_mempool_objhdr);
+STAILQ_HEAD(rte_mempool_memhdr_list, rte_mempool_memhdr);
+struct rte_mempool {
+ char name[RTE_MEMZONE_NAMESIZE];
+ RTE_STD_C11
+ union {
+ void *pool_data;
+ uint64_t pool_id;
+ };
+ void *pool_config;
+ const struct rte_memzone *mz;
+ unsigned int flags;
+ int socket_id;
+ uint32_t size;
+ uint32_t cache_size;
+ uint32_t elt_size;
+ uint32_t header_size;
+ uint32_t trailer_size;
+ unsigned private_data_size;
+ int32_t ops_index;
+ struct rte_mempool_cache *local_cache;
+ uint32_t populated_size;
+ struct rte_mempool_objhdr_list elt_list;
+ uint32_t nb_mem_chunks;
+ struct rte_mempool_memhdr_list mem_list;
+#ifdef RTE_LIBRTE_MEMPOOL_DEBUG
+ struct rte_mempool_debug_stats stats[RTE_MAX_LCORE];
+#endif
+} __rte_cache_aligned;
+#define RTE_MEMPOOL_OPS_NAMESIZE 32
+typedef int (*rte_mempool_alloc_t)(struct rte_mempool *mp);
+typedef void (*rte_mempool_free_t)(struct rte_mempool *mp);
+typedef int (*rte_mempool_enqueue_t)(struct rte_mempool *mp,
+ void *const *obj_table, unsigned int n);
+typedef int (*rte_mempool_dequeue_t)(struct rte_mempool *mp,
+ void **obj_table, unsigned int n);
+typedef unsigned(*rte_mempool_get_count)(const struct rte_mempool *mp);
+typedef int (*rte_mempool_get_capabilities_t)(const struct rte_mempool *mp,
+ unsigned int *flags);
+typedef int (*rte_mempool_ops_register_memory_area_t)
+(const struct rte_mempool *mp, char *vaddr, rte_iova_t iova, size_t len);
+struct rte_mempool_ops {
+ char name[RTE_MEMPOOL_OPS_NAMESIZE];
+ rte_mempool_alloc_t alloc;
+ rte_mempool_free_t free;
+ rte_mempool_enqueue_t enqueue;
+ rte_mempool_dequeue_t dequeue;
+ rte_mempool_get_count get_count;
+ rte_mempool_get_capabilities_t get_capabilities;
+ rte_mempool_ops_register_memory_area_t register_memory_area;
+} __rte_cache_aligned;
+#define RTE_MEMPOOL_MAX_OPS_IDX 16
+struct rte_mempool_ops_table {
+ rte_spinlock_t sl;
+ uint32_t num_ops;
+ struct rte_mempool_ops ops[RTE_MEMPOOL_MAX_OPS_IDX];
+} __rte_cache_aligned;
+extern struct rte_mempool_ops_table rte_mempool_ops_table;
+void
+rte_mempool_free(struct rte_mempool *mp);
+static __rte_always_inline void
+rte_mempool_put_bulk(struct rte_mempool *mp, void *const *obj_table,
+ unsigned int n);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_MEMPOOL_H_ */
diff --git a/src/spdk/test/unit/lib/bdev/gpt/Makefile b/src/spdk/test/unit/lib/bdev/gpt/Makefile
new file mode 100644
index 00000000..2fad9ba0
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/gpt/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 = gpt.c
+
+.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/unit/lib/bdev/gpt/gpt.c/.gitignore b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/.gitignore
new file mode 100644
index 00000000..74d476f5
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/.gitignore
@@ -0,0 +1 @@
+gpt_ut
diff --git a/src/spdk/test/unit/lib/bdev/gpt/gpt.c/Makefile b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/Makefile
new file mode 100644
index 00000000..ad21ea2a
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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 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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+TEST_FILE = gpt_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/bdev/gpt/gpt.c/gpt_ut.c b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/gpt_ut.c
new file mode 100644
index 00000000..3182f9c4
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/gpt/gpt.c/gpt_ut.c
@@ -0,0 +1,297 @@
+/*-
+ * 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 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.
+ */
+
+#include "spdk_cunit.h"
+
+#include "common/lib/test_env.c"
+
+#include "bdev/gpt/gpt.c"
+
+static void
+test_check_mbr(void)
+{
+ struct spdk_gpt *gpt;
+ struct spdk_mbr *mbr;
+ unsigned char a[SPDK_GPT_BUFFER_SIZE];
+ int re;
+
+ /* spdk_gpt_check_mbr(NULL) does not exist, NULL is filtered out in spdk_gpt_parse() */
+ gpt = calloc(1, sizeof(*gpt));
+ SPDK_CU_ASSERT_FATAL(gpt != NULL);
+
+ /* Set *gpt is "aaa...", all are mismatch include mbr_signature */
+ memset(a, 'a', sizeof(a));
+ gpt->buf = &a[0];
+ re = spdk_gpt_check_mbr(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set mbr->mbr_signature matched, start lba mismatch */
+ mbr = (struct spdk_mbr *)gpt->buf;
+ mbr->mbr_signature = 0xAA55;
+ re = spdk_gpt_check_mbr(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set mbr->partitions[0].start lba matched, os_type mismatch */
+ mbr->partitions[0].start_lba = 1;
+ re = spdk_gpt_check_mbr(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set mbr->partitions[0].os_type matched, size_lba mismatch */
+ mbr->partitions[0].os_type = 0xEE;
+ re = spdk_gpt_check_mbr(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set mbr->partitions[0].size_lba matched, passing case */
+ mbr->partitions[0].size_lba = 0xFFFFFFFF;
+ re = spdk_gpt_check_mbr(gpt);
+ CU_ASSERT(re == 0);
+
+ free(gpt);
+}
+
+static void
+test_read_header(void)
+{
+ struct spdk_gpt *gpt;
+ struct spdk_gpt_header *head;
+ unsigned char a[SPDK_GPT_BUFFER_SIZE];
+ int re;
+
+ /* spdk_gpt_read_header(NULL) does not exist, NULL is filtered out in spdk_gpt_parse() */
+ gpt = calloc(1, sizeof(*gpt));
+ SPDK_CU_ASSERT_FATAL(gpt != NULL);
+
+ /* Set *gpt is "aaa..." */
+ memset(a, 'a', sizeof(a));
+ gpt->buf = &a[0];
+
+ /* Set header_size mismatch */
+ gpt->sector_size = 512;
+ head = (struct spdk_gpt_header *)(gpt->buf + GPT_PRIMARY_PARTITION_TABLE_LBA * gpt->sector_size);
+ to_le32(&head->header_size, 0x258);
+ re = spdk_gpt_read_header(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set head->header_size matched, header_crc32 mismatch */
+ head->header_size = sizeof(*head);
+ to_le32(&head->header_crc32, 0x22D18C80);
+ re = spdk_gpt_read_header(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set head->header_crc32 matched, gpt_signature mismatch */
+ to_le32(&head->header_crc32, 0xC5B2117E);
+ re = spdk_gpt_read_header(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set head->gpt_signature matched, lba_end usable_lba mismatch */
+ to_le32(&head->header_crc32, 0xD637335A);
+ head->gpt_signature[0] = 'E';
+ head->gpt_signature[1] = 'F';
+ head->gpt_signature[2] = 'I';
+ head->gpt_signature[3] = ' ';
+ head->gpt_signature[4] = 'P';
+ head->gpt_signature[5] = 'A';
+ head->gpt_signature[6] = 'R';
+ head->gpt_signature[7] = 'T';
+ re = spdk_gpt_read_header(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set gpt->lba_end usable_lba matched, passing case */
+ to_le32(&head->header_crc32, 0x30CB7378);
+ to_le64(&gpt->lba_start, 0x0);
+ to_le64(&gpt->lba_end, 0x2E935FFE);
+ to_le64(&head->first_usable_lba, 0xA);
+ to_le64(&head->last_usable_lba, 0xF4240);
+ re = spdk_gpt_read_header(gpt);
+ CU_ASSERT(re == 0);
+
+ free(gpt);
+}
+
+static void
+test_read_partitions(void)
+{
+ struct spdk_gpt *gpt;
+ struct spdk_gpt_header *head;
+ unsigned char a[SPDK_GPT_BUFFER_SIZE];
+ int re;
+
+ /* spdk_gpt_read_partitions(NULL) does not exist, NULL is filtered out in spdk_gpt_parse() */
+ gpt = calloc(1, sizeof(*gpt));
+ SPDK_CU_ASSERT_FATAL(gpt != NULL);
+
+ /* Set *gpt is "aaa..." */
+ memset(a, 'a', sizeof(a));
+ gpt->buf = &a[0];
+
+ /* Set num_partition_entries exceeds Max value of entries GPT supported */
+ gpt->sector_size = 512;
+ head = (struct spdk_gpt_header *)(gpt->buf + GPT_PRIMARY_PARTITION_TABLE_LBA * gpt->sector_size);
+ gpt->header = head;
+ to_le32(&head->num_partition_entries, 0x100);
+ re = spdk_gpt_read_partitions(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set num_partition_entries within Max value, size_of_partition_entry mismatch */
+ to_le32(&head->header_crc32, 0x573857BE);
+ to_le32(&head->num_partition_entries, 0x40);
+ to_le32(&head->size_of_partition_entry, 0x0);
+ re = spdk_gpt_read_partitions(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set size_of_partition_entry matched, partition_entry_lba mismatch */
+ to_le32(&head->header_crc32, 0x5279B712);
+ to_le32(&head->size_of_partition_entry, 0x80);
+ to_le64(&head->partition_entry_lba, 0x64);
+ re = spdk_gpt_read_partitions(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set partition_entry_lba matched, partition_entry_array_crc32 mismatch */
+ to_le32(&head->header_crc32, 0xEC093B43);
+ to_le64(&head->partition_entry_lba, 0x20);
+ to_le32(&head->partition_entry_array_crc32, 0x0);
+ re = spdk_gpt_read_partitions(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set partition_entry_array_crc32 matched, passing case */
+ to_le32(&head->header_crc32, 0xE1A08822);
+ to_le32(&head->partition_entry_array_crc32, 0xEBEE44FB);
+ to_le32(&head->num_partition_entries, 0x80);
+ re = spdk_gpt_read_partitions(gpt);
+ CU_ASSERT(re == 0);
+
+ free(gpt);
+}
+
+static void
+test_parse(void)
+{
+ struct spdk_gpt *gpt;
+ struct spdk_mbr *mbr;
+ struct spdk_gpt_header *head;
+ unsigned char a[SPDK_GPT_BUFFER_SIZE];
+ int re;
+
+ /* Set gpt is NULL */
+ re = spdk_gpt_parse(NULL);
+ CU_ASSERT(re == -1);
+
+ /* Set gpt->buf is NULL */
+ gpt = calloc(1, sizeof(*gpt));
+ SPDK_CU_ASSERT_FATAL(gpt != NULL);
+ re = spdk_gpt_parse(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set *gpt is "aaa...", check_mbr failed */
+ memset(a, 'a', sizeof(a));
+ gpt->buf = &a[0];
+ re = spdk_gpt_parse(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set check_mbr passed, read_header failed */
+ mbr = (struct spdk_mbr *)gpt->buf;
+ mbr->mbr_signature = 0xAA55;
+ mbr->partitions[0].start_lba = 1;
+ mbr->partitions[0].os_type = 0xEE;
+ mbr->partitions[0].size_lba = 0xFFFFFFFF;
+ re = spdk_gpt_parse(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set read_header passed, read_partitions failed */
+ gpt->sector_size = 512;
+ head = (struct spdk_gpt_header *)(gpt->buf + GPT_PRIMARY_PARTITION_TABLE_LBA * gpt->sector_size);
+ head->header_size = sizeof(*head);
+ head->gpt_signature[0] = 'E';
+ head->gpt_signature[1] = 'F';
+ head->gpt_signature[2] = 'I';
+ head->gpt_signature[3] = ' ';
+ head->gpt_signature[4] = 'P';
+ head->gpt_signature[5] = 'A';
+ head->gpt_signature[6] = 'R';
+ head->gpt_signature[7] = 'T';
+ to_le32(&head->header_crc32, 0x30CB7378);
+ to_le64(&gpt->lba_start, 0x0);
+ to_le64(&gpt->lba_end, 0x2E935FFE);
+ to_le64(&head->first_usable_lba, 0xA);
+ to_le64(&head->last_usable_lba, 0xF4240);
+ re = spdk_gpt_parse(gpt);
+ CU_ASSERT(re == -1);
+
+ /* Set read_partitions passed, all passed */
+ to_le32(&head->size_of_partition_entry, 0x80);
+ to_le64(&head->partition_entry_lba, 0x20);
+ to_le32(&head->header_crc32, 0xE1A08822);
+ to_le32(&head->partition_entry_array_crc32, 0xEBEE44FB);
+ to_le32(&head->num_partition_entries, 0x80);
+ re = spdk_gpt_parse(gpt);
+ CU_ASSERT(re == 0);
+
+ free(gpt);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("gpt_parse", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "parse",
+ test_parse) == NULL ||
+ CU_add_test(suite, "check mbr",
+ test_check_mbr) == NULL ||
+ CU_add_test(suite, "read header",
+ test_read_header) == NULL ||
+ CU_add_test(suite, "read partitions",
+ test_read_partitions) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/bdev/mt/Makefile b/src/spdk/test/unit/lib/bdev/mt/Makefile
new file mode 100644
index 00000000..a19b345a
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/mt/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.c
+
+.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/unit/lib/bdev/mt/bdev.c/.gitignore b/src/spdk/test/unit/lib/bdev/mt/bdev.c/.gitignore
new file mode 100644
index 00000000..a5a22d0d
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/mt/bdev.c/.gitignore
@@ -0,0 +1 @@
+bdev_ut
diff --git a/src/spdk/test/unit/lib/bdev/mt/bdev.c/Makefile b/src/spdk/test/unit/lib/bdev/mt/bdev.c/Makefile
new file mode 100644
index 00000000..96b48574
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/mt/bdev.c/Makefile
@@ -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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.mock.unittest.mk
+
+TEST_FILE = bdev_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/bdev/mt/bdev.c/bdev_ut.c b/src/spdk/test/unit/lib/bdev/mt/bdev.c/bdev_ut.c
new file mode 100644
index 00000000..09740fa9
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/mt/bdev.c/bdev_ut.c
@@ -0,0 +1,1360 @@
+/*-
+ * 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_cunit.h"
+
+#include "common/lib/test_env.c"
+#include "common/lib/ut_multithread.c"
+#include "unit/lib/json_mock.c"
+
+#include "spdk/config.h"
+/* HACK: disable VTune integration so the unit test doesn't need VTune headers and libs to build */
+#undef SPDK_CONFIG_VTUNE
+
+#include "bdev/bdev.c"
+
+#define BDEV_UT_NUM_THREADS 3
+
+DEFINE_STUB_V(spdk_scsi_nvme_translate, (const struct spdk_bdev_io *bdev_io,
+ int *sc, int *sk, int *asc, int *ascq));
+
+DEFINE_STUB(spdk_conf_find_section, struct spdk_conf_section *, (struct spdk_conf *cp,
+ const char *name), NULL);
+DEFINE_STUB(spdk_conf_section_get_nmval, char *,
+ (struct spdk_conf_section *sp, const char *key, int idx1, int idx2), NULL);
+DEFINE_STUB(spdk_conf_section_get_intval, int, (struct spdk_conf_section *sp, const char *key), -1);
+
+struct spdk_trace_histories *g_trace_histories;
+DEFINE_STUB_V(spdk_trace_add_register_fn, (struct spdk_trace_register_fn *reg_fn));
+DEFINE_STUB_V(spdk_trace_register_owner, (uint8_t type, char id_prefix));
+DEFINE_STUB_V(spdk_trace_register_object, (uint8_t type, char id_prefix));
+DEFINE_STUB_V(spdk_trace_register_description, (const char *name, const char *short_name,
+ uint16_t tpoint_id, uint8_t owner_type,
+ uint8_t object_type, uint8_t new_object,
+ uint8_t arg1_is_ptr, const char *arg1_name));
+DEFINE_STUB_V(_spdk_trace_record, (uint64_t tsc, uint16_t tpoint_id, uint16_t poller_id,
+ uint32_t size, uint64_t object_id, uint64_t arg1));
+
+struct ut_bdev {
+ struct spdk_bdev bdev;
+ void *io_target;
+};
+
+struct ut_bdev_channel {
+ TAILQ_HEAD(, spdk_bdev_io) outstanding_io;
+ uint32_t outstanding_cnt;
+ uint32_t avail_cnt;
+};
+
+int g_io_device;
+struct ut_bdev g_bdev;
+struct spdk_bdev_desc *g_desc;
+bool g_teardown_done = false;
+bool g_get_io_channel = true;
+bool g_create_ch = true;
+bool g_init_complete_called = false;
+bool g_fini_start_called = true;
+
+static int
+stub_create_ch(void *io_device, void *ctx_buf)
+{
+ struct ut_bdev_channel *ch = ctx_buf;
+
+ if (g_create_ch == false) {
+ return -1;
+ }
+
+ TAILQ_INIT(&ch->outstanding_io);
+ ch->outstanding_cnt = 0;
+ /*
+ * When avail gets to 0, the submit_request function will return ENOMEM.
+ * Most tests to not want ENOMEM to occur, so by default set this to a
+ * big value that won't get hit. The ENOMEM tests can then override this
+ * value to something much smaller to induce ENOMEM conditions.
+ */
+ ch->avail_cnt = 2048;
+ return 0;
+}
+
+static void
+stub_destroy_ch(void *io_device, void *ctx_buf)
+{
+}
+
+static struct spdk_io_channel *
+stub_get_io_channel(void *ctx)
+{
+ struct ut_bdev *ut_bdev = ctx;
+
+ if (g_get_io_channel == true) {
+ return spdk_get_io_channel(ut_bdev->io_target);
+ } else {
+ return NULL;
+ }
+}
+
+static int
+stub_destruct(void *ctx)
+{
+ return 0;
+}
+
+static void
+stub_submit_request(struct spdk_io_channel *_ch, struct spdk_bdev_io *bdev_io)
+{
+ struct ut_bdev_channel *ch = spdk_io_channel_get_ctx(_ch);
+
+ if (bdev_io->type == SPDK_BDEV_IO_TYPE_RESET) {
+ struct spdk_bdev_io *io;
+
+ while (!TAILQ_EMPTY(&ch->outstanding_io)) {
+ io = TAILQ_FIRST(&ch->outstanding_io);
+ TAILQ_REMOVE(&ch->outstanding_io, io, module_link);
+ ch->outstanding_cnt--;
+ spdk_bdev_io_complete(io, SPDK_BDEV_IO_STATUS_FAILED);
+ ch->avail_cnt++;
+ }
+ }
+
+ if (ch->avail_cnt > 0) {
+ TAILQ_INSERT_TAIL(&ch->outstanding_io, bdev_io, module_link);
+ ch->outstanding_cnt++;
+ ch->avail_cnt--;
+ } else {
+ spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_NOMEM);
+ }
+}
+
+static uint32_t
+stub_complete_io(void *io_target, uint32_t num_to_complete)
+{
+ struct spdk_io_channel *_ch = spdk_get_io_channel(io_target);
+ struct ut_bdev_channel *ch = spdk_io_channel_get_ctx(_ch);
+ struct spdk_bdev_io *io;
+ bool complete_all = (num_to_complete == 0);
+ uint32_t num_completed = 0;
+
+ while (complete_all || num_completed < num_to_complete) {
+ if (TAILQ_EMPTY(&ch->outstanding_io)) {
+ break;
+ }
+ io = TAILQ_FIRST(&ch->outstanding_io);
+ TAILQ_REMOVE(&ch->outstanding_io, io, module_link);
+ ch->outstanding_cnt--;
+ spdk_bdev_io_complete(io, SPDK_BDEV_IO_STATUS_SUCCESS);
+ ch->avail_cnt++;
+ num_completed++;
+ }
+
+ spdk_put_io_channel(_ch);
+ return num_completed;
+}
+
+static struct spdk_bdev_fn_table fn_table = {
+ .get_io_channel = stub_get_io_channel,
+ .destruct = stub_destruct,
+ .submit_request = stub_submit_request,
+};
+
+static int
+module_init(void)
+{
+ return 0;
+}
+
+static void
+module_fini(void)
+{
+}
+
+static void
+init_complete(void)
+{
+ g_init_complete_called = true;
+}
+
+static void
+fini_start(void)
+{
+ g_fini_start_called = true;
+}
+
+struct spdk_bdev_module bdev_ut_if = {
+ .name = "bdev_ut",
+ .module_init = module_init,
+ .module_fini = module_fini,
+ .init_complete = init_complete,
+ .fini_start = fini_start,
+};
+
+SPDK_BDEV_MODULE_REGISTER(&bdev_ut_if)
+
+static void
+register_bdev(struct ut_bdev *ut_bdev, char *name, void *io_target)
+{
+ memset(ut_bdev, 0, sizeof(*ut_bdev));
+
+ ut_bdev->io_target = io_target;
+ ut_bdev->bdev.ctxt = ut_bdev;
+ ut_bdev->bdev.name = name;
+ ut_bdev->bdev.fn_table = &fn_table;
+ ut_bdev->bdev.module = &bdev_ut_if;
+ ut_bdev->bdev.blocklen = 4096;
+ ut_bdev->bdev.blockcnt = 1024;
+
+ spdk_bdev_register(&ut_bdev->bdev);
+}
+
+static void
+unregister_bdev(struct ut_bdev *ut_bdev)
+{
+ /* Handle any deferred messages. */
+ poll_threads();
+ spdk_bdev_unregister(&ut_bdev->bdev, NULL, NULL);
+}
+
+static void
+bdev_init_cb(void *done, int rc)
+{
+ CU_ASSERT(rc == 0);
+ *(bool *)done = true;
+}
+
+static void
+setup_test(void)
+{
+ bool done = false;
+
+ allocate_threads(BDEV_UT_NUM_THREADS);
+ set_thread(0);
+ spdk_bdev_initialize(bdev_init_cb, &done);
+ spdk_io_device_register(&g_io_device, stub_create_ch, stub_destroy_ch,
+ sizeof(struct ut_bdev_channel), NULL);
+ register_bdev(&g_bdev, "ut_bdev", &g_io_device);
+ spdk_bdev_open(&g_bdev.bdev, true, NULL, NULL, &g_desc);
+}
+
+static void
+finish_cb(void *cb_arg)
+{
+ g_teardown_done = true;
+}
+
+static void
+teardown_test(void)
+{
+ set_thread(0);
+ g_teardown_done = false;
+ spdk_bdev_close(g_desc);
+ g_desc = NULL;
+ unregister_bdev(&g_bdev);
+ spdk_io_device_unregister(&g_io_device, NULL);
+ spdk_bdev_finish(finish_cb, NULL);
+ poll_threads();
+ memset(&g_bdev, 0, sizeof(g_bdev));
+ CU_ASSERT(g_teardown_done == true);
+ g_teardown_done = false;
+ free_threads();
+}
+
+static uint32_t
+bdev_io_tailq_cnt(bdev_io_tailq_t *tailq)
+{
+ struct spdk_bdev_io *io;
+ uint32_t cnt = 0;
+
+ TAILQ_FOREACH(io, tailq, internal.link) {
+ cnt++;
+ }
+
+ return cnt;
+}
+
+static void
+basic(void)
+{
+ g_init_complete_called = false;
+ setup_test();
+ CU_ASSERT(g_init_complete_called == true);
+
+ set_thread(0);
+
+ g_get_io_channel = false;
+ g_ut_threads[0].ch = spdk_bdev_get_io_channel(g_desc);
+ CU_ASSERT(g_ut_threads[0].ch == NULL);
+
+ g_get_io_channel = true;
+ g_create_ch = false;
+ g_ut_threads[0].ch = spdk_bdev_get_io_channel(g_desc);
+ CU_ASSERT(g_ut_threads[0].ch == NULL);
+
+ g_get_io_channel = true;
+ g_create_ch = true;
+ g_ut_threads[0].ch = spdk_bdev_get_io_channel(g_desc);
+ CU_ASSERT(g_ut_threads[0].ch != NULL);
+ spdk_put_io_channel(g_ut_threads[0].ch);
+
+ g_fini_start_called = false;
+ teardown_test();
+ CU_ASSERT(g_fini_start_called == true);
+}
+
+static void
+_bdev_removed(void *done)
+{
+ *(bool *)done = true;
+}
+
+static void
+_bdev_unregistered(void *done, int rc)
+{
+ CU_ASSERT(rc == 0);
+ *(bool *)done = true;
+}
+
+static void
+unregister_and_close(void)
+{
+ bool done, remove_notify;
+ struct spdk_bdev_desc *desc;
+
+ setup_test();
+ set_thread(0);
+
+ /* setup_test() automatically opens the bdev,
+ * but this test needs to do that in a different
+ * way. */
+ spdk_bdev_close(g_desc);
+ poll_threads();
+
+ remove_notify = false;
+ spdk_bdev_open(&g_bdev.bdev, true, _bdev_removed, &remove_notify, &desc);
+ CU_ASSERT(remove_notify == false);
+ CU_ASSERT(desc != NULL);
+
+ /* There is an open descriptor on the device. Unregister it,
+ * which can't proceed until the descriptor is closed. */
+ done = false;
+ spdk_bdev_unregister(&g_bdev.bdev, _bdev_unregistered, &done);
+ /* No polling has occurred, so neither of these should execute */
+ CU_ASSERT(remove_notify == false);
+ CU_ASSERT(done == false);
+
+ /* Prior to the unregister completing, close the descriptor */
+ spdk_bdev_close(desc);
+
+ /* Poll the threads to allow all events to be processed */
+ poll_threads();
+
+ /* Remove notify should not have been called because the
+ * descriptor is already closed. */
+ CU_ASSERT(remove_notify == false);
+
+ /* The unregister should have completed */
+ CU_ASSERT(done == true);
+
+ spdk_bdev_finish(finish_cb, NULL);
+ poll_threads();
+ free_threads();
+}
+
+static void
+reset_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
+{
+ bool *done = cb_arg;
+
+ CU_ASSERT(success == true);
+ *done = true;
+ spdk_bdev_free_io(bdev_io);
+}
+
+static void
+put_channel_during_reset(void)
+{
+ struct spdk_io_channel *io_ch;
+ bool done = false;
+
+ setup_test();
+
+ set_thread(0);
+ io_ch = spdk_bdev_get_io_channel(g_desc);
+ CU_ASSERT(io_ch != NULL);
+
+ /*
+ * Start a reset, but then put the I/O channel before
+ * the deferred messages for the reset get a chance to
+ * execute.
+ */
+ spdk_bdev_reset(g_desc, io_ch, reset_done, &done);
+ spdk_put_io_channel(io_ch);
+ poll_threads();
+ stub_complete_io(g_bdev.io_target, 0);
+
+ teardown_test();
+}
+
+static void
+aborted_reset_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
+{
+ enum spdk_bdev_io_status *status = cb_arg;
+
+ *status = success ? SPDK_BDEV_IO_STATUS_SUCCESS : SPDK_BDEV_IO_STATUS_FAILED;
+ spdk_bdev_free_io(bdev_io);
+}
+
+static void
+aborted_reset(void)
+{
+ struct spdk_io_channel *io_ch[2];
+ enum spdk_bdev_io_status status1 = SPDK_BDEV_IO_STATUS_PENDING,
+ status2 = SPDK_BDEV_IO_STATUS_PENDING;
+
+ setup_test();
+
+ set_thread(0);
+ io_ch[0] = spdk_bdev_get_io_channel(g_desc);
+ CU_ASSERT(io_ch[0] != NULL);
+ spdk_bdev_reset(g_desc, io_ch[0], aborted_reset_done, &status1);
+ poll_threads();
+ CU_ASSERT(g_bdev.bdev.internal.reset_in_progress != NULL);
+
+ /*
+ * First reset has been submitted on ch0. Now submit a second
+ * reset on ch1 which will get queued since there is already a
+ * reset in progress.
+ */
+ set_thread(1);
+ io_ch[1] = spdk_bdev_get_io_channel(g_desc);
+ CU_ASSERT(io_ch[1] != NULL);
+ spdk_bdev_reset(g_desc, io_ch[1], aborted_reset_done, &status2);
+ poll_threads();
+ CU_ASSERT(g_bdev.bdev.internal.reset_in_progress != NULL);
+
+ /*
+ * Now destroy ch1. This will abort the queued reset. Check that
+ * the second reset was completed with failed status. Also check
+ * that bdev->internal.reset_in_progress != NULL, since the
+ * original reset has not been completed yet. This ensures that
+ * the bdev code is correctly noticing that the failed reset is
+ * *not* the one that had been submitted to the bdev module.
+ */
+ set_thread(1);
+ spdk_put_io_channel(io_ch[1]);
+ poll_threads();
+ CU_ASSERT(status2 == SPDK_BDEV_IO_STATUS_FAILED);
+ CU_ASSERT(g_bdev.bdev.internal.reset_in_progress != NULL);
+
+ /*
+ * Now complete the first reset, verify that it completed with SUCCESS
+ * status and that bdev->internal.reset_in_progress is also set back to NULL.
+ */
+ set_thread(0);
+ spdk_put_io_channel(io_ch[0]);
+ stub_complete_io(g_bdev.io_target, 0);
+ poll_threads();
+ CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(g_bdev.bdev.internal.reset_in_progress == NULL);
+
+ teardown_test();
+}
+
+static void
+io_during_io_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
+{
+ enum spdk_bdev_io_status *status = cb_arg;
+
+ *status = success ? SPDK_BDEV_IO_STATUS_SUCCESS : SPDK_BDEV_IO_STATUS_FAILED;
+ spdk_bdev_free_io(bdev_io);
+}
+
+static void
+io_during_reset(void)
+{
+ struct spdk_io_channel *io_ch[2];
+ struct spdk_bdev_channel *bdev_ch[2];
+ enum spdk_bdev_io_status status0, status1, status_reset;
+ int rc;
+
+ setup_test();
+
+ /*
+ * First test normal case - submit an I/O on each of two channels (with no resets)
+ * and verify they complete successfully.
+ */
+ set_thread(0);
+ io_ch[0] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]);
+ CU_ASSERT(bdev_ch[0]->flags == 0);
+ status0 = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &status0);
+ CU_ASSERT(rc == 0);
+
+ set_thread(1);
+ io_ch[1] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]);
+ CU_ASSERT(bdev_ch[1]->flags == 0);
+ status1 = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &status1);
+ CU_ASSERT(rc == 0);
+
+ poll_threads();
+ CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_PENDING);
+ CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_PENDING);
+
+ set_thread(0);
+ stub_complete_io(g_bdev.io_target, 0);
+ CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ set_thread(1);
+ stub_complete_io(g_bdev.io_target, 0);
+ CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ /*
+ * Now submit a reset, and leave it pending while we submit I/O on two different
+ * channels. These I/O should be failed by the bdev layer since the reset is in
+ * progress.
+ */
+ set_thread(0);
+ status_reset = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_reset(g_desc, io_ch[0], io_during_io_done, &status_reset);
+ CU_ASSERT(rc == 0);
+
+ CU_ASSERT(bdev_ch[0]->flags == 0);
+ CU_ASSERT(bdev_ch[1]->flags == 0);
+ poll_threads();
+ CU_ASSERT(bdev_ch[0]->flags == BDEV_CH_RESET_IN_PROGRESS);
+ CU_ASSERT(bdev_ch[1]->flags == BDEV_CH_RESET_IN_PROGRESS);
+
+ set_thread(0);
+ status0 = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &status0);
+ CU_ASSERT(rc == 0);
+
+ set_thread(1);
+ status1 = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &status1);
+ CU_ASSERT(rc == 0);
+
+ /*
+ * A reset is in progress so these read I/O should complete with failure. Note that we
+ * need to poll_threads() since I/O completed inline have their completion deferred.
+ */
+ poll_threads();
+ CU_ASSERT(status_reset == SPDK_BDEV_IO_STATUS_PENDING);
+ CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_FAILED);
+ CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_FAILED);
+
+ /*
+ * Complete the reset
+ */
+ set_thread(0);
+ stub_complete_io(g_bdev.io_target, 0);
+
+ /*
+ * Only poll thread 0. We should not get a completion.
+ */
+ poll_thread(0);
+ CU_ASSERT(status_reset == SPDK_BDEV_IO_STATUS_PENDING);
+
+ /*
+ * Poll both thread 0 and 1 so the messages can propagate and we
+ * get a completion.
+ */
+ poll_threads();
+ CU_ASSERT(status_reset == SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ spdk_put_io_channel(io_ch[0]);
+ set_thread(1);
+ spdk_put_io_channel(io_ch[1]);
+ poll_threads();
+
+ teardown_test();
+}
+
+static void
+basic_qos(void)
+{
+ struct spdk_io_channel *io_ch[2];
+ struct spdk_bdev_channel *bdev_ch[2];
+ struct spdk_bdev *bdev;
+ enum spdk_bdev_io_status status;
+ int rc;
+
+ setup_test();
+
+ /* Enable QoS */
+ bdev = &g_bdev.bdev;
+ bdev->internal.qos = calloc(1, sizeof(*bdev->internal.qos));
+ SPDK_CU_ASSERT_FATAL(bdev->internal.qos != NULL);
+ TAILQ_INIT(&bdev->internal.qos->queued);
+ /*
+ * Enable both IOPS and bandwidth rate limits.
+ * In this case, both rate limits will take equal effect.
+ */
+ /* 2000 I/O per second, or 2 per millisecond */
+ bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT].limit = 2000;
+ /* 8K byte per millisecond with 4K block size */
+ bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT].limit = 8192000;
+
+ g_get_io_channel = true;
+
+ set_thread(0);
+ io_ch[0] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]);
+ CU_ASSERT(bdev_ch[0]->flags == BDEV_CH_QOS_ENABLED);
+
+ set_thread(1);
+ io_ch[1] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]);
+ CU_ASSERT(bdev_ch[1]->flags == BDEV_CH_QOS_ENABLED);
+
+ /*
+ * Send an I/O on thread 0, which is where the QoS thread is running.
+ */
+ set_thread(0);
+ status = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &status);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(status == SPDK_BDEV_IO_STATUS_PENDING);
+ poll_threads();
+ stub_complete_io(g_bdev.io_target, 0);
+ poll_threads();
+ CU_ASSERT(status == SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ /* Send an I/O on thread 1. The QoS thread is not running here. */
+ status = SPDK_BDEV_IO_STATUS_PENDING;
+ set_thread(1);
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &status);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(status == SPDK_BDEV_IO_STATUS_PENDING);
+ poll_threads();
+ /* Complete I/O on thread 1. This should not complete the I/O we submitted */
+ stub_complete_io(g_bdev.io_target, 0);
+ poll_threads();
+ CU_ASSERT(status == SPDK_BDEV_IO_STATUS_PENDING);
+ /* Now complete I/O on thread 0 */
+ set_thread(0);
+ poll_threads();
+ stub_complete_io(g_bdev.io_target, 0);
+ poll_threads();
+ CU_ASSERT(status == SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ /* Tear down the channels */
+ set_thread(0);
+ spdk_put_io_channel(io_ch[0]);
+ set_thread(1);
+ spdk_put_io_channel(io_ch[1]);
+ poll_threads();
+ set_thread(0);
+
+ /* Close the descriptor, which should stop the qos channel */
+ spdk_bdev_close(g_desc);
+ poll_threads();
+ CU_ASSERT(bdev->internal.qos->ch == NULL);
+
+ spdk_bdev_open(bdev, true, NULL, NULL, &g_desc);
+
+ /* Create the channels in reverse order. */
+ set_thread(1);
+ io_ch[1] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]);
+ CU_ASSERT(bdev_ch[1]->flags == BDEV_CH_QOS_ENABLED);
+
+ set_thread(0);
+ io_ch[0] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]);
+ CU_ASSERT(bdev_ch[0]->flags == BDEV_CH_QOS_ENABLED);
+
+ /* Confirm that the qos thread is now thread 1 */
+ CU_ASSERT(bdev->internal.qos->ch == bdev_ch[1]);
+
+ /* Tear down the channels */
+ set_thread(0);
+ spdk_put_io_channel(io_ch[0]);
+ set_thread(1);
+ spdk_put_io_channel(io_ch[1]);
+ poll_threads();
+
+ set_thread(0);
+
+ teardown_test();
+}
+
+static void
+io_during_qos_queue(void)
+{
+ struct spdk_io_channel *io_ch[2];
+ struct spdk_bdev_channel *bdev_ch[2];
+ struct spdk_bdev *bdev;
+ enum spdk_bdev_io_status status0, status1;
+ int rc;
+
+ setup_test();
+ reset_time();
+
+ /* Enable QoS */
+ bdev = &g_bdev.bdev;
+ bdev->internal.qos = calloc(1, sizeof(*bdev->internal.qos));
+ SPDK_CU_ASSERT_FATAL(bdev->internal.qos != NULL);
+ TAILQ_INIT(&bdev->internal.qos->queued);
+ /*
+ * Enable both IOPS and bandwidth rate limits.
+ * In this case, IOPS rate limit will take effect first.
+ */
+ /* 1000 I/O per second, or 1 per millisecond */
+ bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT].limit = 1000;
+ /* 8K byte per millisecond with 4K block size */
+ bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT].limit = 8192000;
+
+ g_get_io_channel = true;
+
+ /* Create channels */
+ set_thread(0);
+ io_ch[0] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]);
+ CU_ASSERT(bdev_ch[0]->flags == BDEV_CH_QOS_ENABLED);
+
+ set_thread(1);
+ io_ch[1] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]);
+ CU_ASSERT(bdev_ch[1]->flags == BDEV_CH_QOS_ENABLED);
+
+ /* Send two I/O */
+ status1 = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &status1);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_PENDING);
+ set_thread(0);
+ status0 = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &status0);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_PENDING);
+
+ /* Complete any I/O that arrived at the disk */
+ poll_threads();
+ set_thread(1);
+ stub_complete_io(g_bdev.io_target, 0);
+ set_thread(0);
+ stub_complete_io(g_bdev.io_target, 0);
+ poll_threads();
+
+ /* Only one of the I/O should complete. (logical XOR) */
+ if (status0 == SPDK_BDEV_IO_STATUS_SUCCESS) {
+ CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_PENDING);
+ } else {
+ CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_SUCCESS);
+ }
+
+ /* Advance in time by a millisecond */
+ increment_time(1000);
+
+ /* Complete more I/O */
+ poll_threads();
+ set_thread(1);
+ stub_complete_io(g_bdev.io_target, 0);
+ set_thread(0);
+ stub_complete_io(g_bdev.io_target, 0);
+ poll_threads();
+
+ /* Now the second I/O should be done */
+ CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ /* Tear down the channels */
+ set_thread(1);
+ spdk_put_io_channel(io_ch[1]);
+ set_thread(0);
+ spdk_put_io_channel(io_ch[0]);
+ poll_threads();
+
+ teardown_test();
+}
+
+static void
+io_during_qos_reset(void)
+{
+ struct spdk_io_channel *io_ch[2];
+ struct spdk_bdev_channel *bdev_ch[2];
+ struct spdk_bdev *bdev;
+ enum spdk_bdev_io_status status0, status1, reset_status;
+ int rc;
+
+ setup_test();
+ reset_time();
+
+ /* Enable QoS */
+ bdev = &g_bdev.bdev;
+ bdev->internal.qos = calloc(1, sizeof(*bdev->internal.qos));
+ SPDK_CU_ASSERT_FATAL(bdev->internal.qos != NULL);
+ TAILQ_INIT(&bdev->internal.qos->queued);
+ /*
+ * Enable both IOPS and bandwidth rate limits.
+ * In this case, bandwidth rate limit will take effect first.
+ */
+ /* 2000 I/O per second, or 2 per millisecond */
+ bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT].limit = 2000;
+ /* 4K byte per millisecond with 4K block size */
+ bdev->internal.qos->rate_limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT].limit = 4096000;
+
+ g_get_io_channel = true;
+
+ /* Create channels */
+ set_thread(0);
+ io_ch[0] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]);
+ CU_ASSERT(bdev_ch[0]->flags == BDEV_CH_QOS_ENABLED);
+
+ set_thread(1);
+ io_ch[1] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]);
+ CU_ASSERT(bdev_ch[1]->flags == BDEV_CH_QOS_ENABLED);
+
+ /* Send two I/O. One of these gets queued by QoS. The other is sitting at the disk. */
+ status1 = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &status1);
+ CU_ASSERT(rc == 0);
+ set_thread(0);
+ status0 = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &status0);
+ CU_ASSERT(rc == 0);
+
+ poll_threads();
+ CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_PENDING);
+ CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_PENDING);
+
+ /* Reset the bdev. */
+ reset_status = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_reset(g_desc, io_ch[0], io_during_io_done, &reset_status);
+ CU_ASSERT(rc == 0);
+
+ /* Complete any I/O that arrived at the disk */
+ poll_threads();
+ set_thread(1);
+ stub_complete_io(g_bdev.io_target, 0);
+ set_thread(0);
+ stub_complete_io(g_bdev.io_target, 0);
+ poll_threads();
+
+ CU_ASSERT(reset_status == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(status0 == SPDK_BDEV_IO_STATUS_FAILED);
+ CU_ASSERT(status1 == SPDK_BDEV_IO_STATUS_FAILED);
+
+ /* Tear down the channels */
+ set_thread(1);
+ spdk_put_io_channel(io_ch[1]);
+ set_thread(0);
+ spdk_put_io_channel(io_ch[0]);
+ poll_threads();
+
+ teardown_test();
+}
+
+static void
+enomem_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
+{
+ enum spdk_bdev_io_status *status = cb_arg;
+
+ *status = success ? SPDK_BDEV_IO_STATUS_SUCCESS : SPDK_BDEV_IO_STATUS_FAILED;
+ spdk_bdev_free_io(bdev_io);
+}
+
+static void
+enomem(void)
+{
+ struct spdk_io_channel *io_ch;
+ struct spdk_bdev_channel *bdev_ch;
+ struct spdk_bdev_shared_resource *shared_resource;
+ struct ut_bdev_channel *ut_ch;
+ const uint32_t IO_ARRAY_SIZE = 64;
+ const uint32_t AVAIL = 20;
+ enum spdk_bdev_io_status status[IO_ARRAY_SIZE], status_reset;
+ uint32_t nomem_cnt, i;
+ struct spdk_bdev_io *first_io;
+ int rc;
+
+ setup_test();
+
+ set_thread(0);
+ io_ch = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch = spdk_io_channel_get_ctx(io_ch);
+ shared_resource = bdev_ch->shared_resource;
+ ut_ch = spdk_io_channel_get_ctx(bdev_ch->channel);
+ ut_ch->avail_cnt = AVAIL;
+
+ /* First submit a number of IOs equal to what the channel can support. */
+ for (i = 0; i < AVAIL; i++) {
+ status[i] = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch, NULL, 0, 1, enomem_done, &status[i]);
+ CU_ASSERT(rc == 0);
+ }
+ CU_ASSERT(TAILQ_EMPTY(&shared_resource->nomem_io));
+
+ /*
+ * Next, submit one additional I/O. This one should fail with ENOMEM and then go onto
+ * the enomem_io list.
+ */
+ status[AVAIL] = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch, NULL, 0, 1, enomem_done, &status[AVAIL]);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&shared_resource->nomem_io));
+ first_io = TAILQ_FIRST(&shared_resource->nomem_io);
+
+ /*
+ * Now submit a bunch more I/O. These should all fail with ENOMEM and get queued behind
+ * the first_io above.
+ */
+ for (i = AVAIL + 1; i < IO_ARRAY_SIZE; i++) {
+ status[i] = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch, NULL, 0, 1, enomem_done, &status[i]);
+ CU_ASSERT(rc == 0);
+ }
+
+ /* Assert that first_io is still at the head of the list. */
+ CU_ASSERT(TAILQ_FIRST(&shared_resource->nomem_io) == first_io);
+ CU_ASSERT(bdev_io_tailq_cnt(&shared_resource->nomem_io) == (IO_ARRAY_SIZE - AVAIL));
+ nomem_cnt = bdev_io_tailq_cnt(&shared_resource->nomem_io);
+ CU_ASSERT(shared_resource->nomem_threshold == (AVAIL - NOMEM_THRESHOLD_COUNT));
+
+ /*
+ * Complete 1 I/O only. The key check here is bdev_io_tailq_cnt - this should not have
+ * changed since completing just 1 I/O should not trigger retrying the queued nomem_io
+ * list.
+ */
+ stub_complete_io(g_bdev.io_target, 1);
+ CU_ASSERT(bdev_io_tailq_cnt(&shared_resource->nomem_io) == nomem_cnt);
+
+ /*
+ * Complete enough I/O to hit the nomem_theshold. This should trigger retrying nomem_io,
+ * and we should see I/O get resubmitted to the test bdev module.
+ */
+ stub_complete_io(g_bdev.io_target, NOMEM_THRESHOLD_COUNT - 1);
+ CU_ASSERT(bdev_io_tailq_cnt(&shared_resource->nomem_io) < nomem_cnt);
+ nomem_cnt = bdev_io_tailq_cnt(&shared_resource->nomem_io);
+
+ /* Complete 1 I/O only. This should not trigger retrying the queued nomem_io. */
+ stub_complete_io(g_bdev.io_target, 1);
+ CU_ASSERT(bdev_io_tailq_cnt(&shared_resource->nomem_io) == nomem_cnt);
+
+ /*
+ * Send a reset and confirm that all I/O are completed, including the ones that
+ * were queued on the nomem_io list.
+ */
+ status_reset = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_reset(g_desc, io_ch, enomem_done, &status_reset);
+ poll_threads();
+ CU_ASSERT(rc == 0);
+ /* This will complete the reset. */
+ stub_complete_io(g_bdev.io_target, 0);
+
+ CU_ASSERT(bdev_io_tailq_cnt(&shared_resource->nomem_io) == 0);
+ CU_ASSERT(shared_resource->io_outstanding == 0);
+
+ spdk_put_io_channel(io_ch);
+ poll_threads();
+ teardown_test();
+}
+
+static void
+enomem_multi_bdev(void)
+{
+ struct spdk_io_channel *io_ch;
+ struct spdk_bdev_channel *bdev_ch;
+ struct spdk_bdev_shared_resource *shared_resource;
+ struct ut_bdev_channel *ut_ch;
+ const uint32_t IO_ARRAY_SIZE = 64;
+ const uint32_t AVAIL = 20;
+ enum spdk_bdev_io_status status[IO_ARRAY_SIZE];
+ uint32_t i;
+ struct ut_bdev *second_bdev;
+ struct spdk_bdev_desc *second_desc = NULL;
+ struct spdk_bdev_channel *second_bdev_ch;
+ struct spdk_io_channel *second_ch;
+ int rc;
+
+ setup_test();
+
+ /* Register second bdev with the same io_target */
+ second_bdev = calloc(1, sizeof(*second_bdev));
+ SPDK_CU_ASSERT_FATAL(second_bdev != NULL);
+ register_bdev(second_bdev, "ut_bdev2", g_bdev.io_target);
+ spdk_bdev_open(&second_bdev->bdev, true, NULL, NULL, &second_desc);
+ SPDK_CU_ASSERT_FATAL(second_desc != NULL);
+
+ set_thread(0);
+ io_ch = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch = spdk_io_channel_get_ctx(io_ch);
+ shared_resource = bdev_ch->shared_resource;
+ ut_ch = spdk_io_channel_get_ctx(bdev_ch->channel);
+ ut_ch->avail_cnt = AVAIL;
+
+ second_ch = spdk_bdev_get_io_channel(second_desc);
+ second_bdev_ch = spdk_io_channel_get_ctx(second_ch);
+ SPDK_CU_ASSERT_FATAL(shared_resource == second_bdev_ch->shared_resource);
+
+ /* Saturate io_target through bdev A. */
+ for (i = 0; i < AVAIL; i++) {
+ status[i] = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch, NULL, 0, 1, enomem_done, &status[i]);
+ CU_ASSERT(rc == 0);
+ }
+ CU_ASSERT(TAILQ_EMPTY(&shared_resource->nomem_io));
+
+ /*
+ * Now submit I/O through the second bdev. This should fail with ENOMEM
+ * and then go onto the nomem_io list.
+ */
+ status[AVAIL] = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(second_desc, second_ch, NULL, 0, 1, enomem_done, &status[AVAIL]);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&shared_resource->nomem_io));
+
+ /* Complete first bdev's I/O. This should retry sending second bdev's nomem_io */
+ stub_complete_io(g_bdev.io_target, AVAIL);
+
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&shared_resource->nomem_io));
+ CU_ASSERT(shared_resource->io_outstanding == 1);
+
+ /* Now complete our retried I/O */
+ stub_complete_io(g_bdev.io_target, 1);
+ SPDK_CU_ASSERT_FATAL(shared_resource->io_outstanding == 0);
+
+ spdk_put_io_channel(io_ch);
+ spdk_put_io_channel(second_ch);
+ spdk_bdev_close(second_desc);
+ unregister_bdev(second_bdev);
+ poll_threads();
+ free(second_bdev);
+ teardown_test();
+}
+
+
+static void
+enomem_multi_io_target(void)
+{
+ struct spdk_io_channel *io_ch;
+ struct spdk_bdev_channel *bdev_ch;
+ struct ut_bdev_channel *ut_ch;
+ const uint32_t IO_ARRAY_SIZE = 64;
+ const uint32_t AVAIL = 20;
+ enum spdk_bdev_io_status status[IO_ARRAY_SIZE];
+ uint32_t i;
+ int new_io_device;
+ struct ut_bdev *second_bdev;
+ struct spdk_bdev_desc *second_desc = NULL;
+ struct spdk_bdev_channel *second_bdev_ch;
+ struct spdk_io_channel *second_ch;
+ int rc;
+
+ setup_test();
+
+ /* Create new io_target and a second bdev using it */
+ spdk_io_device_register(&new_io_device, stub_create_ch, stub_destroy_ch,
+ sizeof(struct ut_bdev_channel), NULL);
+ second_bdev = calloc(1, sizeof(*second_bdev));
+ SPDK_CU_ASSERT_FATAL(second_bdev != NULL);
+ register_bdev(second_bdev, "ut_bdev2", &new_io_device);
+ spdk_bdev_open(&second_bdev->bdev, true, NULL, NULL, &second_desc);
+ SPDK_CU_ASSERT_FATAL(second_desc != NULL);
+
+ set_thread(0);
+ io_ch = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch = spdk_io_channel_get_ctx(io_ch);
+ ut_ch = spdk_io_channel_get_ctx(bdev_ch->channel);
+ ut_ch->avail_cnt = AVAIL;
+
+ /* Different io_target should imply a different shared_resource */
+ second_ch = spdk_bdev_get_io_channel(second_desc);
+ second_bdev_ch = spdk_io_channel_get_ctx(second_ch);
+ SPDK_CU_ASSERT_FATAL(bdev_ch->shared_resource != second_bdev_ch->shared_resource);
+
+ /* Saturate io_target through bdev A. */
+ for (i = 0; i < AVAIL; i++) {
+ status[i] = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch, NULL, 0, 1, enomem_done, &status[i]);
+ CU_ASSERT(rc == 0);
+ }
+ CU_ASSERT(TAILQ_EMPTY(&bdev_ch->shared_resource->nomem_io));
+
+ /* Issue one more I/O to fill ENOMEM list. */
+ status[AVAIL] = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch, NULL, 0, 1, enomem_done, &status[AVAIL]);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&bdev_ch->shared_resource->nomem_io));
+
+ /*
+ * Now submit I/O through the second bdev. This should go through and complete
+ * successfully because we're using a different io_device underneath.
+ */
+ status[AVAIL] = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(second_desc, second_ch, NULL, 0, 1, enomem_done, &status[AVAIL]);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&second_bdev_ch->shared_resource->nomem_io));
+ stub_complete_io(second_bdev->io_target, 1);
+
+ /* Cleanup; Complete outstanding I/O. */
+ stub_complete_io(g_bdev.io_target, AVAIL);
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&bdev_ch->shared_resource->nomem_io));
+ /* Complete the ENOMEM I/O */
+ stub_complete_io(g_bdev.io_target, 1);
+ CU_ASSERT(bdev_ch->shared_resource->io_outstanding == 0);
+
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&bdev_ch->shared_resource->nomem_io));
+ CU_ASSERT(bdev_ch->shared_resource->io_outstanding == 0);
+ spdk_put_io_channel(io_ch);
+ spdk_put_io_channel(second_ch);
+ spdk_bdev_close(second_desc);
+ unregister_bdev(second_bdev);
+ spdk_io_device_unregister(&new_io_device, NULL);
+ poll_threads();
+ free(second_bdev);
+ teardown_test();
+}
+
+static void
+qos_dynamic_enable_done(void *cb_arg, int status)
+{
+ int *rc = cb_arg;
+ *rc = status;
+}
+
+static void
+qos_dynamic_enable(void)
+{
+ struct spdk_io_channel *io_ch[2];
+ struct spdk_bdev_channel *bdev_ch[2];
+ struct spdk_bdev *bdev;
+ enum spdk_bdev_io_status bdev_io_status[2];
+ uint64_t limits[SPDK_BDEV_QOS_NUM_RATE_LIMIT_TYPES] = {};
+ int status, second_status, rc, i;
+
+ setup_test();
+ reset_time();
+
+ for (i = 0; i < SPDK_BDEV_QOS_NUM_RATE_LIMIT_TYPES; i++) {
+ limits[i] = UINT64_MAX;
+ }
+
+ bdev = &g_bdev.bdev;
+
+ g_get_io_channel = true;
+
+ /* Create channels */
+ set_thread(0);
+ io_ch[0] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[0] = spdk_io_channel_get_ctx(io_ch[0]);
+ CU_ASSERT(bdev_ch[0]->flags == 0);
+
+ set_thread(1);
+ io_ch[1] = spdk_bdev_get_io_channel(g_desc);
+ bdev_ch[1] = spdk_io_channel_get_ctx(io_ch[1]);
+ CU_ASSERT(bdev_ch[1]->flags == 0);
+
+ set_thread(0);
+
+ /*
+ * Enable QoS: IOPS and byte per second rate limits.
+ * More than 10 I/Os allowed per timeslice.
+ */
+ status = -1;
+ limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 10000;
+ limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT] = 100;
+ spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status);
+ poll_threads();
+ CU_ASSERT(status == 0);
+ CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) != 0);
+ CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) != 0);
+
+ /*
+ * Submit and complete 10 I/O to fill the QoS allotment for this timeslice.
+ * Additional I/O will then be queued.
+ */
+ set_thread(0);
+ for (i = 0; i < 10; i++) {
+ bdev_io_status[0] = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &bdev_io_status[0]);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(bdev_io_status[0] == SPDK_BDEV_IO_STATUS_PENDING);
+ poll_thread(0);
+ stub_complete_io(g_bdev.io_target, 0);
+ CU_ASSERT(bdev_io_status[0] == SPDK_BDEV_IO_STATUS_SUCCESS);
+ }
+
+ /*
+ * Send two more I/O. These I/O will be queued since the current timeslice allotment has been
+ * filled already. We want to test that when QoS is disabled that these two I/O:
+ * 1) are not aborted
+ * 2) are sent back to their original thread for resubmission
+ */
+ bdev_io_status[0] = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[0], NULL, 0, 1, io_during_io_done, &bdev_io_status[0]);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(bdev_io_status[0] == SPDK_BDEV_IO_STATUS_PENDING);
+ set_thread(1);
+ bdev_io_status[1] = SPDK_BDEV_IO_STATUS_PENDING;
+ rc = spdk_bdev_read_blocks(g_desc, io_ch[1], NULL, 0, 1, io_during_io_done, &bdev_io_status[1]);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(bdev_io_status[1] == SPDK_BDEV_IO_STATUS_PENDING);
+ poll_threads();
+
+ /* Disable QoS: IOPS rate limit */
+ status = -1;
+ limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 0;
+ spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status);
+ poll_threads();
+ CU_ASSERT(status == 0);
+ CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) != 0);
+ CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) != 0);
+
+ /* Disable QoS: Byte per second rate limit */
+ status = -1;
+ limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT] = 0;
+ spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status);
+ poll_threads();
+ CU_ASSERT(status == 0);
+ CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) == 0);
+ CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) == 0);
+
+ /*
+ * All I/O should have been resubmitted back on their original thread. Complete
+ * all I/O on thread 0, and ensure that only the thread 0 I/O was completed.
+ */
+ set_thread(0);
+ stub_complete_io(g_bdev.io_target, 0);
+ poll_threads();
+ CU_ASSERT(bdev_io_status[0] == SPDK_BDEV_IO_STATUS_SUCCESS);
+ CU_ASSERT(bdev_io_status[1] == SPDK_BDEV_IO_STATUS_PENDING);
+
+ /* Now complete all I/O on thread 1 and ensure the thread 1 I/O was completed. */
+ set_thread(1);
+ stub_complete_io(g_bdev.io_target, 0);
+ poll_threads();
+ CU_ASSERT(bdev_io_status[1] == SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ /* Disable QoS again */
+ status = -1;
+ limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 0;
+ spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status);
+ poll_threads();
+ CU_ASSERT(status == 0); /* This should succeed */
+ CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) == 0);
+ CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) == 0);
+
+ /* Enable QoS on thread 0 */
+ status = -1;
+ limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 10000;
+ spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status);
+ poll_threads();
+ CU_ASSERT(status == 0);
+ CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) != 0);
+ CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) != 0);
+
+ /* Disable QoS on thread 1 */
+ set_thread(1);
+ status = -1;
+ limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 0;
+ spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status);
+ /* Don't poll yet. This should leave the channels with QoS enabled */
+ CU_ASSERT(status == -1);
+ CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) != 0);
+ CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) != 0);
+
+ /* Enable QoS. This should immediately fail because the previous disable QoS hasn't completed. */
+ second_status = 0;
+ limits[SPDK_BDEV_QOS_RW_BPS_RATE_LIMIT] = 10;
+ spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &second_status);
+ poll_threads();
+ CU_ASSERT(status == 0); /* The disable should succeed */
+ CU_ASSERT(second_status < 0); /* The enable should fail */
+ CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) == 0);
+ CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) == 0);
+
+ /* Enable QoS on thread 1. This should succeed now that the disable has completed. */
+ status = -1;
+ limits[SPDK_BDEV_QOS_RW_IOPS_RATE_LIMIT] = 10000;
+ spdk_bdev_set_qos_rate_limits(bdev, limits, qos_dynamic_enable_done, &status);
+ poll_threads();
+ CU_ASSERT(status == 0);
+ CU_ASSERT((bdev_ch[0]->flags & BDEV_CH_QOS_ENABLED) != 0);
+ CU_ASSERT((bdev_ch[1]->flags & BDEV_CH_QOS_ENABLED) != 0);
+
+ /* Tear down the channels */
+ set_thread(0);
+ spdk_put_io_channel(io_ch[0]);
+ set_thread(1);
+ spdk_put_io_channel(io_ch[1]);
+ poll_threads();
+
+ set_thread(0);
+ teardown_test();
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("bdev", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "basic", basic) == NULL ||
+ CU_add_test(suite, "unregister_and_close", unregister_and_close) == NULL ||
+ CU_add_test(suite, "basic_qos", basic_qos) == NULL ||
+ CU_add_test(suite, "put_channel_during_reset", put_channel_during_reset) == NULL ||
+ CU_add_test(suite, "aborted_reset", aborted_reset) == NULL ||
+ CU_add_test(suite, "io_during_reset", io_during_reset) == NULL ||
+ CU_add_test(suite, "io_during_qos_queue", io_during_qos_queue) == NULL ||
+ CU_add_test(suite, "io_during_qos_reset", io_during_qos_reset) == NULL ||
+ CU_add_test(suite, "enomem", enomem) == NULL ||
+ CU_add_test(suite, "enomem_multi_bdev", enomem_multi_bdev) == NULL ||
+ CU_add_test(suite, "enomem_multi_io_target", enomem_multi_io_target) == NULL ||
+ CU_add_test(suite, "qos_dynamic_enable", qos_dynamic_enable) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/bdev/part.c/.gitignore b/src/spdk/test/unit/lib/bdev/part.c/.gitignore
new file mode 100644
index 00000000..c8302779
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/part.c/.gitignore
@@ -0,0 +1 @@
+part_ut
diff --git a/src/spdk/test/unit/lib/bdev/part.c/Makefile b/src/spdk/test/unit/lib/bdev/part.c/Makefile
new file mode 100644
index 00000000..9073c5cd
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/part.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = part_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/bdev/part.c/part_ut.c b/src/spdk/test/unit/lib/bdev/part.c/part_ut.c
new file mode 100644
index 00000000..fd251f4c
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/part.c/part_ut.c
@@ -0,0 +1,179 @@
+/*-
+ * 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_cunit.h"
+
+#include "common/lib/test_env.c"
+#include "unit/lib/json_mock.c"
+
+#include "spdk/config.h"
+/* HACK: disable VTune integration so the unit test doesn't need VTune headers and libs to build */
+#undef SPDK_CONFIG_VTUNE
+
+#include "bdev/bdev.c"
+#include "bdev/part.c"
+
+DEFINE_STUB(spdk_conf_find_section, struct spdk_conf_section *, (struct spdk_conf *cp,
+ const char *name), NULL);
+DEFINE_STUB(spdk_conf_section_get_nmval, char *,
+ (struct spdk_conf_section *sp, const char *key, int idx1, int idx2), NULL);
+DEFINE_STUB(spdk_conf_section_get_intval, int, (struct spdk_conf_section *sp, const char *key), -1);
+
+struct spdk_trace_histories *g_trace_histories;
+DEFINE_STUB_V(spdk_trace_add_register_fn, (struct spdk_trace_register_fn *reg_fn));
+DEFINE_STUB_V(spdk_trace_register_owner, (uint8_t type, char id_prefix));
+DEFINE_STUB_V(spdk_trace_register_object, (uint8_t type, char id_prefix));
+DEFINE_STUB_V(spdk_trace_register_description, (const char *name, const char *short_name,
+ uint16_t tpoint_id, uint8_t owner_type,
+ uint8_t object_type, uint8_t new_object,
+ uint8_t arg1_is_ptr, const char *arg1_name));
+DEFINE_STUB_V(_spdk_trace_record, (uint64_t tsc, uint16_t tpoint_id, uint16_t poller_id,
+ uint32_t size, uint64_t object_id, uint64_t arg1));
+
+static void
+_part_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx)
+{
+ fn(ctx);
+}
+
+static void
+_part_cleanup(struct spdk_bdev_part *part)
+{
+ free(part->internal.bdev.name);
+ free(part->internal.bdev.product_name);
+}
+
+void
+spdk_scsi_nvme_translate(const struct spdk_bdev_io *bdev_io,
+ int *sc, int *sk, int *asc, int *ascq)
+{
+}
+
+struct spdk_bdev_module bdev_ut_if = {
+ .name = "bdev_ut",
+};
+
+static void vbdev_ut_examine(struct spdk_bdev *bdev);
+
+struct spdk_bdev_module vbdev_ut_if = {
+ .name = "vbdev_ut",
+ .examine_config = vbdev_ut_examine,
+};
+
+SPDK_BDEV_MODULE_REGISTER(&bdev_ut_if)
+SPDK_BDEV_MODULE_REGISTER(&vbdev_ut_if)
+
+static void
+vbdev_ut_examine(struct spdk_bdev *bdev)
+{
+ spdk_bdev_module_examine_done(&vbdev_ut_if);
+}
+
+static int
+__destruct(void *ctx)
+{
+ return 0;
+}
+
+static struct spdk_bdev_fn_table base_fn_table = {
+ .destruct = __destruct,
+};
+static struct spdk_bdev_fn_table part_fn_table = {
+ .destruct = __destruct,
+};
+
+static void
+part_test(void)
+{
+ struct spdk_bdev_part_base *base;
+ struct spdk_bdev_part part1 = {};
+ struct spdk_bdev_part part2 = {};
+ struct spdk_bdev bdev_base = {};
+ SPDK_BDEV_PART_TAILQ tailq = TAILQ_HEAD_INITIALIZER(tailq);
+ int rc;
+
+ bdev_base.name = "base";
+ bdev_base.fn_table = &base_fn_table;
+ bdev_base.module = &bdev_ut_if;
+ rc = spdk_bdev_register(&bdev_base);
+ CU_ASSERT(rc == 0);
+ base = spdk_bdev_part_base_construct(&bdev_base, NULL, &vbdev_ut_if,
+ &part_fn_table, &tailq, NULL,
+ NULL, 0, NULL, NULL);
+
+ SPDK_CU_ASSERT_FATAL(base != NULL);
+
+ rc = spdk_bdev_part_construct(&part1, base, "test1", 0, 100, "test");
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ rc = spdk_bdev_part_construct(&part2, base, "test2", 100, 100, "test");
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+
+ spdk_bdev_part_base_hotremove(&bdev_base, &tailq);
+
+ spdk_bdev_part_base_free(base);
+ _part_cleanup(&part1);
+ _part_cleanup(&part2);
+ spdk_bdev_unregister(&bdev_base, NULL, NULL);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("bdev_part", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "part", part_test) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ spdk_allocate_thread(_part_send_msg, NULL, NULL, NULL, "thread0");
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ spdk_free_thread();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/bdev/pmem/.gitignore b/src/spdk/test/unit/lib/bdev/pmem/.gitignore
new file mode 100644
index 00000000..b2e0df1e
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/pmem/.gitignore
@@ -0,0 +1 @@
+bdev_pmem_ut
diff --git a/src/spdk/test/unit/lib/bdev/pmem/Makefile b/src/spdk/test/unit/lib/bdev/pmem/Makefile
new file mode 100644
index 00000000..9c0e7dc1
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/pmem/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = bdev_pmem_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/bdev/pmem/bdev_pmem_ut.c b/src/spdk/test/unit/lib/bdev/pmem/bdev_pmem_ut.c
new file mode 100644
index 00000000..742ec638
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/pmem/bdev_pmem_ut.c
@@ -0,0 +1,783 @@
+/*-
+ * 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_cunit.h"
+
+#include "common/lib/test_env.c"
+#include "unit/lib/json_mock.c"
+
+#include "bdev/pmem/bdev_pmem.c"
+
+DEFINE_STUB(spdk_conf_find_section, struct spdk_conf_section *,
+ (struct spdk_conf *cp, const char *name), NULL);
+DEFINE_STUB(spdk_conf_section_get_nval, char *,
+ (struct spdk_conf_section *sp, const char *key, int idx), NULL);
+DEFINE_STUB(spdk_conf_section_get_nmval, char *,
+ (struct spdk_conf_section *sp, const char *key, int idx1, int idx2), NULL);
+
+static struct spdk_bdev_module *g_bdev_pmem_module;
+static int g_bdev_module_cnt;
+
+struct pmemblk {
+ const char *name;
+ bool is_open;
+ bool is_consistent;
+ size_t bsize;
+ long long nblock;
+
+ uint8_t *buffer;
+};
+
+static const char *g_bdev_name = "pmem0";
+
+/* PMEMblkpool is a typedef of struct pmemblk */
+static PMEMblkpool g_pool_ok = {
+ .name = "/pools/ok_pool",
+ .is_open = false,
+ .is_consistent = true,
+ .bsize = 4096,
+ .nblock = 150
+};
+
+static PMEMblkpool g_pool_nblock_0 = {
+ .name = "/pools/nblock_0",
+ .is_open = false,
+ .is_consistent = true,
+ .bsize = 4096,
+ .nblock = 0
+};
+
+static PMEMblkpool g_pool_bsize_0 = {
+ .name = "/pools/nblock_0",
+ .is_open = false,
+ .is_consistent = true,
+ .bsize = 0,
+ .nblock = 100
+};
+
+static PMEMblkpool g_pool_inconsistent = {
+ .name = "/pools/inconsistent",
+ .is_open = false,
+ .is_consistent = false,
+ .bsize = 512,
+ .nblock = 1
+};
+
+static int g_opened_pools;
+static struct spdk_bdev *g_bdev;
+static const char *g_check_version_msg;
+static bool g_pmemblk_open_allow_open = true;
+
+static void
+_pmem_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx)
+{
+ fn(ctx);
+}
+
+static PMEMblkpool *
+find_pmemblk_pool(const char *path)
+{
+ if (path == NULL) {
+ errno = EINVAL;
+ return NULL;
+ } else if (strcmp(g_pool_ok.name, path) == 0) {
+ return &g_pool_ok;
+ } else if (strcmp(g_pool_nblock_0.name, path) == 0) {
+ return &g_pool_nblock_0;
+ } else if (strcmp(g_pool_bsize_0.name, path) == 0) {
+ return &g_pool_bsize_0;
+ } else if (strcmp(g_pool_inconsistent.name, path) == 0) {
+ return &g_pool_inconsistent;
+ }
+
+ errno = ENOENT;
+ return NULL;
+}
+
+PMEMblkpool *
+pmemblk_open(const char *path, size_t bsize)
+{
+ PMEMblkpool *pool;
+
+ if (!g_pmemblk_open_allow_open) {
+ errno = EIO;
+ return NULL;
+ }
+
+ pool = find_pmemblk_pool(path);
+ if (!pool) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ CU_ASSERT_TRUE_FATAL(pool->is_consistent);
+ CU_ASSERT_FALSE(pool->is_open);
+ if (pool->is_open == false) {
+ pool->is_open = true;
+ g_opened_pools++;
+ } else {
+ errno = EBUSY;
+ pool = NULL;
+ }
+
+ return pool;
+}
+void
+spdk_bdev_io_get_buf(struct spdk_bdev_io *bdev_io, spdk_bdev_io_get_buf_cb cb, uint64_t len)
+{
+ cb(NULL, bdev_io);
+}
+
+static void
+check_open_pool_fatal(PMEMblkpool *pool)
+{
+ SPDK_CU_ASSERT_FATAL(pool != NULL);
+ SPDK_CU_ASSERT_FATAL(find_pmemblk_pool(pool->name) == pool);
+ SPDK_CU_ASSERT_FATAL(pool->is_open == true);
+}
+
+void
+pmemblk_close(PMEMblkpool *pool)
+{
+ check_open_pool_fatal(pool);
+ pool->is_open = false;
+ CU_ASSERT(g_opened_pools > 0);
+ g_opened_pools--;
+}
+
+size_t
+pmemblk_bsize(PMEMblkpool *pool)
+{
+ check_open_pool_fatal(pool);
+ return pool->bsize;
+}
+
+size_t
+pmemblk_nblock(PMEMblkpool *pool)
+{
+ check_open_pool_fatal(pool);
+ return pool->nblock;
+}
+
+int
+pmemblk_read(PMEMblkpool *pool, void *buf, long long blockno)
+{
+ check_open_pool_fatal(pool);
+ if (blockno >= pool->nblock) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memcpy(buf, &pool->buffer[blockno * pool->bsize], pool->bsize);
+ return 0;
+}
+
+int
+pmemblk_write(PMEMblkpool *pool, const void *buf, long long blockno)
+{
+ check_open_pool_fatal(pool);
+ if (blockno >= pool->nblock) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memcpy(&pool->buffer[blockno * pool->bsize], buf, pool->bsize);
+ return 0;
+}
+
+int
+pmemblk_set_zero(PMEMblkpool *pool, long long blockno)
+{
+ check_open_pool_fatal(pool);
+ if (blockno >= pool->nblock) {
+
+ errno = EINVAL;
+ return -1;
+ }
+
+ memset(&pool->buffer[blockno * pool->bsize], 0, pool->bsize);
+ return 0;
+}
+
+const char *
+pmemblk_errormsg(void)
+{
+ return strerror(errno);
+}
+
+const char *
+pmemblk_check_version(unsigned major_required, unsigned minor_required)
+{
+ return g_check_version_msg;
+}
+
+int
+pmemblk_check(const char *path, size_t bsize)
+{
+ PMEMblkpool *pool = find_pmemblk_pool(path);
+
+ if (!pool) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (!pool->is_consistent) {
+ /* errno ? */
+ return 0;
+ }
+
+ if (bsize != 0 && pool->bsize != bsize) {
+ /* errno ? */
+ return 0;
+ }
+
+ return 1;
+}
+
+void
+spdk_bdev_io_complete(struct spdk_bdev_io *bdev_io, enum spdk_bdev_io_status status)
+{
+ bdev_io->internal.status = status;
+}
+
+int
+spdk_bdev_register(struct spdk_bdev *bdev)
+{
+ CU_ASSERT_PTR_NULL(g_bdev);
+ g_bdev = bdev;
+
+ return 0;
+}
+
+void
+spdk_bdev_unregister(struct spdk_bdev *bdev, spdk_bdev_unregister_cb cb_fn, void *cb_arg)
+{
+}
+
+void
+spdk_bdev_module_finish_done(void)
+{
+}
+
+int
+spdk_bdev_notify_blockcnt_change(struct spdk_bdev *bdev, uint64_t size)
+{
+ bdev->blockcnt = size;
+ return 0;
+}
+
+static void
+ut_bdev_pmem_destruct(struct spdk_bdev *bdev)
+{
+ SPDK_CU_ASSERT_FATAL(g_bdev != NULL);
+ CU_ASSERT_EQUAL(bdev_pmem_destruct(bdev->ctxt), 0);
+ g_bdev = NULL;
+}
+
+void
+spdk_bdev_module_list_add(struct spdk_bdev_module *bdev_module)
+{
+ g_bdev_pmem_module = bdev_module;
+ g_bdev_module_cnt++;
+}
+
+static int
+bdev_submit_request(struct spdk_bdev *bdev, int16_t io_type, uint64_t offset_blocks,
+ uint64_t num_blocks, struct iovec *iovs, size_t iov_cnt)
+{
+ struct spdk_bdev_io bio = { 0 };
+
+ switch (io_type) {
+ case SPDK_BDEV_IO_TYPE_READ:
+ bio.u.bdev.iovs = iovs;
+ bio.u.bdev.iovcnt = iov_cnt;
+ bio.u.bdev.offset_blocks = offset_blocks;
+ bio.u.bdev.num_blocks = num_blocks;
+ break;
+ case SPDK_BDEV_IO_TYPE_WRITE:
+ bio.u.bdev.iovs = iovs;
+ bio.u.bdev.iovcnt = iov_cnt;
+ bio.u.bdev.offset_blocks = offset_blocks;
+ bio.u.bdev.num_blocks = num_blocks;
+ break;
+ case SPDK_BDEV_IO_TYPE_FLUSH:
+ bio.u.bdev.offset_blocks = offset_blocks;
+ bio.u.bdev.num_blocks = num_blocks;
+ break;
+ case SPDK_BDEV_IO_TYPE_RESET:
+ break;
+ case SPDK_BDEV_IO_TYPE_UNMAP:
+ bio.u.bdev.offset_blocks = offset_blocks;
+ bio.u.bdev.num_blocks = num_blocks;
+ break;
+ case SPDK_BDEV_IO_TYPE_WRITE_ZEROES:
+ bio.u.bdev.offset_blocks = offset_blocks;
+ bio.u.bdev.num_blocks = num_blocks;
+ break;
+ default:
+ CU_FAIL_FATAL("BUG:Unexpected IO type");
+ break;
+ }
+
+ /*
+ * Set status to value that shouldn't be returned
+ */
+ bio.type = io_type;
+ bio.internal.status = SPDK_BDEV_IO_STATUS_PENDING;
+ bio.bdev = bdev;
+ bdev_pmem_submit_request(NULL, &bio);
+ return bio.internal.status;
+}
+
+
+static int
+ut_pmem_blk_clean(void)
+{
+ free(g_pool_ok.buffer);
+ g_pool_ok.buffer = NULL;
+
+ /* Unload module to free IO channel */
+ g_bdev_pmem_module->module_fini();
+
+ spdk_free_thread();
+
+ return 0;
+}
+
+static int
+ut_pmem_blk_init(void)
+{
+ errno = 0;
+
+ spdk_allocate_thread(_pmem_send_msg, NULL, NULL, NULL, NULL);
+
+ g_pool_ok.buffer = calloc(g_pool_ok.nblock, g_pool_ok.bsize);
+ if (g_pool_ok.buffer == NULL) {
+ ut_pmem_blk_clean();
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+ut_pmem_init(void)
+{
+ SPDK_CU_ASSERT_FATAL(g_bdev_pmem_module != NULL);
+ CU_ASSERT_EQUAL(g_bdev_module_cnt, 1);
+
+ /* Make pmemblk_check_version fail with provided error message */
+ g_check_version_msg = "TEST FAIL MESSAGE";
+ CU_ASSERT_NOT_EQUAL(g_bdev_pmem_module->module_init(), 0);
+
+ /* This init must success */
+ g_check_version_msg = NULL;
+ CU_ASSERT_EQUAL(g_bdev_pmem_module->module_init(), 0);
+}
+
+static void
+ut_pmem_open_close(void)
+{
+ struct spdk_bdev *bdev = NULL;
+ int pools_cnt;
+ int rc;
+
+ pools_cnt = g_opened_pools;
+
+ /* Try opening with NULL name */
+ rc = spdk_create_pmem_disk(NULL, NULL, &bdev);
+ CU_ASSERT_PTR_NULL(bdev);
+ CU_ASSERT_EQUAL(pools_cnt, g_opened_pools);
+ CU_ASSERT_NOT_EQUAL(rc, 0);
+
+ /* Open non-existent pool */
+ rc = spdk_create_pmem_disk("non existent pool", NULL, &bdev);
+ CU_ASSERT_PTR_NULL(bdev);
+ CU_ASSERT_EQUAL(pools_cnt, g_opened_pools);
+ CU_ASSERT_NOT_EQUAL(rc, 0);
+
+ /* Open inconsistent pool */
+ rc = spdk_create_pmem_disk(g_pool_inconsistent.name, NULL, &bdev);
+ CU_ASSERT_PTR_NULL(bdev);
+ CU_ASSERT_EQUAL(pools_cnt, g_opened_pools);
+ CU_ASSERT_NOT_EQUAL(rc, 0);
+
+ /* Open consistent pool fail the open from unknown reason. */
+ g_pmemblk_open_allow_open = false;
+ rc = spdk_create_pmem_disk(g_pool_inconsistent.name, NULL, &bdev);
+ g_pmemblk_open_allow_open = true;
+ CU_ASSERT_PTR_NULL(bdev);
+ CU_ASSERT_EQUAL(pools_cnt, g_opened_pools);
+ CU_ASSERT_NOT_EQUAL(rc, 0);
+
+ /* Open pool with nblocks = 0 */
+ rc = spdk_create_pmem_disk(g_pool_nblock_0.name, NULL, &bdev);
+ CU_ASSERT_PTR_NULL(bdev);
+ CU_ASSERT_EQUAL(pools_cnt, g_opened_pools);
+ CU_ASSERT_NOT_EQUAL(rc, 0);
+
+ /* Open pool with bsize = 0 */
+ rc = spdk_create_pmem_disk(g_pool_bsize_0.name, NULL, &bdev);
+ CU_ASSERT_PTR_NULL(bdev);
+ CU_ASSERT_EQUAL(pools_cnt, g_opened_pools);
+ CU_ASSERT_NOT_EQUAL(rc, 0);
+
+ /* Open pool with NULL name */
+ rc = spdk_create_pmem_disk(g_pool_ok.name, NULL, &bdev);
+ CU_ASSERT_PTR_NULL(bdev);
+ CU_ASSERT_EQUAL(pools_cnt, g_opened_pools);
+ CU_ASSERT_NOT_EQUAL(rc, 0);
+
+ /* Open good pool */
+ rc = spdk_create_pmem_disk(g_pool_ok.name, g_bdev_name, &bdev);
+ SPDK_CU_ASSERT_FATAL(bdev != NULL);
+ CU_ASSERT_TRUE(g_pool_ok.is_open);
+ CU_ASSERT_EQUAL(pools_cnt + 1, g_opened_pools);
+ CU_ASSERT_EQUAL(rc, 0);
+
+ /* Now remove this bdev */
+ ut_bdev_pmem_destruct(bdev);
+ CU_ASSERT_FALSE(g_pool_ok.is_open);
+ CU_ASSERT_EQUAL(pools_cnt, g_opened_pools);
+}
+
+static void
+ut_pmem_write_read(void)
+{
+ uint8_t *write_buf, *read_buf;
+ struct spdk_bdev *bdev;
+ int rc;
+ size_t unaligned_aligned_size = 100;
+ size_t buf_size = g_pool_ok.bsize * g_pool_ok.nblock;
+ size_t i;
+ const uint64_t nblock_offset = 10;
+ uint64_t offset;
+ size_t io_size, nblock, total_io_size, bsize;
+
+ bsize = 4096;
+ struct iovec iov[] = {
+ { 0, 2 * bsize },
+ { 0, 3 * bsize },
+ { 0, 4 * bsize },
+ };
+
+ rc = spdk_create_pmem_disk(g_pool_ok.name, g_bdev_name, &bdev);
+ CU_ASSERT_EQUAL(rc, 0);
+
+ SPDK_CU_ASSERT_FATAL(g_pool_ok.nblock > 40);
+
+ write_buf = calloc(1, buf_size);
+ read_buf = calloc(1, buf_size);
+
+ SPDK_CU_ASSERT_FATAL(bdev != NULL);
+ SPDK_CU_ASSERT_FATAL(write_buf != NULL);
+ SPDK_CU_ASSERT_FATAL(read_buf != NULL);
+
+ total_io_size = 0;
+ offset = nblock_offset * g_pool_ok.bsize;
+ for (i = 0; i < 3; i++) {
+ iov[i].iov_base = &write_buf[offset + total_io_size];
+ total_io_size += iov[i].iov_len;
+ }
+
+ for (i = 0; i < total_io_size + unaligned_aligned_size; i++) {
+ write_buf[offset + i] = 0x42 + i;
+ }
+
+ SPDK_CU_ASSERT_FATAL(total_io_size < buf_size);
+
+ /*
+ * Write outside pool.
+ */
+ rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_WRITE, g_pool_ok.nblock, 1, &iov[0], 2);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED);
+
+ /*
+ * Write with insufficient IOV buffers length.
+ */
+ rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_WRITE, 0, g_pool_ok.nblock, &iov[0], 2);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED);
+
+ /*
+ * Try to write two IOV with first one iov_len % bsize != 0.
+ */
+ io_size = iov[0].iov_len + iov[1].iov_len;
+ nblock = io_size / g_pool_ok.bsize;
+ iov[0].iov_len += unaligned_aligned_size;
+ rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_WRITE, 0, nblock, &iov[0], 2);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED);
+ iov[0].iov_len -= unaligned_aligned_size;
+
+ /*
+ * Try to write one IOV.
+ */
+ nblock = iov[0].iov_len / g_pool_ok.bsize;
+ rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_WRITE, nblock_offset, nblock, &iov[0], 1);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ /*
+ * Try to write 2 IOV.
+ * Sum of IOV length is larger than IO size and last IOV is larger and iov_len % bsize != 0
+ */
+ offset = iov[0].iov_len / g_pool_ok.bsize;
+ io_size = iov[1].iov_len + iov[2].iov_len;
+ nblock = io_size / g_pool_ok.bsize;
+ iov[2].iov_len += unaligned_aligned_size;
+ rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_WRITE, nblock_offset + offset, nblock,
+ &iov[1], 2);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS);
+ iov[2].iov_len -= unaligned_aligned_size;
+
+ /*
+ * Examine pool state:
+ * 1. Written area should have expected values.
+ * 2. Anything else should contain zeros.
+ */
+ offset = nblock_offset * g_pool_ok.bsize + total_io_size;
+ rc = memcmp(&g_pool_ok.buffer[0], write_buf, offset);
+ CU_ASSERT_EQUAL(rc, 0);
+
+ for (i = offset; i < buf_size; i++) {
+ if (g_pool_ok.buffer[i] != 0) {
+ CU_ASSERT_EQUAL(g_pool_ok.buffer[i], 0);
+ break;
+ }
+ }
+
+ /* Setup IOV for reads */
+ memset(read_buf, 0xAB, buf_size);
+ offset = nblock_offset * g_pool_ok.bsize;
+ for (i = 0; i < 3; i++) {
+ iov[i].iov_base = &read_buf[offset];
+ offset += iov[i].iov_len;
+ }
+
+ /*
+ * Write outside pool.
+ */
+ rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_READ, g_pool_ok.nblock, 1, &iov[0], 2);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED);
+
+ /*
+ * Read with insufficient IOV buffers length.
+ */
+ rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_READ, 0, g_pool_ok.nblock, &iov[0], 2);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED);
+
+ /*
+ * Try to read two IOV with first one iov_len % bsize != 0.
+ */
+ io_size = iov[0].iov_len + iov[1].iov_len;
+ nblock = io_size / g_pool_ok.bsize;
+ iov[0].iov_len += unaligned_aligned_size;
+ rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_READ, 0, nblock, &iov[0], 2);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED);
+ iov[0].iov_len -= unaligned_aligned_size;
+
+ /*
+ * Try to write one IOV.
+ */
+ nblock = iov[0].iov_len / g_pool_ok.bsize;
+ rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_READ, nblock_offset, nblock, &iov[0], 1);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ /*
+ * Try to read 2 IOV.
+ * Sum of IOV length is larger than IO size and last IOV is larger and iov_len % bsize != 0
+ */
+ offset = iov[0].iov_len / g_pool_ok.bsize;
+ io_size = iov[1].iov_len + iov[2].iov_len;
+ nblock = io_size / g_pool_ok.bsize;
+ iov[2].iov_len += unaligned_aligned_size;
+ rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_READ, nblock_offset + offset, nblock,
+ &iov[1], 2);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS);
+ iov[2].iov_len -= unaligned_aligned_size;
+
+
+ /*
+ * Examine what we read state:
+ * 1. Written area should have expected values.
+ * 2. Anything else should contain zeros.
+ */
+ offset = nblock_offset * g_pool_ok.bsize;
+ for (i = 0; i < offset; i++) {
+ if (read_buf[i] != 0xAB) {
+ CU_ASSERT_EQUAL(read_buf[i], 0xAB);
+ break;
+ }
+ }
+
+ rc = memcmp(&read_buf[offset], &write_buf[offset], total_io_size);
+ CU_ASSERT_EQUAL(rc, 0);
+
+ offset += total_io_size;
+ for (i = offset; i < buf_size; i++) {
+ if (read_buf[i] != 0xAB) {
+ CU_ASSERT_EQUAL(read_buf[i], 0xAB);
+ break;
+ }
+ }
+
+ memset(g_pool_ok.buffer, 0, g_pool_ok.bsize * g_pool_ok.nblock);
+ free(write_buf);
+ free(read_buf);
+
+ /* Now remove this bdev */
+ ut_bdev_pmem_destruct(bdev);
+ CU_ASSERT_FALSE(g_pool_ok.is_open);
+ CU_ASSERT_EQUAL(g_opened_pools, 0);
+}
+
+static void
+ut_pmem_reset(void)
+{
+ struct spdk_bdev *bdev;
+ int rc;
+
+ rc = spdk_create_pmem_disk(g_pool_ok.name, g_bdev_name, &bdev);
+ CU_ASSERT_EQUAL(rc, 0);
+ SPDK_CU_ASSERT_FATAL(bdev != NULL);
+
+ rc = bdev_submit_request(bdev, SPDK_BDEV_IO_TYPE_RESET, 0, 0, NULL, 0);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ ut_bdev_pmem_destruct(bdev);
+}
+
+static void
+ut_pmem_unmap_write_zero(int16_t io_type)
+{
+ struct spdk_bdev *bdev;
+ size_t buff_size = g_pool_ok.nblock * g_pool_ok.bsize;
+ size_t i;
+ uint8_t *buffer;
+ int rc;
+
+ CU_ASSERT(io_type == SPDK_BDEV_IO_TYPE_UNMAP || io_type == SPDK_BDEV_IO_TYPE_WRITE_ZEROES);
+ rc = spdk_create_pmem_disk(g_pool_ok.name, g_bdev_name, &bdev);
+ CU_ASSERT_EQUAL(rc, 0);
+ SPDK_CU_ASSERT_FATAL(bdev != NULL);
+ SPDK_CU_ASSERT_FATAL(g_pool_ok.nblock > 40);
+
+ buffer = calloc(1, buff_size);
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+
+ for (i = 10 * g_pool_ok.bsize; i < 30 * g_pool_ok.bsize; i++) {
+ buffer[i] = 0x30 + io_type + i;
+ }
+ memcpy(g_pool_ok.buffer, buffer, buff_size);
+
+ /*
+ * Block outside of pool.
+ */
+ rc = bdev_submit_request(bdev, io_type, g_pool_ok.nblock, 1, NULL, 0);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_FAILED);
+
+ rc = memcmp(buffer, g_pool_ok.buffer, buff_size);
+ CU_ASSERT_EQUAL(rc, 0);
+
+ /*
+ * Blocks 15 to 25
+ */
+ memset(&buffer[15 * g_pool_ok.bsize], 0, 10 * g_pool_ok.bsize);
+ rc = bdev_submit_request(bdev, io_type, 15, 10, NULL, 0);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ rc = memcmp(buffer, g_pool_ok.buffer, buff_size);
+ CU_ASSERT_EQUAL(rc, 0);
+
+ /*
+ * All blocks.
+ */
+ memset(buffer, 0, buff_size);
+ rc = bdev_submit_request(bdev, io_type, 0, g_pool_ok.nblock, NULL, 0);
+ CU_ASSERT_EQUAL(rc, SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ rc = memcmp(buffer, g_pool_ok.buffer, buff_size);
+ CU_ASSERT_EQUAL(rc, 0);
+
+ /* Now remove this bdev */
+ ut_bdev_pmem_destruct(bdev);
+ CU_ASSERT_FALSE(g_pool_ok.is_open);
+ CU_ASSERT_EQUAL(g_opened_pools, 0);
+
+ free(buffer);
+}
+
+static void
+ut_pmem_write_zero(void)
+{
+ ut_pmem_unmap_write_zero(SPDK_BDEV_IO_TYPE_WRITE_ZEROES);
+}
+
+static void
+ut_pmem_unmap(void)
+{
+ ut_pmem_unmap_write_zero(SPDK_BDEV_IO_TYPE_UNMAP);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("bdev_pmem", ut_pmem_blk_init, ut_pmem_blk_clean);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "ut_pmem_init", ut_pmem_init) == NULL ||
+ CU_add_test(suite, "ut_pmem_open_close", ut_pmem_open_close) == NULL ||
+ CU_add_test(suite, "ut_pmem_write_read", ut_pmem_write_read) == NULL ||
+ CU_add_test(suite, "ut_pmem_reset", ut_pmem_reset) == NULL ||
+ CU_add_test(suite, "ut_pmem_write_zero", ut_pmem_write_zero) == NULL ||
+ CU_add_test(suite, "ut_pmem_unmap", ut_pmem_unmap) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/bdev/scsi_nvme.c/.gitignore b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/.gitignore
new file mode 100644
index 00000000..75800527
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/.gitignore
@@ -0,0 +1 @@
+scsi_nvme_ut
diff --git a/src/spdk/test/unit/lib/bdev/scsi_nvme.c/Makefile b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/Makefile
new file mode 100644
index 00000000..0c908148
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/Makefile
@@ -0,0 +1,39 @@
+#
+# BSD LICENSE
+#
+# Copyright (c) 2016 FUJITSU LIMITED, 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 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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+TEST_FILE = scsi_nvme_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/bdev/scsi_nvme.c/scsi_nvme_ut.c b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/scsi_nvme_ut.c
new file mode 100644
index 00000000..9b2eff35
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/scsi_nvme.c/scsi_nvme_ut.c
@@ -0,0 +1,142 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright (c) 2016 FUJITSU LIMITED, 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 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.
+ */
+
+#include "spdk_cunit.h"
+
+#include "bdev/scsi_nvme.c"
+
+static int
+null_init(void)
+{
+ return 0;
+}
+
+static int
+null_clean(void)
+{
+ return 0;
+}
+
+static void
+scsi_nvme_translate_test(void)
+{
+ struct spdk_bdev_io bdev_io;
+ int sc, sk, asc, ascq;
+
+ /* SPDK_NVME_SCT_GENERIC */
+ bdev_io.internal.error.nvme.sct = SPDK_NVME_SCT_GENERIC;
+ bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_ABORTED_POWER_LOSS;
+ spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq);
+ CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_TASK_ABORTED);
+ CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ABORTED_COMMAND);
+ CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_WARNING);
+ CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_POWER_LOSS_EXPECTED);
+
+ bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_INVALID_NUM_SGL_DESCIRPTORS;
+ spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq);
+ CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ILLEGAL_REQUEST);
+ CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE);
+ CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE);
+
+ /* SPDK_NVME_SCT_COMMAND_SPECIFIC */
+ bdev_io.internal.error.nvme.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
+ bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_INVALID_FORMAT;
+ spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq);
+ CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ILLEGAL_REQUEST);
+ CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_FORMAT_COMMAND_FAILED);
+ CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_FORMAT_COMMAND_FAILED);
+
+ bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_OVERLAPPING_RANGE;
+ spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq);
+ CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ILLEGAL_REQUEST);
+ CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE);
+ CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE);
+
+ /* SPDK_NVME_SCT_MEDIA_ERROR */
+ bdev_io.internal.error.nvme.sct = SPDK_NVME_SCT_MEDIA_ERROR;
+ bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_GUARD_CHECK_ERROR;
+ spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq);
+ CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_MEDIUM_ERROR);
+ CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_LOGICAL_BLOCK_GUARD_CHECK_FAILED);
+ CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_LOGICAL_BLOCK_GUARD_CHECK_FAILED);
+
+ bdev_io.internal.error.nvme.sc = SPDK_NVME_SC_DEALLOCATED_OR_UNWRITTEN_BLOCK;
+ spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq);
+ CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ILLEGAL_REQUEST);
+ CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE);
+ CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE);
+
+ /* SPDK_NVME_SCT_VENDOR_SPECIFIC */
+ bdev_io.internal.error.nvme.sct = SPDK_NVME_SCT_VENDOR_SPECIFIC;
+ bdev_io.internal.error.nvme.sc = 0xff;
+ spdk_scsi_nvme_translate(&bdev_io, &sc, &sk, &asc, &ascq);
+ CU_ASSERT_EQUAL(sc, SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT_EQUAL(sk, SPDK_SCSI_SENSE_ILLEGAL_REQUEST);
+ CU_ASSERT_EQUAL(asc, SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE);
+ CU_ASSERT_EQUAL(ascq, SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("scsi_nvme_suite", null_init, null_clean);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "scsi_nvme - translate nvme error to scsi error",
+ scsi_nvme_translate_test) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/.gitignore b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/.gitignore
new file mode 100644
index 00000000..5f2f6fdf
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/.gitignore
@@ -0,0 +1 @@
+vbdev_lvol_ut
diff --git a/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/Makefile b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/Makefile
new file mode 100644
index 00000000..c2e6b99e
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = vbdev_lvol_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c
new file mode 100644
index 00000000..2500378b
--- /dev/null
+++ b/src/spdk/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c
@@ -0,0 +1,1410 @@
+/*-
+ * 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_cunit.h"
+#include "spdk/string.h"
+
+#include "bdev/lvol/vbdev_lvol.c"
+
+#define SPDK_BS_PAGE_SIZE 0x1000
+
+int g_lvolerrno;
+int g_lvserrno;
+int g_cluster_size;
+int g_registered_bdevs;
+int g_num_lvols = 0;
+struct spdk_lvol_store *g_lvs = NULL;
+struct spdk_lvol *g_lvol = NULL;
+struct lvol_store_bdev *g_lvs_bdev = NULL;
+struct spdk_bdev *g_base_bdev = NULL;
+struct spdk_bdev_io *g_io = NULL;
+struct spdk_io_channel *g_ch = NULL;
+struct lvol_task *g_task = NULL;
+
+static struct spdk_bdev g_bdev = {};
+static struct spdk_lvol_store *g_lvol_store = NULL;
+bool lvol_store_initialize_fail = false;
+bool lvol_store_initialize_cb_fail = false;
+bool lvol_already_opened = false;
+bool g_examine_done = false;
+bool g_bdev_alias_already_exists = false;
+bool g_lvs_with_name_already_exists = false;
+bool g_lvol_deletable = true;
+
+int
+spdk_bdev_alias_add(struct spdk_bdev *bdev, const char *alias)
+{
+ struct spdk_bdev_alias *tmp;
+
+ CU_ASSERT(alias != NULL);
+ CU_ASSERT(bdev != NULL);
+ if (g_bdev_alias_already_exists) {
+ return -EEXIST;
+ }
+
+ tmp = calloc(1, sizeof(*tmp));
+ SPDK_CU_ASSERT_FATAL(tmp != NULL);
+
+ tmp->alias = strdup(alias);
+ SPDK_CU_ASSERT_FATAL(tmp->alias != NULL);
+
+ TAILQ_INSERT_TAIL(&bdev->aliases, tmp, tailq);
+
+ return 0;
+}
+
+int
+spdk_bdev_alias_del(struct spdk_bdev *bdev, const char *alias)
+{
+ struct spdk_bdev_alias *tmp;
+
+ CU_ASSERT(alias != NULL);
+ CU_ASSERT(bdev != NULL);
+
+ TAILQ_FOREACH(tmp, &bdev->aliases, tailq) {
+ if (strncmp(alias, tmp->alias, SPDK_LVOL_NAME_MAX) == 0) {
+ TAILQ_REMOVE(&bdev->aliases, tmp, tailq);
+ free(tmp->alias);
+ free(tmp);
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+void
+spdk_bdev_alias_del_all(struct spdk_bdev *bdev)
+{
+ struct spdk_bdev_alias *p, *tmp;
+
+ TAILQ_FOREACH_SAFE(p, &bdev->aliases, tailq, tmp) {
+ TAILQ_REMOVE(&bdev->aliases, p, tailq);
+ free(p->alias);
+ free(p);
+ }
+}
+
+void
+spdk_bdev_destruct_done(struct spdk_bdev *bdev, int bdeverrno)
+{
+}
+
+void
+spdk_lvs_rename(struct spdk_lvol_store *lvs, const char *new_name,
+ spdk_lvs_op_complete cb_fn, void *cb_arg)
+{
+ if (g_lvs_with_name_already_exists) {
+ g_lvolerrno = -EEXIST;
+ } else {
+ snprintf(lvs->name, sizeof(lvs->name), "%s", new_name);
+ g_lvolerrno = 0;
+ }
+
+ cb_fn(cb_arg, g_lvolerrno);
+}
+
+void
+spdk_lvol_rename(struct spdk_lvol *lvol, const char *new_name,
+ spdk_lvol_op_complete cb_fn, void *cb_arg)
+{
+ struct spdk_lvol *tmp;
+
+ if (strncmp(lvol->name, new_name, SPDK_LVOL_NAME_MAX) == 0) {
+ cb_fn(cb_arg, 0);
+ return;
+ }
+
+ TAILQ_FOREACH(tmp, &lvol->lvol_store->lvols, link) {
+ if (strncmp(tmp->name, new_name, SPDK_LVOL_NAME_MAX) == 0) {
+ SPDK_ERRLOG("Lvol %s already exists in lvol store %s\n", new_name, lvol->lvol_store->name);
+ cb_fn(cb_arg, -EEXIST);
+ return;
+ }
+ }
+
+ snprintf(lvol->name, sizeof(lvol->name), "%s", new_name);
+
+ cb_fn(cb_arg, g_lvolerrno);
+}
+
+void
+spdk_lvol_open(struct spdk_lvol *lvol, spdk_lvol_op_with_handle_complete cb_fn, void *cb_arg)
+{
+ cb_fn(cb_arg, lvol, g_lvolerrno);
+}
+
+uint64_t
+spdk_blob_get_num_clusters(struct spdk_blob *b)
+{
+ return 0;
+}
+
+int
+spdk_blob_get_clones(struct spdk_blob_store *bs, spdk_blob_id blobid, spdk_blob_id *ids,
+ size_t *count)
+{
+ *count = 0;
+ return 0;
+}
+
+spdk_blob_id
+spdk_blob_get_parent_snapshot(struct spdk_blob_store *bs, spdk_blob_id blobid)
+{
+ return 0;
+}
+
+bool g_blob_is_read_only = false;
+
+bool
+spdk_blob_is_read_only(struct spdk_blob *blob)
+{
+ return g_blob_is_read_only;
+}
+
+bool
+spdk_blob_is_snapshot(struct spdk_blob *blob)
+{
+ return false;
+}
+
+bool
+spdk_blob_is_clone(struct spdk_blob *blob)
+{
+ return false;
+}
+
+bool
+spdk_blob_is_thin_provisioned(struct spdk_blob *blob)
+{
+ return false;
+}
+
+static struct spdk_lvol *_lvol_create(struct spdk_lvol_store *lvs);
+
+void
+spdk_lvs_load(struct spdk_bs_dev *dev,
+ spdk_lvs_op_with_handle_complete cb_fn, void *cb_arg)
+{
+ struct spdk_lvol_store *lvs = NULL;
+ int i;
+ int lvserrno = g_lvserrno;
+
+ if (lvserrno != 0) {
+ /* On error blobstore destroys bs_dev itself,
+ * by puttin back io channels.
+ * This operation is asynchronous, and completed
+ * after calling the callback for lvol. */
+ cb_fn(cb_arg, g_lvol_store, lvserrno);
+ dev->destroy(dev);
+ return;
+ }
+
+ lvs = calloc(1, sizeof(*lvs));
+ SPDK_CU_ASSERT_FATAL(lvs != NULL);
+ TAILQ_INIT(&lvs->lvols);
+ TAILQ_INIT(&lvs->pending_lvols);
+ spdk_uuid_generate(&lvs->uuid);
+ lvs->bs_dev = dev;
+ for (i = 0; i < g_num_lvols; i++) {
+ _lvol_create(lvs);
+ }
+
+ cb_fn(cb_arg, lvs, lvserrno);
+}
+
+int
+spdk_bs_bdev_claim(struct spdk_bs_dev *bs_dev, struct spdk_bdev_module *module)
+{
+ if (lvol_already_opened == true) {
+ return -1;
+ }
+
+ lvol_already_opened = true;
+
+ return 0;
+}
+
+void
+spdk_bdev_unregister(struct spdk_bdev *vbdev, spdk_bdev_unregister_cb cb_fn, void *cb_arg)
+{
+ int rc;
+
+ SPDK_CU_ASSERT_FATAL(vbdev != NULL);
+ rc = vbdev->fn_table->destruct(vbdev->ctxt);
+
+ SPDK_CU_ASSERT_FATAL(cb_fn != NULL);
+ cb_fn(cb_arg, rc);
+}
+
+void
+spdk_bdev_module_finish_done(void)
+{
+ return;
+}
+
+uint64_t
+spdk_bs_get_page_size(struct spdk_blob_store *bs)
+{
+ return SPDK_BS_PAGE_SIZE;
+}
+
+uint64_t
+spdk_bs_get_io_unit_size(struct spdk_blob_store *bs)
+{
+ return SPDK_BS_PAGE_SIZE;
+}
+
+static void
+bdev_blob_destroy(struct spdk_bs_dev *bs_dev)
+{
+ CU_ASSERT(bs_dev != NULL);
+ free(bs_dev);
+ lvol_already_opened = false;
+}
+
+struct spdk_bs_dev *
+spdk_bdev_create_bs_dev(struct spdk_bdev *bdev, spdk_bdev_remove_cb_t remove_cb, void *remove_ctx)
+{
+ struct spdk_bs_dev *bs_dev;
+
+ if (lvol_already_opened == true || bdev == NULL) {
+ return NULL;
+ }
+
+ bs_dev = calloc(1, sizeof(*bs_dev));
+ SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
+ bs_dev->destroy = bdev_blob_destroy;
+
+ return bs_dev;
+}
+
+void
+spdk_lvs_opts_init(struct spdk_lvs_opts *opts)
+{
+}
+
+int
+spdk_lvs_init(struct spdk_bs_dev *bs_dev, struct spdk_lvs_opts *o,
+ spdk_lvs_op_with_handle_complete cb_fn, void *cb_arg)
+{
+ struct spdk_lvol_store *lvs;
+ int error = 0;
+
+ if (lvol_store_initialize_fail) {
+ return -1;
+ }
+
+ if (lvol_store_initialize_cb_fail) {
+ bs_dev->destroy(bs_dev);
+ lvs = NULL;
+ error = -1;
+ } else {
+ lvs = calloc(1, sizeof(*lvs));
+ SPDK_CU_ASSERT_FATAL(lvs != NULL);
+ TAILQ_INIT(&lvs->lvols);
+ TAILQ_INIT(&lvs->pending_lvols);
+ spdk_uuid_generate(&lvs->uuid);
+ snprintf(lvs->name, sizeof(lvs->name), "%s", o->name);
+ lvs->bs_dev = bs_dev;
+ error = 0;
+ }
+ cb_fn(cb_arg, lvs, error);
+
+ return 0;
+}
+
+int
+spdk_lvs_unload(struct spdk_lvol_store *lvs, spdk_lvs_op_complete cb_fn, void *cb_arg)
+{
+ struct spdk_lvol *lvol, *tmp;
+
+ TAILQ_FOREACH_SAFE(lvol, &lvs->lvols, link, tmp) {
+ TAILQ_REMOVE(&lvs->lvols, lvol, link);
+ free(lvol->unique_id);
+ free(lvol);
+ }
+ g_lvol_store = NULL;
+
+ lvs->bs_dev->destroy(lvs->bs_dev);
+ free(lvs);
+
+ if (cb_fn != NULL) {
+ cb_fn(cb_arg, 0);
+ }
+
+ return 0;
+}
+
+int
+spdk_lvs_destroy(struct spdk_lvol_store *lvs, spdk_lvs_op_complete cb_fn,
+ void *cb_arg)
+{
+ struct spdk_lvol *lvol, *tmp;
+ char *alias;
+
+ TAILQ_FOREACH_SAFE(lvol, &lvs->lvols, link, tmp) {
+ TAILQ_REMOVE(&lvs->lvols, lvol, link);
+
+ alias = spdk_sprintf_alloc("%s/%s", lvs->name, lvol->name);
+ if (alias == NULL) {
+ SPDK_ERRLOG("Cannot alloc memory for alias\n");
+ return -1;
+ }
+ spdk_bdev_alias_del(lvol->bdev, alias);
+
+ free(alias);
+ free(lvol->unique_id);
+ free(lvol);
+ }
+ g_lvol_store = NULL;
+
+ lvs->bs_dev->destroy(lvs->bs_dev);
+ free(lvs);
+
+ if (cb_fn != NULL) {
+ cb_fn(cb_arg, 0);
+ }
+
+ return 0;
+}
+
+void
+spdk_lvol_resize(struct spdk_lvol *lvol, size_t sz, spdk_lvol_op_complete cb_fn, void *cb_arg)
+{
+ cb_fn(cb_arg, 0);
+}
+
+int
+spdk_bdev_notify_blockcnt_change(struct spdk_bdev *bdev, uint64_t size)
+{
+ bdev->blockcnt = size;
+ return 0;
+}
+
+uint64_t
+spdk_bs_get_cluster_size(struct spdk_blob_store *bs)
+{
+ return g_cluster_size;
+}
+
+struct spdk_bdev *
+spdk_bdev_get_by_name(const char *bdev_name)
+{
+ if (!strcmp(g_base_bdev->name, bdev_name)) {
+ return g_base_bdev;
+ }
+
+ return NULL;
+}
+
+void
+spdk_lvol_close(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn, void *cb_arg)
+{
+ lvol->ref_count--;
+
+ SPDK_CU_ASSERT_FATAL(cb_fn != NULL);
+ cb_fn(cb_arg, 0);
+}
+
+bool
+spdk_lvol_deletable(struct spdk_lvol *lvol)
+{
+ return g_lvol_deletable;
+}
+
+void
+spdk_lvol_destroy(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn, void *cb_arg)
+{
+ if (lvol->ref_count != 0) {
+ cb_fn(cb_arg, -ENODEV);
+ }
+
+ TAILQ_REMOVE(&lvol->lvol_store->lvols, lvol, link);
+
+ SPDK_CU_ASSERT_FATAL(cb_fn != NULL);
+ cb_fn(cb_arg, 0);
+
+ g_lvol = NULL;
+ free(lvol->unique_id);
+ free(lvol);
+}
+
+void
+spdk_bdev_io_complete(struct spdk_bdev_io *bdev_io, enum spdk_bdev_io_status status)
+{
+}
+
+struct spdk_io_channel *spdk_lvol_get_io_channel(struct spdk_lvol *lvol)
+{
+ CU_ASSERT(lvol == g_lvol);
+ return g_ch;
+}
+
+void
+spdk_bdev_io_get_buf(struct spdk_bdev_io *bdev_io, spdk_bdev_io_get_buf_cb cb, uint64_t len)
+{
+ CU_ASSERT(cb == lvol_read);
+}
+
+void
+spdk_blob_io_read(struct spdk_blob *blob, struct spdk_io_channel *channel,
+ void *payload, uint64_t offset, uint64_t length,
+ spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+}
+
+void
+spdk_blob_io_write(struct spdk_blob *blob, struct spdk_io_channel *channel,
+ void *payload, uint64_t offset, uint64_t length,
+ spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+}
+
+void
+spdk_blob_io_unmap(struct spdk_blob *blob, struct spdk_io_channel *channel,
+ uint64_t offset, uint64_t length, spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ CU_ASSERT(blob == NULL);
+ CU_ASSERT(channel == g_ch);
+ CU_ASSERT(offset == g_io->u.bdev.offset_blocks);
+ CU_ASSERT(length == g_io->u.bdev.num_blocks);
+}
+
+void
+spdk_blob_io_write_zeroes(struct spdk_blob *blob, struct spdk_io_channel *channel,
+ uint64_t offset, uint64_t length, spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ CU_ASSERT(blob == NULL);
+ CU_ASSERT(channel == g_ch);
+ CU_ASSERT(offset == g_io->u.bdev.offset_blocks);
+ CU_ASSERT(length == g_io->u.bdev.num_blocks);
+}
+
+void
+spdk_blob_io_writev(struct spdk_blob *blob, struct spdk_io_channel *channel,
+ struct iovec *iov, int iovcnt, uint64_t offset, uint64_t length,
+ spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ CU_ASSERT(blob == NULL);
+ CU_ASSERT(channel == g_ch);
+ CU_ASSERT(offset == g_io->u.bdev.offset_blocks);
+ CU_ASSERT(length == g_io->u.bdev.num_blocks);
+}
+
+void
+spdk_blob_io_readv(struct spdk_blob *blob, struct spdk_io_channel *channel,
+ struct iovec *iov, int iovcnt, uint64_t offset, uint64_t length,
+ spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ CU_ASSERT(blob == NULL);
+ CU_ASSERT(channel == g_ch);
+ CU_ASSERT(offset == g_io->u.bdev.offset_blocks);
+ CU_ASSERT(length == g_io->u.bdev.num_blocks);
+}
+
+void
+spdk_bdev_module_list_add(struct spdk_bdev_module *bdev_module)
+{
+}
+
+int
+spdk_json_write_name(struct spdk_json_write_ctx *w, const char *name)
+{
+ return 0;
+}
+
+int
+spdk_json_write_array_begin(struct spdk_json_write_ctx *w)
+{
+ return 0;
+}
+
+int
+spdk_json_write_array_end(struct spdk_json_write_ctx *w)
+{
+ return 0;
+}
+
+int
+spdk_json_write_string(struct spdk_json_write_ctx *w, const char *val)
+{
+ return 0;
+}
+
+int
+spdk_json_write_bool(struct spdk_json_write_ctx *w, bool val)
+{
+ return 0;
+}
+
+int
+spdk_json_write_object_begin(struct spdk_json_write_ctx *w)
+{
+ return 0;
+}
+
+int
+spdk_json_write_object_end(struct spdk_json_write_ctx *w)
+{
+ return 0;
+}
+
+const char *
+spdk_bdev_get_name(const struct spdk_bdev *bdev)
+{
+ return "test";
+}
+
+int
+spdk_vbdev_register(struct spdk_bdev *vbdev, struct spdk_bdev **base_bdevs, int base_bdev_count)
+{
+ TAILQ_INIT(&vbdev->aliases);
+
+ g_registered_bdevs++;
+ return 0;
+}
+
+void
+spdk_bdev_module_examine_done(struct spdk_bdev_module *module)
+{
+ SPDK_CU_ASSERT_FATAL(g_examine_done != true);
+ g_examine_done = true;
+}
+
+static struct spdk_lvol *
+_lvol_create(struct spdk_lvol_store *lvs)
+{
+ struct spdk_lvol *lvol = calloc(1, sizeof(*lvol));
+
+ SPDK_CU_ASSERT_FATAL(lvol != NULL);
+
+ lvol->lvol_store = lvs;
+ lvol->ref_count++;
+ lvol->unique_id = spdk_sprintf_alloc("%s", "UNIT_TEST_UUID");
+ SPDK_CU_ASSERT_FATAL(lvol->unique_id != NULL);
+
+ TAILQ_INSERT_TAIL(&lvol->lvol_store->lvols, lvol, link);
+
+ return lvol;
+}
+
+int
+spdk_lvol_create(struct spdk_lvol_store *lvs, const char *name, size_t sz,
+ bool thin_provision, spdk_lvol_op_with_handle_complete cb_fn, void *cb_arg)
+{
+ struct spdk_lvol *lvol;
+
+ lvol = _lvol_create(lvs);
+ snprintf(lvol->name, sizeof(lvol->name), "%s", name);
+ cb_fn(cb_arg, lvol, 0);
+
+ return 0;
+}
+
+void
+spdk_lvol_create_snapshot(struct spdk_lvol *lvol, const char *snapshot_name,
+ spdk_lvol_op_with_handle_complete cb_fn, void *cb_arg)
+{
+ struct spdk_lvol *snap;
+
+ snap = _lvol_create(lvol->lvol_store);
+ snprintf(snap->name, sizeof(snap->name), "%s", snapshot_name);
+ cb_fn(cb_arg, snap, 0);
+}
+
+void
+spdk_lvol_create_clone(struct spdk_lvol *lvol, const char *clone_name,
+ spdk_lvol_op_with_handle_complete cb_fn, void *cb_arg)
+{
+ struct spdk_lvol *clone;
+
+ clone = _lvol_create(lvol->lvol_store);
+ snprintf(clone->name, sizeof(clone->name), "%s", clone_name);
+ cb_fn(cb_arg, clone, 0);
+}
+
+static void
+lvol_store_op_complete(void *cb_arg, int lvserrno)
+{
+ g_lvserrno = lvserrno;
+ return;
+}
+
+static void
+lvol_store_op_with_handle_complete(void *cb_arg, struct spdk_lvol_store *lvs, int lvserrno)
+{
+ g_lvserrno = lvserrno;
+ g_lvol_store = lvs;
+ return;
+}
+
+static void
+vbdev_lvol_create_complete(void *cb_arg, struct spdk_lvol *lvol, int lvolerrno)
+{
+ g_lvolerrno = lvolerrno;
+ g_lvol = lvol;
+}
+
+static void
+vbdev_lvol_resize_complete(void *cb_arg, int lvolerrno)
+{
+ g_lvolerrno = lvolerrno;
+}
+
+static void
+vbdev_lvol_rename_complete(void *cb_arg, int lvolerrno)
+{
+ g_lvolerrno = lvolerrno;
+}
+
+static void
+ut_lvs_destroy(void)
+{
+ int rc = 0;
+ int sz = 10;
+ struct spdk_lvol_store *lvs;
+
+ /* Lvol store is successfully created */
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+
+ lvs = g_lvol_store;
+ g_lvol_store = NULL;
+
+ spdk_uuid_generate(&lvs->uuid);
+
+ /* Successfully create lvol, which should be unloaded with lvs later */
+ g_lvolerrno = -1;
+ rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvolerrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ /* Unload lvol store */
+ vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+}
+
+static void
+ut_lvol_init(void)
+{
+ struct spdk_lvol_store *lvs;
+ int sz = 10;
+ int rc;
+
+ /* Lvol store is successfully created */
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+ lvs = g_lvol_store;
+
+ /* Successful lvol create */
+ g_lvolerrno = -1;
+ rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ CU_ASSERT(g_lvol != NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+
+ /* Successful lvol destroy */
+ vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvol == NULL);
+
+ /* Destroy lvol store */
+ vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+}
+
+static void
+ut_lvol_snapshot(void)
+{
+ struct spdk_lvol_store *lvs;
+ int sz = 10;
+ int rc;
+ struct spdk_lvol *lvol = NULL;
+
+ /* Lvol store is successfully created */
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+ lvs = g_lvol_store;
+
+ /* Successful lvol create */
+ g_lvolerrno = -1;
+ rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+
+ lvol = g_lvol;
+
+ /* Successful snap create */
+ vbdev_lvol_create_snapshot(lvol, "snap", vbdev_lvol_create_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ CU_ASSERT(g_lvol != NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+
+ /* Successful lvol destroy */
+ vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvol == NULL);
+
+ /* Successful snap destroy */
+ g_lvol = lvol;
+ vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvol == NULL);
+
+ /* Destroy lvol store */
+ vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+}
+
+static void
+ut_lvol_clone(void)
+{
+ struct spdk_lvol_store *lvs;
+ int sz = 10;
+ int rc;
+ struct spdk_lvol *lvol = NULL;
+ struct spdk_lvol *snap = NULL;
+ struct spdk_lvol *clone = NULL;
+
+ /* Lvol store is successfully created */
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+ lvs = g_lvol_store;
+
+ /* Successful lvol create */
+ g_lvolerrno = -1;
+ rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+
+ lvol = g_lvol;
+
+ /* Successful snap create */
+ vbdev_lvol_create_snapshot(lvol, "snap", vbdev_lvol_create_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+
+ snap = g_lvol;
+
+ /* Successful clone create */
+ vbdev_lvol_create_clone(snap, "clone", vbdev_lvol_create_complete, NULL);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+
+ clone = g_lvol;
+
+ /* Successful lvol destroy */
+ g_lvol = lvol;
+ vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvol == NULL);
+
+ /* Successful clone destroy */
+ g_lvol = clone;
+ vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvol == NULL);
+
+ /* Successful lvol destroy */
+ g_lvol = snap;
+ vbdev_lvol_destroy(g_lvol, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvol == NULL);
+
+ /* Destroy lvol store */
+ vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+}
+
+static void
+ut_lvol_hotremove(void)
+{
+ int rc = 0;
+
+ lvol_store_initialize_fail = false;
+ lvol_store_initialize_cb_fail = false;
+ lvol_already_opened = false;
+
+ /* Lvol store is successfully created */
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+
+ /* Hot remove callback with NULL - stability check */
+ vbdev_lvs_hotremove_cb(NULL);
+
+ /* Hot remove lvs on bdev removal */
+ vbdev_lvs_hotremove_cb(&g_bdev);
+
+ CU_ASSERT(g_lvol_store == NULL);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_lvol_pairs));
+
+}
+
+static void
+ut_lvs_examine_check(bool success)
+{
+ struct lvol_store_bdev *lvs_bdev;
+
+ /* Examine was finished regardless of result */
+ CU_ASSERT(g_examine_done == true);
+ g_examine_done = false;
+
+ if (success) {
+ SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_spdk_lvol_pairs));
+ lvs_bdev = TAILQ_FIRST(&g_spdk_lvol_pairs);
+ SPDK_CU_ASSERT_FATAL(lvs_bdev != NULL);
+ g_lvol_store = lvs_bdev->lvs;
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+ } else {
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_spdk_lvol_pairs));
+ g_lvol_store = NULL;
+ }
+}
+
+static void
+ut_lvol_examine(void)
+{
+ /* Examine unsuccessfully - bdev already opened */
+ g_lvserrno = -1;
+ lvol_already_opened = true;
+ vbdev_lvs_examine(&g_bdev);
+ ut_lvs_examine_check(false);
+
+ /* Examine unsuccessfully - fail on lvol store */
+ g_lvserrno = -1;
+ lvol_already_opened = false;
+ vbdev_lvs_examine(&g_bdev);
+ ut_lvs_examine_check(false);
+
+ /* Examine successfully
+ * - one lvol fails to load
+ * - lvs is loaded with no lvols present */
+ g_lvserrno = 0;
+ g_lvolerrno = -1;
+ g_num_lvols = 1;
+ lvol_already_opened = false;
+ g_registered_bdevs = 0;
+ vbdev_lvs_examine(&g_bdev);
+ ut_lvs_examine_check(true);
+ CU_ASSERT(g_registered_bdevs == 0);
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_store->lvols));
+ vbdev_lvs_destruct(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+
+ /* Examine successfully */
+ g_lvserrno = 0;
+ g_lvolerrno = 0;
+ g_registered_bdevs = 0;
+ lvol_already_opened = false;
+ vbdev_lvs_examine(&g_bdev);
+ ut_lvs_examine_check(true);
+ CU_ASSERT(g_registered_bdevs != 0);
+ SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_lvol_store->lvols));
+ vbdev_lvs_destruct(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+}
+
+static void
+ut_lvol_rename(void)
+{
+ struct spdk_lvol_store *lvs;
+ struct spdk_lvol *lvol;
+ struct spdk_lvol *lvol2;
+ int sz = 10;
+ int rc;
+
+ /* Lvol store is successfully created */
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+ lvs = g_lvol_store;
+
+ /* Successful lvols create */
+ g_lvolerrno = -1;
+ rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ CU_ASSERT(g_lvol != NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+ lvol = g_lvol;
+
+ g_lvolerrno = -1;
+ rc = vbdev_lvol_create(lvs, "lvol2", sz, false, vbdev_lvol_create_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ CU_ASSERT(g_lvol != NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+ lvol2 = g_lvol;
+
+ /* Successful rename lvol */
+ vbdev_lvol_rename(lvol, "new_lvol_name", vbdev_lvol_rename_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(g_lvolerrno == 0);
+ CU_ASSERT_STRING_EQUAL(lvol->name, "new_lvol_name");
+
+ /* Renaming lvol with name already existing */
+ g_bdev_alias_already_exists = true;
+ vbdev_lvol_rename(lvol2, "new_lvol_name", vbdev_lvol_rename_complete, NULL);
+ g_bdev_alias_already_exists = false;
+ SPDK_CU_ASSERT_FATAL(g_lvolerrno != 0);
+ CU_ASSERT_STRING_NOT_EQUAL(lvol2->name, "new_lvol_name");
+
+ /* Renaming lvol with it's own name */
+ vbdev_lvol_rename(lvol, "new_lvol_name", vbdev_lvol_rename_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(g_lvolerrno == 0);
+ CU_ASSERT_STRING_EQUAL(lvol->name, "new_lvol_name");
+
+ /* Successful lvols destroy */
+ vbdev_lvol_destroy(lvol, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvol == NULL);
+
+ vbdev_lvol_destroy(lvol2, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvol == NULL);
+
+ /* Destroy lvol store */
+ vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+}
+
+static void
+ut_lvol_destroy(void)
+{
+ struct spdk_lvol_store *lvs;
+ struct spdk_lvol *lvol;
+ struct spdk_lvol *lvol2;
+ int sz = 10;
+ int rc;
+
+ /* Lvol store is successfully created */
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+ lvs = g_lvol_store;
+
+ /* Successful lvols create */
+ g_lvolerrno = -1;
+ rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ CU_ASSERT(g_lvol != NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+ lvol = g_lvol;
+
+ g_lvolerrno = -1;
+ rc = vbdev_lvol_create(lvs, "lvol2", sz, false, vbdev_lvol_create_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ CU_ASSERT(g_lvol != NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+ lvol2 = g_lvol;
+
+ /* Unsuccessful lvols destroy */
+ g_lvol_deletable = false;
+ vbdev_lvol_destroy(lvol, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvol != NULL);
+ CU_ASSERT(g_lvserrno == -EPERM);
+
+ g_lvol_deletable = true;
+ /* Successful lvols destroy */
+ vbdev_lvol_destroy(lvol, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvol == NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+
+ /* Hot remove lvol bdev */
+ vbdev_lvol_unregister(lvol2);
+
+ /* Unload lvol store */
+ vbdev_lvs_unload(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+}
+
+static void
+ut_lvol_resize(void)
+{
+ struct spdk_lvol_store *lvs;
+ struct spdk_lvol *lvol;
+ int sz = 10;
+ int rc = 0;
+
+ /* Lvol store is successfully created */
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+ lvs = g_lvol_store;
+
+ /* Successful lvol create */
+ g_lvolerrno = -1;
+ rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvolerrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ lvol = g_lvol;
+
+ /* Successful lvol resize */
+ g_lvolerrno = -1;
+ vbdev_lvol_resize(lvol, 20, vbdev_lvol_resize_complete, NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+ CU_ASSERT(lvol->bdev->blockcnt == 20 * g_cluster_size / lvol->bdev->blocklen);
+
+ /* Resize with NULL lvol */
+ vbdev_lvol_resize(NULL, 20, vbdev_lvol_resize_complete, NULL);
+ CU_ASSERT(g_lvolerrno != 0);
+
+ /* Successful lvol destroy */
+ vbdev_lvol_destroy(lvol, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvol == NULL);
+
+ /* Destroy lvol store */
+ vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+}
+
+static void
+ut_lvs_unload(void)
+{
+ int rc = 0;
+ int sz = 10;
+ struct spdk_lvol_store *lvs;
+
+ /* Lvol store is successfully created */
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+
+ lvs = g_lvol_store;
+ g_lvol_store = NULL;
+
+ spdk_uuid_generate(&lvs->uuid);
+
+ /* Successfully create lvol, which should be destroyed with lvs later */
+ g_lvolerrno = -1;
+ rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvolerrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ /* Unload lvol store */
+ vbdev_lvs_unload(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+ CU_ASSERT(g_lvol != NULL);
+}
+
+static void
+ut_lvs_init(void)
+{
+ int rc = 0;
+ struct spdk_lvol_store *lvs;
+
+ /* spdk_lvs_init() fails */
+ lvol_store_initialize_fail = true;
+
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc != 0);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+
+ lvol_store_initialize_fail = false;
+
+ /* spdk_lvs_init_cb() fails */
+ lvol_store_initialize_cb_fail = true;
+
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno != 0);
+ CU_ASSERT(g_lvol_store == NULL);
+
+ lvol_store_initialize_cb_fail = false;
+
+ /* Lvol store is successfully created */
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+
+ lvs = g_lvol_store;
+ g_lvol_store = NULL;
+
+ /* Bdev with lvol store already claimed */
+ rc = vbdev_lvs_create(&g_bdev, "lvs", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc != 0);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+
+ /* Destruct lvol store */
+ vbdev_lvs_destruct(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+}
+
+static void
+ut_vbdev_lvol_get_io_channel(void)
+{
+ struct spdk_io_channel *ch;
+
+ g_lvol = calloc(1, sizeof(struct spdk_lvol));
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ ch = vbdev_lvol_get_io_channel(g_lvol);
+ CU_ASSERT(ch == g_ch);
+
+ free(g_lvol);
+}
+
+static void
+ut_vbdev_lvol_io_type_supported(void)
+{
+ struct spdk_lvol *lvol;
+ bool ret;
+
+ lvol = calloc(1, sizeof(struct spdk_lvol));
+ SPDK_CU_ASSERT_FATAL(lvol != NULL);
+
+ g_blob_is_read_only = false;
+
+ /* Supported types */
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_READ);
+ CU_ASSERT(ret == true);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_WRITE);
+ CU_ASSERT(ret == true);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_RESET);
+ CU_ASSERT(ret == true);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_UNMAP);
+ CU_ASSERT(ret == true);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_WRITE_ZEROES);
+ CU_ASSERT(ret == true);
+
+ /* Unsupported types */
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_FLUSH);
+ CU_ASSERT(ret == false);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_NVME_ADMIN);
+ CU_ASSERT(ret == false);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_NVME_IO);
+ CU_ASSERT(ret == false);
+
+ g_blob_is_read_only = true;
+
+ /* Supported types */
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_READ);
+ CU_ASSERT(ret == true);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_RESET);
+ CU_ASSERT(ret == true);
+
+ /* Unsupported types */
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_WRITE);
+ CU_ASSERT(ret == false);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_UNMAP);
+ CU_ASSERT(ret == false);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_WRITE_ZEROES);
+ CU_ASSERT(ret == false);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_FLUSH);
+ CU_ASSERT(ret == false);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_NVME_ADMIN);
+ CU_ASSERT(ret == false);
+ ret = vbdev_lvol_io_type_supported(lvol, SPDK_BDEV_IO_TYPE_NVME_IO);
+ CU_ASSERT(ret == false);
+
+ free(lvol);
+}
+
+static void
+ut_lvol_read_write(void)
+{
+ g_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct lvol_task));
+ SPDK_CU_ASSERT_FATAL(g_io != NULL);
+ g_base_bdev = calloc(1, sizeof(struct spdk_bdev));
+ SPDK_CU_ASSERT_FATAL(g_base_bdev != NULL);
+ g_lvol = calloc(1, sizeof(struct spdk_lvol));
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ g_task = (struct lvol_task *)g_io->driver_ctx;
+ g_io->bdev = g_base_bdev;
+ g_io->bdev->ctxt = g_lvol;
+ g_io->u.bdev.offset_blocks = 20;
+ g_io->u.bdev.num_blocks = 20;
+
+ lvol_read(g_ch, g_io);
+ CU_ASSERT(g_task->status == SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ lvol_write(g_lvol, g_ch, g_io);
+ CU_ASSERT(g_task->status == SPDK_BDEV_IO_STATUS_SUCCESS);
+
+ free(g_io);
+ free(g_base_bdev);
+ free(g_lvol);
+}
+
+static void
+ut_vbdev_lvol_submit_request(void)
+{
+ struct spdk_lvol request_lvol = {};
+ g_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct lvol_task));
+ SPDK_CU_ASSERT_FATAL(g_io != NULL);
+ g_base_bdev = calloc(1, sizeof(struct spdk_bdev));
+ SPDK_CU_ASSERT_FATAL(g_base_bdev != NULL);
+ g_task = (struct lvol_task *)g_io->driver_ctx;
+ g_io->bdev = g_base_bdev;
+
+ g_io->type = SPDK_BDEV_IO_TYPE_READ;
+ g_base_bdev->ctxt = &request_lvol;
+ vbdev_lvol_submit_request(g_ch, g_io);
+
+ free(g_io);
+ free(g_base_bdev);
+}
+
+static void
+ut_lvs_rename(void)
+{
+ int rc = 0;
+ int sz = 10;
+ struct spdk_lvol_store *lvs;
+
+ /* Lvol store is successfully created */
+ rc = vbdev_lvs_create(&g_bdev, "old_lvs_name", 0, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(g_lvol_store->bs_dev != NULL);
+
+ lvs = g_lvol_store;
+ g_lvol_store = NULL;
+
+ g_base_bdev = calloc(1, sizeof(*g_base_bdev));
+ SPDK_CU_ASSERT_FATAL(g_base_bdev != NULL);
+
+ /* Successfully create lvol, which should be destroyed with lvs later */
+ g_lvolerrno = -1;
+ rc = vbdev_lvol_create(lvs, "lvol", sz, false, vbdev_lvol_create_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvolerrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ /* Trying to rename lvs with lvols created */
+ vbdev_lvs_rename(lvs, "new_lvs_name", lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name");
+ CU_ASSERT_STRING_EQUAL(TAILQ_FIRST(&g_lvol->bdev->aliases)->alias, "new_lvs_name/lvol");
+
+ /* Trying to rename lvs with name already used by another lvs */
+ /* This is a bdev_lvol test, so g_lvs_with_name_already_exists simulates
+ * existing lvs with name 'another_new_lvs_name' and this name in fact is not compared */
+ g_lvs_with_name_already_exists = true;
+ vbdev_lvs_rename(lvs, "another_new_lvs_name", lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == -EEXIST);
+ CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name");
+ CU_ASSERT_STRING_EQUAL(TAILQ_FIRST(&g_lvol->bdev->aliases)->alias, "new_lvs_name/lvol");
+ g_lvs_with_name_already_exists = false;
+
+ /* Unload lvol store */
+ g_lvol_store = lvs;
+ vbdev_lvs_destruct(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store == NULL);
+
+ free(g_base_bdev->name);
+ free(g_base_bdev);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("lvol", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "ut_lvs_init", ut_lvs_init) == NULL ||
+ CU_add_test(suite, "ut_lvol_init", ut_lvol_init) == NULL ||
+ CU_add_test(suite, "ut_lvol_snapshot", ut_lvol_snapshot) == NULL ||
+ CU_add_test(suite, "ut_lvol_clone", ut_lvol_clone) == NULL ||
+ CU_add_test(suite, "ut_lvs_destroy", ut_lvs_destroy) == NULL ||
+ CU_add_test(suite, "ut_lvs_unload", ut_lvs_unload) == NULL ||
+ CU_add_test(suite, "ut_lvol_resize", ut_lvol_resize) == NULL ||
+ CU_add_test(suite, "lvol_hotremove", ut_lvol_hotremove) == NULL ||
+ CU_add_test(suite, "ut_vbdev_lvol_get_io_channel", ut_vbdev_lvol_get_io_channel) == NULL ||
+ CU_add_test(suite, "ut_vbdev_lvol_io_type_supported", ut_vbdev_lvol_io_type_supported) == NULL ||
+ CU_add_test(suite, "ut_lvol_read_write", ut_lvol_read_write) == NULL ||
+ CU_add_test(suite, "ut_vbdev_lvol_submit_request", ut_vbdev_lvol_submit_request) == NULL ||
+ CU_add_test(suite, "lvol_examine", ut_lvol_examine) == NULL ||
+ CU_add_test(suite, "ut_lvol_rename", ut_lvol_rename) == NULL ||
+ CU_add_test(suite, "ut_lvol_destroy", ut_lvol_destroy) == NULL ||
+ CU_add_test(suite, "ut_lvs_rename", ut_lvs_rename) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/blob/Makefile b/src/spdk/test/unit/lib/blob/Makefile
new file mode 100644
index 00000000..c57d0b1c
--- /dev/null
+++ b/src/spdk/test/unit/lib/blob/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 = blob.c
+
+.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/unit/lib/blob/blob.c/.gitignore b/src/spdk/test/unit/lib/blob/blob.c/.gitignore
new file mode 100644
index 00000000..553f5465
--- /dev/null
+++ b/src/spdk/test/unit/lib/blob/blob.c/.gitignore
@@ -0,0 +1 @@
+blob_ut
diff --git a/src/spdk/test/unit/lib/blob/blob.c/Makefile b/src/spdk/test/unit/lib/blob/blob.c/Makefile
new file mode 100644
index 00000000..6e279ff0
--- /dev/null
+++ b/src/spdk/test/unit/lib/blob/blob.c/Makefile
@@ -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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.mock.unittest.mk
+
+TEST_FILE = blob_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/blob/blob.c/blob_ut.c b/src/spdk/test/unit/lib/blob/blob.c/blob_ut.c
new file mode 100644
index 00000000..88f438eb
--- /dev/null
+++ b/src/spdk/test/unit/lib/blob/blob.c/blob_ut.c
@@ -0,0 +1,5914 @@
+/*-
+ * 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_cunit.h"
+#include "spdk/blob.h"
+#include "spdk/string.h"
+
+#include "common/lib/test_env.c"
+#include "../bs_dev_common.c"
+#include "blob/blobstore.c"
+#include "blob/request.c"
+#include "blob/zeroes.c"
+#include "blob/blob_bs_dev.c"
+
+struct spdk_blob_store *g_bs;
+spdk_blob_id g_blobid;
+struct spdk_blob *g_blob;
+int g_bserrno;
+struct spdk_xattr_names *g_names;
+int g_done;
+char *g_xattr_names[] = {"first", "second", "third"};
+char *g_xattr_values[] = {"one", "two", "three"};
+uint64_t g_ctx = 1729;
+
+struct spdk_bs_super_block_ver1 {
+ uint8_t signature[8];
+ uint32_t version;
+ uint32_t length;
+ uint32_t clean; /* If there was a clean shutdown, this is 1. */
+ spdk_blob_id super_blob;
+
+ uint32_t cluster_size; /* In bytes */
+
+ uint32_t used_page_mask_start; /* Offset from beginning of disk, in pages */
+ uint32_t used_page_mask_len; /* Count, in pages */
+
+ uint32_t used_cluster_mask_start; /* Offset from beginning of disk, in pages */
+ uint32_t used_cluster_mask_len; /* Count, in pages */
+
+ uint32_t md_start; /* Offset from beginning of disk, in pages */
+ uint32_t md_len; /* Count, in pages */
+
+ uint8_t reserved[4036];
+ uint32_t crc;
+} __attribute__((packed));
+SPDK_STATIC_ASSERT(sizeof(struct spdk_bs_super_block_ver1) == 0x1000, "Invalid super block size");
+
+
+static void
+_get_xattr_value(void *arg, const char *name,
+ const void **value, size_t *value_len)
+{
+ uint64_t i;
+
+ SPDK_CU_ASSERT_FATAL(value_len != NULL);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(arg == &g_ctx)
+
+ for (i = 0; i < sizeof(g_xattr_names); i++) {
+ if (!strcmp(name, g_xattr_names[i])) {
+ *value_len = strlen(g_xattr_values[i]);
+ *value = g_xattr_values[i];
+ break;
+ }
+ }
+}
+
+static void
+_get_xattr_value_null(void *arg, const char *name,
+ const void **value, size_t *value_len)
+{
+ SPDK_CU_ASSERT_FATAL(value_len != NULL);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(arg == NULL)
+
+ *value_len = 0;
+ *value = NULL;
+}
+
+
+
+static void
+bs_op_complete(void *cb_arg, int bserrno)
+{
+ g_bserrno = bserrno;
+}
+
+static void
+bs_op_with_handle_complete(void *cb_arg, struct spdk_blob_store *bs,
+ int bserrno)
+{
+ g_bs = bs;
+ g_bserrno = bserrno;
+}
+
+static void
+blob_op_complete(void *cb_arg, int bserrno)
+{
+ g_bserrno = bserrno;
+}
+
+static void
+blob_op_with_id_complete(void *cb_arg, spdk_blob_id blobid, int bserrno)
+{
+ g_blobid = blobid;
+ g_bserrno = bserrno;
+}
+
+static void
+blob_op_with_handle_complete(void *cb_arg, struct spdk_blob *blb, int bserrno)
+{
+ g_blob = blb;
+ g_bserrno = bserrno;
+}
+
+static void
+blob_init(void)
+{
+ struct spdk_bs_dev *dev;
+
+ dev = init_dev();
+
+ /* should fail for an unsupported blocklen */
+ dev->blocklen = 500;
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ dev = init_dev();
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_super(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ spdk_blob_id blobid;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ /* Get the super blob without having set one */
+ spdk_bs_get_super(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == -ENOENT);
+ CU_ASSERT(g_blobid == SPDK_BLOBID_INVALID);
+
+ /* Create a blob */
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ /* Set the blob as the super blob */
+ spdk_bs_set_super(bs, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Get the super blob */
+ spdk_bs_get_super(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(blobid == g_blobid);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_open(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ spdk_blob_id blobid, blobid2;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ blobid2 = spdk_blob_get_id(blob);
+ CU_ASSERT(blobid == blobid2);
+
+ /* Try to open file again. It should return success. */
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(blob == g_blob);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /*
+ * Close the file a second time, releasing the second reference. This
+ * should succeed.
+ */
+ blob = g_blob;
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /*
+ * Try to open file again. It should succeed. This tests the case
+ * where the file is opened, closed, then re-opened again.
+ */
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_create(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ /* Create blob with 10 clusters */
+
+ spdk_blob_opts_init(&opts);
+ opts.num_clusters = 10;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10)
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Create blob with 0 clusters */
+
+ spdk_blob_opts_init(&opts);
+ opts.num_clusters = 0;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 0)
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Create blob with default options (opts == NULL) */
+
+ spdk_bs_create_blob_ext(bs, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 0)
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Try to create blob with size larger than blobstore */
+
+ spdk_blob_opts_init(&opts);
+ opts.num_clusters = bs->total_clusters + 1;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == -ENOSPC);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+}
+
+static void
+blob_create_internal(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_blob_opts opts;
+ struct spdk_blob_xattr_opts internal_xattrs;
+ const void *value;
+ size_t value_len;
+ spdk_blob_id blobid;
+ int rc;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ /* Create blob with custom xattrs */
+
+ spdk_blob_opts_init(&opts);
+ _spdk_blob_xattrs_init(&internal_xattrs);
+ internal_xattrs.count = 3;
+ internal_xattrs.names = g_xattr_names;
+ internal_xattrs.get_value = _get_xattr_value;
+ internal_xattrs.ctx = &g_ctx;
+
+ _spdk_bs_create_blob(bs, &opts, &internal_xattrs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ rc = _spdk_blob_get_xattr_value(blob, g_xattr_names[0], &value, &value_len, true);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[0]));
+ CU_ASSERT_NSTRING_EQUAL_FATAL(value, g_xattr_values[0], value_len);
+
+ rc = _spdk_blob_get_xattr_value(blob, g_xattr_names[1], &value, &value_len, true);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[1]));
+ CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[1], value_len);
+
+ rc = _spdk_blob_get_xattr_value(blob, g_xattr_names[2], &value, &value_len, true);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[2]));
+ CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[2], value_len);
+
+ rc = spdk_blob_get_xattr_value(blob, g_xattr_names[0], &value, &value_len);
+ CU_ASSERT(rc != 0);
+
+ rc = spdk_blob_get_xattr_value(blob, g_xattr_names[1], &value, &value_len);
+ CU_ASSERT(rc != 0);
+
+ rc = spdk_blob_get_xattr_value(blob, g_xattr_names[2], &value, &value_len);
+ CU_ASSERT(rc != 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Create blob with NULL internal options */
+
+ _spdk_bs_create_blob(bs, NULL, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ CU_ASSERT(TAILQ_FIRST(&g_blob->xattrs_internal) == NULL);
+
+ blob = g_blob;
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+}
+
+static void
+blob_thin_provision(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_blob_opts opts;
+ struct spdk_bs_opts bs_opts;
+ spdk_blob_id blobid;
+
+ dev = init_dev();
+ spdk_bs_opts_init(&bs_opts);
+ snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "TESTTYPE");
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &bs_opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ bs = g_bs;
+
+ /* Create blob with thin provisioning enabled */
+
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = true;
+ opts.num_clusters = 10;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+ CU_ASSERT(blob->invalid_flags & SPDK_BLOB_THIN_PROV);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Do not shut down cleanly. This makes sure that when we load again
+ * and try to recover a valid used_cluster map, that blobstore will
+ * ignore clusters with index 0 since these are unallocated clusters.
+ */
+
+ /* Load an existing blob store and check if invalid_flags is set */
+ dev = init_dev();
+ snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "TESTTYPE");
+ spdk_bs_load(dev, &bs_opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ bs = g_bs;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+ CU_ASSERT(blob->invalid_flags & SPDK_BLOB_THIN_PROV);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_snapshot(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_blob *snapshot, *snapshot2;
+ struct spdk_blob_bs_dev *blob_bs_dev;
+ struct spdk_blob_opts opts;
+ struct spdk_blob_xattr_opts xattrs;
+ spdk_blob_id blobid;
+ spdk_blob_id snapshotid;
+ const void *value;
+ size_t value_len;
+ int rc;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ /* Create blob with 10 clusters */
+ spdk_blob_opts_init(&opts);
+ opts.num_clusters = 10;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10)
+
+ /* Create snapshot from blob */
+ spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ snapshotid = g_blobid;
+
+ spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ snapshot = g_blob;
+ CU_ASSERT(snapshot->data_ro == true)
+ CU_ASSERT(snapshot->md_ro == true)
+ CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 10)
+
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10)
+ CU_ASSERT(blob->invalid_flags & SPDK_BLOB_THIN_PROV);
+ CU_ASSERT(spdk_mem_all_zero(blob->active.clusters,
+ blob->active.num_clusters * sizeof(blob->active.clusters[0])));
+
+ /* Try to create snapshot from clone with xattrs */
+ xattrs.names = g_xattr_names;
+ xattrs.get_value = _get_xattr_value;
+ xattrs.count = 3;
+ xattrs.ctx = &g_ctx;
+ spdk_bs_create_snapshot(bs, blobid, &xattrs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ snapshot2 = g_blob;
+ CU_ASSERT(snapshot2->data_ro == true)
+ CU_ASSERT(snapshot2->md_ro == true)
+ CU_ASSERT(spdk_blob_get_num_clusters(snapshot2) == 10)
+
+ /* Confirm that blob is backed by snapshot2 and snapshot2 is backed by snapshot */
+ CU_ASSERT(snapshot->back_bs_dev == NULL);
+ SPDK_CU_ASSERT_FATAL(blob->back_bs_dev != NULL);
+ SPDK_CU_ASSERT_FATAL(snapshot2->back_bs_dev != NULL);
+
+ blob_bs_dev = (struct spdk_blob_bs_dev *)blob->back_bs_dev;
+ CU_ASSERT(blob_bs_dev->blob == snapshot2);
+
+ blob_bs_dev = (struct spdk_blob_bs_dev *)snapshot2->back_bs_dev;
+ CU_ASSERT(blob_bs_dev->blob == snapshot);
+
+ rc = spdk_blob_get_xattr_value(snapshot2, g_xattr_names[0], &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[0]));
+ CU_ASSERT_NSTRING_EQUAL_FATAL(value, g_xattr_values[0], value_len);
+
+ rc = spdk_blob_get_xattr_value(snapshot2, g_xattr_names[1], &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[1]));
+ CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[1], value_len);
+
+ rc = spdk_blob_get_xattr_value(snapshot2, g_xattr_names[2], &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[2]));
+ CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[2], value_len);
+
+ /* Try to create snapshot from snapshot */
+ spdk_bs_create_snapshot(bs, snapshotid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+ CU_ASSERT(g_blobid == SPDK_BLOBID_INVALID);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(snapshot, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(snapshot2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_snapshot_freeze_io(void)
+{
+ struct spdk_io_channel *channel;
+ struct spdk_bs_channel *bs_channel;
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid;
+ uint32_t num_of_pages = 10;
+ uint8_t payload_read[num_of_pages * SPDK_BS_PAGE_SIZE];
+ uint8_t payload_write[num_of_pages * SPDK_BS_PAGE_SIZE];
+ uint8_t payload_zero[num_of_pages * SPDK_BS_PAGE_SIZE];
+
+ memset(payload_write, 0xE5, sizeof(payload_write));
+ memset(payload_read, 0x00, sizeof(payload_read));
+ memset(payload_zero, 0x00, sizeof(payload_zero));
+
+ dev = init_dev();
+ memset(g_dev_buffer, 0, DEV_BUFFER_SIZE);
+
+ /* Test freeze I/O during snapshot */
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ bs_channel = spdk_io_channel_get_ctx(channel);
+
+ /* Create blob with 10 clusters */
+ spdk_blob_opts_init(&opts);
+ opts.num_clusters = 10;
+ opts.thin_provision = false;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10);
+
+ /* Enable explicitly calling callbacks. On each read/write to back device
+ * execution will stop and wait until _bs_flush_scheduler is called */
+ g_scheduler_delay = true;
+
+ spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+
+ /* This is implementation specific.
+ * Flag 'frozen_io' is set in _spdk_bs_snapshot_freeze_cpl callback.
+ * Four async I/O operations happen before that. */
+
+ _bs_flush_scheduler(4);
+
+ CU_ASSERT(TAILQ_EMPTY(&bs_channel->queued_io));
+
+ /* Blob I/O should be frozen here */
+ CU_ASSERT(blob->frozen_refcnt == 1);
+
+ /* Write to the blob */
+ spdk_blob_io_write(blob, channel, payload_write, 0, num_of_pages, blob_op_complete, NULL);
+
+ /* Verify that I/O is queued */
+ CU_ASSERT(!TAILQ_EMPTY(&bs_channel->queued_io));
+ /* Verify that payload is not written to disk */
+ CU_ASSERT(memcmp(payload_zero, &g_dev_buffer[blob->active.clusters[0]*SPDK_BS_PAGE_SIZE],
+ SPDK_BS_PAGE_SIZE) == 0);
+
+ /* Disable scheduler delay.
+ * Finish all operations including spdk_bs_create_snapshot */
+ g_scheduler_delay = false;
+ _bs_flush_scheduler(1);
+
+ /* Verify snapshot */
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+
+ /* Verify that blob has unset frozen_io */
+ CU_ASSERT(blob->frozen_refcnt == 0);
+
+ /* Verify that postponed I/O completed successfully by comparing payload */
+ spdk_blob_io_read(blob, channel, payload_read, 0, num_of_pages, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_write, payload_read, num_of_pages * SPDK_BS_PAGE_SIZE) == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_clone(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob_opts opts;
+ struct spdk_blob *blob, *snapshot, *clone;
+ spdk_blob_id blobid, cloneid, snapshotid;
+ struct spdk_blob_xattr_opts xattrs;
+ const void *value;
+ size_t value_len;
+ int rc;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ /* Create blob with 10 clusters */
+
+ spdk_blob_opts_init(&opts);
+ opts.num_clusters = 10;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10)
+
+ /* Create snapshot */
+ spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ snapshotid = g_blobid;
+
+ spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ snapshot = g_blob;
+ CU_ASSERT(snapshot->data_ro == true)
+ CU_ASSERT(snapshot->md_ro == true)
+ CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 10);
+
+ spdk_blob_close(snapshot, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Create clone from snapshot with xattrs */
+ xattrs.names = g_xattr_names;
+ xattrs.get_value = _get_xattr_value;
+ xattrs.count = 3;
+ xattrs.ctx = &g_ctx;
+
+ spdk_bs_create_clone(bs, snapshotid, &xattrs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ cloneid = g_blobid;
+
+ spdk_bs_open_blob(bs, cloneid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ clone = g_blob;
+ CU_ASSERT(clone->data_ro == false)
+ CU_ASSERT(clone->md_ro == false)
+ CU_ASSERT(spdk_blob_get_num_clusters(clone) == 10);
+
+ rc = spdk_blob_get_xattr_value(clone, g_xattr_names[0], &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[0]));
+ CU_ASSERT_NSTRING_EQUAL_FATAL(value, g_xattr_values[0], value_len);
+
+ rc = spdk_blob_get_xattr_value(clone, g_xattr_names[1], &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[1]));
+ CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[1], value_len);
+
+ rc = spdk_blob_get_xattr_value(clone, g_xattr_names[2], &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[2]));
+ CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[2], value_len);
+
+
+ spdk_blob_close(clone, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Try to create clone from not read only blob */
+ spdk_bs_create_clone(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+ CU_ASSERT(g_blobid == SPDK_BLOBID_INVALID);
+
+ /* Mark blob as read only */
+ spdk_blob_set_read_only(blob);
+ spdk_blob_sync_md(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Create clone from read only blob */
+ spdk_bs_create_clone(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ cloneid = g_blobid;
+
+ spdk_bs_open_blob(bs, cloneid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ clone = g_blob;
+ CU_ASSERT(clone->data_ro == false)
+ CU_ASSERT(clone->md_ro == false)
+ CU_ASSERT(spdk_blob_get_num_clusters(clone) == 10);
+
+ spdk_blob_close(clone, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+}
+
+static void
+_blob_inflate(bool decouple_parent)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob_opts opts;
+ struct spdk_blob *blob, *snapshot;
+ spdk_blob_id blobid, snapshotid;
+ struct spdk_io_channel *channel;
+ uint64_t free_clusters;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ SPDK_CU_ASSERT_FATAL(channel != NULL);
+
+ /* Create blob with 10 clusters */
+
+ spdk_blob_opts_init(&opts);
+ opts.num_clusters = 10;
+ opts.thin_provision = true;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10)
+ CU_ASSERT(spdk_blob_is_thin_provisioned(blob) == true);
+
+ /* 1) Blob with no parent */
+ if (decouple_parent) {
+ /* Decouple parent of blob with no parent (should fail) */
+ spdk_bs_blob_decouple_parent(bs, channel, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+ } else {
+ /* Inflate of thin blob with no parent should made it thick */
+ spdk_bs_inflate_blob(bs, channel, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(spdk_blob_is_thin_provisioned(blob) == false);
+ }
+
+ spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ snapshotid = g_blobid;
+
+ CU_ASSERT(spdk_blob_is_thin_provisioned(blob) == true);
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10)
+
+ spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ snapshot = g_blob;
+ CU_ASSERT(snapshot->data_ro == true)
+ CU_ASSERT(snapshot->md_ro == true)
+ CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 10);
+
+ spdk_blob_close(snapshot, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ free_clusters = spdk_bs_free_cluster_count(bs);
+
+ /* 2) Blob with parent */
+ if (!decouple_parent) {
+ /* Do full blob inflation */
+ spdk_bs_inflate_blob(bs, channel, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ /* all 10 clusters should be allocated */
+ CU_ASSERT(spdk_bs_free_cluster_count(bs) == free_clusters - 10);
+ } else {
+ /* Decouple parent of blob */
+ spdk_bs_blob_decouple_parent(bs, channel, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ /* when only parent is removed, none of the clusters should be allocated */
+ CU_ASSERT(spdk_bs_free_cluster_count(bs) == free_clusters);
+ }
+
+ /* Now, it should be possible to delete snapshot */
+ spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10)
+ CU_ASSERT(spdk_blob_is_thin_provisioned(blob) == decouple_parent);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+ spdk_bs_free_io_channel(channel);
+}
+
+static void
+blob_inflate(void)
+{
+ _blob_inflate(false);
+ _blob_inflate(true);
+}
+
+static void
+blob_delete(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ spdk_blob_id blobid;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ /* Create a blob and then delete it. */
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid > 0);
+ blobid = g_blobid;
+
+ spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Try to open the blob */
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -ENOENT);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_resize(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ spdk_blob_id blobid;
+ uint64_t free_clusters;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+ free_clusters = spdk_bs_free_cluster_count(bs);
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ /* Confirm that resize fails if blob is marked read-only. */
+ blob->md_ro = true;
+ spdk_blob_resize(blob, 5, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == -EPERM);
+ blob->md_ro = false;
+
+ /* The blob started at 0 clusters. Resize it to be 5. */
+ spdk_blob_resize(blob, 5, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT((free_clusters - 5) == spdk_bs_free_cluster_count(bs));
+
+ /* Shrink the blob to 3 clusters. This will not actually release
+ * the old clusters until the blob is synced.
+ */
+ spdk_blob_resize(blob, 3, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ /* Verify there are still 5 clusters in use */
+ CU_ASSERT((free_clusters - 5) == spdk_bs_free_cluster_count(bs));
+
+ spdk_blob_sync_md(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ /* Now there are only 3 clusters in use */
+ CU_ASSERT((free_clusters - 3) == spdk_bs_free_cluster_count(bs));
+
+ /* Resize the blob to be 10 clusters. Growth takes effect immediately. */
+ spdk_blob_resize(blob, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT((free_clusters - 10) == spdk_bs_free_cluster_count(bs));
+
+ /* Try to resize the blob to size larger than blobstore. */
+ spdk_blob_resize(blob, bs->total_clusters + 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == -ENOSPC);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_read_only(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_bs_opts opts;
+ spdk_blob_id blobid;
+ int rc;
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ rc = spdk_blob_set_read_only(blob);
+ CU_ASSERT(rc == 0);
+
+ CU_ASSERT(blob->data_ro == false);
+ CU_ASSERT(blob->md_ro == false);
+
+ spdk_blob_sync_md(blob, bs_op_complete, NULL);
+
+ CU_ASSERT(blob->data_ro == true);
+ CU_ASSERT(blob->md_ro == true);
+ CU_ASSERT(blob->data_ro_flags & SPDK_BLOB_READ_ONLY);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(blob->data_ro == true);
+ CU_ASSERT(blob->md_ro == true);
+ CU_ASSERT(blob->data_ro_flags & SPDK_BLOB_READ_ONLY);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+
+ /* Load an existing blob store */
+ dev = init_dev();
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(blob->data_ro == true);
+ CU_ASSERT(blob->md_ro == true);
+ CU_ASSERT(blob->data_ro_flags & SPDK_BLOB_READ_ONLY);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+}
+
+static void
+channel_ops(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_io_channel *channel;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_bs_free_io_channel(channel);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_write(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_io_channel *channel;
+ spdk_blob_id blobid;
+ uint64_t pages_per_cluster;
+ uint8_t payload[10 * 4096];
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ pages_per_cluster = spdk_bs_get_cluster_size(bs) / spdk_bs_get_page_size(bs);
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ /* Write to a blob with 0 size */
+ spdk_blob_io_write(blob, channel, payload, 0, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ /* Resize the blob */
+ spdk_blob_resize(blob, 5, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Confirm that write fails if blob is marked read-only. */
+ blob->data_ro = true;
+ spdk_blob_io_write(blob, channel, payload, 0, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == -EPERM);
+ blob->data_ro = false;
+
+ /* Write to the blob */
+ spdk_blob_io_write(blob, channel, payload, 0, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Write starting beyond the end */
+ spdk_blob_io_write(blob, channel, payload, 5 * pages_per_cluster, 1, blob_op_complete,
+ NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ /* Write starting at a valid location but going off the end */
+ spdk_blob_io_write(blob, channel, payload, 4 * pages_per_cluster, pages_per_cluster + 1,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_read(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_io_channel *channel;
+ spdk_blob_id blobid;
+ uint64_t pages_per_cluster;
+ uint8_t payload[10 * 4096];
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ pages_per_cluster = spdk_bs_get_cluster_size(bs) / spdk_bs_get_page_size(bs);
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ /* Read from a blob with 0 size */
+ spdk_blob_io_read(blob, channel, payload, 0, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ /* Resize the blob */
+ spdk_blob_resize(blob, 5, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Confirm that read passes if blob is marked read-only. */
+ blob->data_ro = true;
+ spdk_blob_io_read(blob, channel, payload, 0, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob->data_ro = false;
+
+ /* Read from the blob */
+ spdk_blob_io_read(blob, channel, payload, 0, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Read starting beyond the end */
+ spdk_blob_io_read(blob, channel, payload, 5 * pages_per_cluster, 1, blob_op_complete,
+ NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ /* Read starting at a valid location but going off the end */
+ spdk_blob_io_read(blob, channel, payload, 4 * pages_per_cluster, pages_per_cluster + 1,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_rw_verify(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_io_channel *channel;
+ spdk_blob_id blobid;
+ uint8_t payload_read[10 * 4096];
+ uint8_t payload_write[10 * 4096];
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ spdk_blob_resize(blob, 32, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ memset(payload_write, 0xE5, sizeof(payload_write));
+ spdk_blob_io_write(blob, channel, payload_write, 4, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ memset(payload_read, 0x00, sizeof(payload_read));
+ spdk_blob_io_read(blob, channel, payload_read, 4, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_write, payload_read, 4 * 4096) == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_rw_verify_iov(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_io_channel *channel;
+ spdk_blob_id blobid;
+ uint8_t payload_read[10 * 4096];
+ uint8_t payload_write[10 * 4096];
+ struct iovec iov_read[3];
+ struct iovec iov_write[3];
+ void *buf;
+
+ dev = init_dev();
+ memset(g_dev_buffer, 0, DEV_BUFFER_SIZE);
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ spdk_blob_resize(blob, 2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /*
+ * Manually adjust the offset of the blob's second cluster. This allows
+ * us to make sure that the readv/write code correctly accounts for I/O
+ * that cross cluster boundaries. Start by asserting that the allocated
+ * clusters are where we expect before modifying the second cluster.
+ */
+ CU_ASSERT(blob->active.clusters[0] == 1 * 256);
+ CU_ASSERT(blob->active.clusters[1] == 2 * 256);
+ blob->active.clusters[1] = 3 * 256;
+
+ memset(payload_write, 0xE5, sizeof(payload_write));
+ iov_write[0].iov_base = payload_write;
+ iov_write[0].iov_len = 1 * 4096;
+ iov_write[1].iov_base = payload_write + 1 * 4096;
+ iov_write[1].iov_len = 5 * 4096;
+ iov_write[2].iov_base = payload_write + 6 * 4096;
+ iov_write[2].iov_len = 4 * 4096;
+ /*
+ * Choose a page offset just before the cluster boundary. The first 6 pages of payload
+ * will get written to the first cluster, the last 4 to the second cluster.
+ */
+ spdk_blob_io_writev(blob, channel, iov_write, 3, 250, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ memset(payload_read, 0xAA, sizeof(payload_read));
+ iov_read[0].iov_base = payload_read;
+ iov_read[0].iov_len = 3 * 4096;
+ iov_read[1].iov_base = payload_read + 3 * 4096;
+ iov_read[1].iov_len = 4 * 4096;
+ iov_read[2].iov_base = payload_read + 7 * 4096;
+ iov_read[2].iov_len = 3 * 4096;
+ spdk_blob_io_readv(blob, channel, iov_read, 3, 250, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0);
+
+ buf = calloc(1, 256 * 4096);
+ SPDK_CU_ASSERT_FATAL(buf != NULL);
+ /* Check that cluster 2 on "disk" was not modified. */
+ CU_ASSERT(memcmp(buf, &g_dev_buffer[512 * 4096], 256 * 4096) == 0);
+ free(buf);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static uint32_t
+bs_channel_get_req_count(struct spdk_io_channel *_channel)
+{
+ struct spdk_bs_channel *channel = spdk_io_channel_get_ctx(_channel);
+ struct spdk_bs_request_set *set;
+ uint32_t count = 0;
+
+ TAILQ_FOREACH(set, &channel->reqs, link) {
+ count++;
+ }
+
+ return count;
+}
+
+static void
+blob_rw_verify_iov_nomem(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_io_channel *channel;
+ spdk_blob_id blobid;
+ uint8_t payload_write[10 * 4096];
+ struct iovec iov_write[3];
+ uint32_t req_count;
+
+ dev = init_dev();
+ memset(g_dev_buffer, 0, DEV_BUFFER_SIZE);
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ spdk_blob_resize(blob, 2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /*
+ * Choose a page offset just before the cluster boundary. The first 6 pages of payload
+ * will get written to the first cluster, the last 4 to the second cluster.
+ */
+ iov_write[0].iov_base = payload_write;
+ iov_write[0].iov_len = 1 * 4096;
+ iov_write[1].iov_base = payload_write + 1 * 4096;
+ iov_write[1].iov_len = 5 * 4096;
+ iov_write[2].iov_base = payload_write + 6 * 4096;
+ iov_write[2].iov_len = 4 * 4096;
+ MOCK_SET(calloc, NULL);
+ req_count = bs_channel_get_req_count(channel);
+ spdk_blob_io_writev(blob, channel, iov_write, 3, 250, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno = -ENOMEM);
+ CU_ASSERT(req_count == bs_channel_get_req_count(channel));
+ MOCK_CLEAR(calloc);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_rw_iov_read_only(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_io_channel *channel;
+ spdk_blob_id blobid;
+ uint8_t payload_read[4096];
+ uint8_t payload_write[4096];
+ struct iovec iov_read;
+ struct iovec iov_write;
+
+ dev = init_dev();
+ memset(g_dev_buffer, 0, DEV_BUFFER_SIZE);
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ spdk_blob_resize(blob, 2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Verify that writev failed if read_only flag is set. */
+ blob->data_ro = true;
+ iov_write.iov_base = payload_write;
+ iov_write.iov_len = sizeof(payload_write);
+ spdk_blob_io_writev(blob, channel, &iov_write, 1, 0, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == -EPERM);
+
+ /* Verify that reads pass if data_ro flag is set. */
+ iov_read.iov_base = payload_read;
+ iov_read.iov_len = sizeof(payload_read);
+ spdk_blob_io_readv(blob, channel, &iov_read, 1, 0, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+_blob_io_read_no_split(struct spdk_blob *blob, struct spdk_io_channel *channel,
+ uint8_t *payload, uint64_t offset, uint64_t length,
+ spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ uint64_t i;
+ uint8_t *buf;
+ uint64_t page_size = spdk_bs_get_page_size(blob->bs);
+
+ /* To be sure that operation is NOT splitted, read one page at the time */
+ buf = payload;
+ for (i = 0; i < length; i++) {
+ spdk_blob_io_read(blob, channel, buf, i + offset, 1, blob_op_complete, NULL);
+ if (g_bserrno != 0) {
+ /* Pass the error code up */
+ break;
+ }
+ buf += page_size;
+ }
+
+ cb_fn(cb_arg, g_bserrno);
+}
+
+static void
+_blob_io_write_no_split(struct spdk_blob *blob, struct spdk_io_channel *channel,
+ uint8_t *payload, uint64_t offset, uint64_t length,
+ spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ uint64_t i;
+ uint8_t *buf;
+ uint64_t page_size = spdk_bs_get_page_size(blob->bs);
+
+ /* To be sure that operation is NOT splitted, write one page at the time */
+ buf = payload;
+ for (i = 0; i < length; i++) {
+ spdk_blob_io_write(blob, channel, buf, i + offset, 1, blob_op_complete, NULL);
+ if (g_bserrno != 0) {
+ /* Pass the error code up */
+ break;
+ }
+ buf += page_size;
+ }
+
+ cb_fn(cb_arg, g_bserrno);
+}
+
+static void
+blob_operation_split_rw(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_io_channel *channel;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid;
+ uint64_t cluster_size;
+
+ uint64_t payload_size;
+ uint8_t *payload_read;
+ uint8_t *payload_write;
+ uint8_t *payload_pattern;
+
+ uint64_t page_size;
+ uint64_t pages_per_cluster;
+ uint64_t pages_per_payload;
+
+ uint64_t i;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ cluster_size = spdk_bs_get_cluster_size(bs);
+ page_size = spdk_bs_get_page_size(bs);
+ pages_per_cluster = cluster_size / page_size;
+ pages_per_payload = pages_per_cluster * 5;
+ payload_size = cluster_size * 5;
+
+ payload_read = malloc(payload_size);
+ SPDK_CU_ASSERT_FATAL(payload_read != NULL);
+
+ payload_write = malloc(payload_size);
+ SPDK_CU_ASSERT_FATAL(payload_write != NULL);
+
+ payload_pattern = malloc(payload_size);
+ SPDK_CU_ASSERT_FATAL(payload_pattern != NULL);
+
+ /* Prepare random pattern to write */
+ memset(payload_pattern, 0xFF, payload_size);
+ for (i = 0; i < pages_per_payload; i++) {
+ *((uint64_t *)(payload_pattern + page_size * i)) = (i + 1);
+ }
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ SPDK_CU_ASSERT_FATAL(channel != NULL);
+
+ /* Create blob */
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = false;
+ opts.num_clusters = 5;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5);
+
+ /* Initial read should return zeroed payload */
+ memset(payload_read, 0xFF, payload_size);
+ spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(spdk_mem_all_zero(payload_read, payload_size));
+
+ /* Fill whole blob except last page */
+ spdk_blob_io_write(blob, channel, payload_pattern, 0, pages_per_payload - 1,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Write last page with a pattern */
+ spdk_blob_io_write(blob, channel, payload_pattern, pages_per_payload - 1, 1,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Read whole blob and check consistency */
+ memset(payload_read, 0xFF, payload_size);
+ spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size - page_size) == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read + payload_size - page_size, page_size) == 0);
+
+ /* Fill whole blob except first page */
+ spdk_blob_io_write(blob, channel, payload_pattern, 1, pages_per_payload - 1,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Write first page with a pattern */
+ spdk_blob_io_write(blob, channel, payload_pattern, 0, 1,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Read whole blob and check consistency */
+ memset(payload_read, 0xFF, payload_size);
+ spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read + page_size, payload_size - page_size) == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read, page_size) == 0);
+
+
+ /* Fill whole blob with a pattern (5 clusters) */
+
+ /* 1. Read test. */
+ _blob_io_write_no_split(blob, channel, payload_pattern, 0, pages_per_payload,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ memset(payload_read, 0xFF, payload_size);
+ spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size) == 0);
+
+ /* 2. Write test. */
+ spdk_blob_io_write(blob, channel, payload_pattern, 0, pages_per_payload,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ memset(payload_read, 0xFF, payload_size);
+ _blob_io_read_no_split(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size) == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+
+ free(payload_read);
+ free(payload_write);
+ free(payload_pattern);
+}
+
+static void
+blob_operation_split_rw_iov(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_io_channel *channel;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid;
+ uint64_t cluster_size;
+
+ uint64_t payload_size;
+ uint8_t *payload_read;
+ uint8_t *payload_write;
+ uint8_t *payload_pattern;
+
+ uint64_t page_size;
+ uint64_t pages_per_cluster;
+ uint64_t pages_per_payload;
+
+ struct iovec iov_read[2];
+ struct iovec iov_write[2];
+
+ uint64_t i, j;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ cluster_size = spdk_bs_get_cluster_size(bs);
+ page_size = spdk_bs_get_page_size(bs);
+ pages_per_cluster = cluster_size / page_size;
+ pages_per_payload = pages_per_cluster * 5;
+ payload_size = cluster_size * 5;
+
+ payload_read = malloc(payload_size);
+ SPDK_CU_ASSERT_FATAL(payload_read != NULL);
+
+ payload_write = malloc(payload_size);
+ SPDK_CU_ASSERT_FATAL(payload_write != NULL);
+
+ payload_pattern = malloc(payload_size);
+ SPDK_CU_ASSERT_FATAL(payload_pattern != NULL);
+
+ /* Prepare random pattern to write */
+ for (i = 0; i < pages_per_payload; i++) {
+ for (j = 0; j < page_size / sizeof(uint64_t); j++) {
+ uint64_t *tmp;
+
+ tmp = (uint64_t *)payload_pattern;
+ tmp += ((page_size * i) / sizeof(uint64_t)) + j;
+ *tmp = i + 1;
+ }
+ }
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ SPDK_CU_ASSERT_FATAL(channel != NULL);
+
+ /* Create blob */
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = false;
+ opts.num_clusters = 5;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5);
+
+ /* Initial read should return zeroes payload */
+ memset(payload_read, 0xFF, payload_size);
+ iov_read[0].iov_base = payload_read;
+ iov_read[0].iov_len = cluster_size * 3;
+ iov_read[1].iov_base = payload_read + cluster_size * 3;
+ iov_read[1].iov_len = cluster_size * 2;
+ spdk_blob_io_readv(blob, channel, iov_read, 2, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(spdk_mem_all_zero(payload_read, payload_size));
+
+ /* First of iovs fills whole blob except last page and second of iovs writes last page
+ * with a pattern. */
+ iov_write[0].iov_base = payload_pattern;
+ iov_write[0].iov_len = payload_size - page_size;
+ iov_write[1].iov_base = payload_pattern;
+ iov_write[1].iov_len = page_size;
+ spdk_blob_io_writev(blob, channel, iov_write, 2, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Read whole blob and check consistency */
+ memset(payload_read, 0xFF, payload_size);
+ iov_read[0].iov_base = payload_read;
+ iov_read[0].iov_len = cluster_size * 2;
+ iov_read[1].iov_base = payload_read + cluster_size * 2;
+ iov_read[1].iov_len = cluster_size * 3;
+ spdk_blob_io_readv(blob, channel, iov_read, 2, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size - page_size) == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read + payload_size - page_size, page_size) == 0);
+
+ /* First of iovs fills only first page and second of iovs writes whole blob except
+ * first page with a pattern. */
+ iov_write[0].iov_base = payload_pattern;
+ iov_write[0].iov_len = page_size;
+ iov_write[1].iov_base = payload_pattern;
+ iov_write[1].iov_len = payload_size - page_size;
+ spdk_blob_io_writev(blob, channel, iov_write, 2, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Read whole blob and check consistency */
+ memset(payload_read, 0xFF, payload_size);
+ iov_read[0].iov_base = payload_read;
+ iov_read[0].iov_len = cluster_size * 4;
+ iov_read[1].iov_base = payload_read + cluster_size * 4;
+ iov_read[1].iov_len = cluster_size;
+ spdk_blob_io_readv(blob, channel, iov_read, 2, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read + page_size, payload_size - page_size) == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read, page_size) == 0);
+
+
+ /* Fill whole blob with a pattern (5 clusters) */
+
+ /* 1. Read test. */
+ _blob_io_write_no_split(blob, channel, payload_pattern, 0, pages_per_payload,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ memset(payload_read, 0xFF, payload_size);
+ iov_read[0].iov_base = payload_read;
+ iov_read[0].iov_len = cluster_size;
+ iov_read[1].iov_base = payload_read + cluster_size;
+ iov_read[1].iov_len = cluster_size * 4;
+ spdk_blob_io_readv(blob, channel, iov_read, 2, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size) == 0);
+
+ /* 2. Write test. */
+ iov_write[0].iov_base = payload_read;
+ iov_write[0].iov_len = cluster_size * 2;
+ iov_write[1].iov_base = payload_read + cluster_size * 2;
+ iov_write[1].iov_len = cluster_size * 3;
+ spdk_blob_io_writev(blob, channel, iov_write, 2, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ memset(payload_read, 0xFF, payload_size);
+ _blob_io_read_no_split(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_pattern, payload_read, payload_size) == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+
+ free(payload_read);
+ free(payload_write);
+ free(payload_pattern);
+}
+
+static void
+blob_unmap(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_io_channel *channel;
+ spdk_blob_id blobid;
+ struct spdk_blob_opts opts;
+ uint8_t payload[4096];
+ int i;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_blob_opts_init(&opts);
+ opts.num_clusters = 10;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ spdk_blob_resize(blob, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ memset(payload, 0, sizeof(payload));
+ payload[0] = 0xFF;
+
+ /*
+ * Set first byte of every cluster to 0xFF.
+ * First cluster on device is reserved so let's start from cluster number 1
+ */
+ for (i = 1; i < 11; i++) {
+ g_dev_buffer[i * SPDK_BLOB_OPTS_CLUSTER_SZ] = 0xFF;
+ }
+
+ /* Confirm writes */
+ for (i = 0; i < 10; i++) {
+ payload[0] = 0;
+ spdk_blob_io_read(blob, channel, &payload, i * SPDK_BLOB_OPTS_CLUSTER_SZ / 4096, 1,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(payload[0] == 0xFF);
+ }
+
+ /* Mark some clusters as unallocated */
+ blob->active.clusters[1] = 0;
+ blob->active.clusters[2] = 0;
+ blob->active.clusters[3] = 0;
+ blob->active.clusters[6] = 0;
+ blob->active.clusters[8] = 0;
+
+ /* Unmap clusters by resizing to 0 */
+ spdk_blob_resize(blob, 0, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_sync_md(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Confirm that only 'allocated' clusters were unmapped */
+ for (i = 1; i < 11; i++) {
+ switch (i) {
+ case 2:
+ case 3:
+ case 4:
+ case 7:
+ case 9:
+ CU_ASSERT(g_dev_buffer[i * SPDK_BLOB_OPTS_CLUSTER_SZ] == 0xFF);
+ break;
+ default:
+ CU_ASSERT(g_dev_buffer[i * SPDK_BLOB_OPTS_CLUSTER_SZ] == 0);
+ break;
+ }
+ }
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+
+static void
+blob_iter(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ spdk_blob_id blobid;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ spdk_bs_iter_first(bs, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_blob == NULL);
+ CU_ASSERT(g_bserrno == -ENOENT);
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_iter_first(bs, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_blob != NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob = g_blob;
+ CU_ASSERT(spdk_blob_get_id(blob) == blobid);
+
+ spdk_bs_iter_next(bs, blob, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_blob == NULL);
+ CU_ASSERT(g_bserrno == -ENOENT);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_xattr(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ spdk_blob_id blobid;
+ uint64_t length;
+ int rc;
+ const char *name1, *name2;
+ const void *value;
+ size_t value_len;
+ struct spdk_xattr_names *names;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ /* Test that set_xattr fails if md_ro flag is set. */
+ blob->md_ro = true;
+ rc = spdk_blob_set_xattr(blob, "name", "log.txt", strlen("log.txt") + 1);
+ CU_ASSERT(rc == -EPERM);
+
+ blob->md_ro = false;
+ rc = spdk_blob_set_xattr(blob, "name", "log.txt", strlen("log.txt") + 1);
+ CU_ASSERT(rc == 0);
+
+ length = 2345;
+ rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length));
+ CU_ASSERT(rc == 0);
+
+ /* Overwrite "length" xattr. */
+ length = 3456;
+ rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length));
+ CU_ASSERT(rc == 0);
+
+ /* get_xattr should still work even if md_ro flag is set. */
+ value = NULL;
+ blob->md_ro = true;
+ rc = spdk_blob_get_xattr_value(blob, "length", &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(*(uint64_t *)value == length);
+ CU_ASSERT(value_len == 8);
+ blob->md_ro = false;
+
+ rc = spdk_blob_get_xattr_value(blob, "foobar", &value, &value_len);
+ CU_ASSERT(rc == -ENOENT);
+
+ names = NULL;
+ rc = spdk_blob_get_xattr_names(blob, &names);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(names != NULL);
+ CU_ASSERT(spdk_xattr_names_get_count(names) == 2);
+ name1 = spdk_xattr_names_get_name(names, 0);
+ SPDK_CU_ASSERT_FATAL(name1 != NULL);
+ CU_ASSERT(!strcmp(name1, "name") || !strcmp(name1, "length"));
+ name2 = spdk_xattr_names_get_name(names, 1);
+ SPDK_CU_ASSERT_FATAL(name2 != NULL);
+ CU_ASSERT(!strcmp(name2, "name") || !strcmp(name2, "length"));
+ CU_ASSERT(strcmp(name1, name2));
+ spdk_xattr_names_free(names);
+
+ /* Confirm that remove_xattr fails if md_ro is set to true. */
+ blob->md_ro = true;
+ rc = spdk_blob_remove_xattr(blob, "name");
+ CU_ASSERT(rc == -EPERM);
+
+ blob->md_ro = false;
+ rc = spdk_blob_remove_xattr(blob, "name");
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_blob_remove_xattr(blob, "foobar");
+ CU_ASSERT(rc == -ENOENT);
+
+ /* Set internal xattr */
+ length = 7898;
+ rc = _spdk_blob_set_xattr(blob, "internal", &length, sizeof(length), true);
+ CU_ASSERT(rc == 0);
+ rc = _spdk_blob_get_xattr_value(blob, "internal", &value, &value_len, true);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(*(uint64_t *)value == length);
+ /* try to get public xattr with same name */
+ rc = spdk_blob_get_xattr_value(blob, "internal", &value, &value_len);
+ CU_ASSERT(rc != 0);
+ rc = _spdk_blob_get_xattr_value(blob, "internal", &value, &value_len, false);
+ CU_ASSERT(rc != 0);
+ /* Check if SPDK_BLOB_INTERNAL_XATTR is set */
+ CU_ASSERT((blob->invalid_flags & SPDK_BLOB_INTERNAL_XATTR) ==
+ SPDK_BLOB_INTERNAL_XATTR)
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+
+ /* Check if xattrs are persisted */
+ dev = init_dev();
+
+ spdk_bs_load(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ bs = g_bs;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ rc = _spdk_blob_get_xattr_value(blob, "internal", &value, &value_len, true);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(*(uint64_t *)value == length);
+
+ /* try to get internal xattr trough public call */
+ rc = spdk_blob_get_xattr_value(blob, "internal", &value, &value_len);
+ CU_ASSERT(rc != 0);
+
+ rc = _spdk_blob_remove_xattr(blob, "internal", true);
+ CU_ASSERT(rc == 0);
+
+ CU_ASSERT((blob->invalid_flags & SPDK_BLOB_INTERNAL_XATTR) == 0);
+
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+bs_load(void)
+{
+ struct spdk_bs_dev *dev;
+ spdk_blob_id blobid;
+ struct spdk_blob *blob;
+ struct spdk_bs_super_block *super_block;
+ uint64_t length;
+ int rc;
+ const void *value;
+ size_t value_len;
+ struct spdk_bs_opts opts;
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ /* Try to open a blobid that does not exist */
+ spdk_bs_open_blob(g_bs, 0, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -ENOENT);
+ CU_ASSERT(g_blob == NULL);
+
+ /* Create a blob */
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ /* Try again to open valid blob but without the upper bit set */
+ spdk_bs_open_blob(g_bs, blobid & 0xFFFFFFFF, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -ENOENT);
+ CU_ASSERT(g_blob == NULL);
+
+ /* Set some xattrs */
+ rc = spdk_blob_set_xattr(blob, "name", "log.txt", strlen("log.txt") + 1);
+ CU_ASSERT(rc == 0);
+
+ length = 2345;
+ rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length));
+ CU_ASSERT(rc == 0);
+
+ /* Resize the blob */
+ spdk_blob_resize(blob, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob = NULL;
+ g_blob = NULL;
+ g_blobid = SPDK_BLOBID_INVALID;
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+
+ super_block = (struct spdk_bs_super_block *)g_dev_buffer;
+ CU_ASSERT(super_block->clean == 1);
+
+ /* Load should fail for device with an unsupported blocklen */
+ dev = init_dev();
+ dev->blocklen = SPDK_BS_PAGE_SIZE * 2;
+ spdk_bs_load(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ /* Load should when max_md_ops is set to zero */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ opts.max_md_ops = 0;
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ /* Load should when max_channel_ops is set to zero */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ opts.max_channel_ops = 0;
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ /* Load an existing blob store */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ super_block = (struct spdk_bs_super_block *)g_dev_buffer;
+ CU_ASSERT(super_block->clean == 1);
+ CU_ASSERT(super_block->size == dev->blockcnt * dev->blocklen);
+
+ spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ /* Verify that blobstore is marked dirty after first metadata sync */
+ spdk_blob_sync_md(blob, blob_op_complete, NULL);
+ CU_ASSERT(super_block->clean == 1);
+
+ /* Get the xattrs */
+ value = NULL;
+ rc = spdk_blob_get_xattr_value(blob, "length", &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(*(uint64_t *)value == length);
+ CU_ASSERT(value_len == 8);
+
+ rc = spdk_blob_get_xattr_value(blob, "foobar", &value, &value_len);
+ CU_ASSERT(rc == -ENOENT);
+
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob = NULL;
+ g_blob = NULL;
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+ /* Load should fail: bdev size < saved size */
+ dev = init_dev();
+ dev->blockcnt /= 2;
+
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+
+ CU_ASSERT(g_bserrno == -EILSEQ);
+
+ /* Load should succeed: bdev size > saved size */
+ dev = init_dev();
+ dev->blockcnt *= 4;
+
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+
+ CU_ASSERT(g_bserrno == 0);
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+
+
+ /* Test compatibility mode */
+
+ dev = init_dev();
+ super_block->size = 0;
+ super_block->crc = _spdk_blob_md_page_calc_crc(super_block);
+
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ /* Create a blob */
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+
+ /* Blobstore should update number of blocks in super_block */
+ CU_ASSERT(super_block->size == dev->blockcnt * dev->blocklen);
+ CU_ASSERT(super_block->clean == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(super_block->clean == 1);
+ g_bs = NULL;
+
+}
+
+static void
+bs_load_custom_cluster_size(void)
+{
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_super_block *super_block;
+ struct spdk_bs_opts opts;
+ uint32_t custom_cluster_size = 4194304; /* 4MiB */
+ uint32_t cluster_sz;
+ uint64_t total_clusters;
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ opts.cluster_sz = custom_cluster_size;
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ cluster_sz = g_bs->cluster_sz;
+ total_clusters = g_bs->total_clusters;
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+
+ super_block = (struct spdk_bs_super_block *)g_dev_buffer;
+ CU_ASSERT(super_block->clean == 1);
+
+ /* Load an existing blob store */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ /* Compare cluster size and number to one after initialization */
+ CU_ASSERT(cluster_sz == g_bs->cluster_sz);
+ CU_ASSERT(total_clusters == g_bs->total_clusters);
+
+ super_block = (struct spdk_bs_super_block *)g_dev_buffer;
+ CU_ASSERT(super_block->clean == 1);
+ CU_ASSERT(super_block->size == dev->blockcnt * dev->blocklen);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(super_block->clean == 1);
+ g_bs = NULL;
+}
+
+static void
+bs_type(void)
+{
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_opts opts;
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+
+ /* Load non existing blobstore type */
+ dev = init_dev();
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "NONEXISTING");
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+
+ /* Load with empty blobstore type */
+ dev = init_dev();
+ memset(opts.bstype.bstype, 0, sizeof(opts.bstype.bstype));
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+ /* Initialize a new blob store with empty bstype */
+ dev = init_dev();
+ memset(opts.bstype.bstype, 0, sizeof(opts.bstype.bstype));
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+ /* Load non existing blobstore type */
+ dev = init_dev();
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "NONEXISTING");
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+
+ /* Load with empty blobstore type */
+ dev = init_dev();
+ memset(opts.bstype.bstype, 0, sizeof(opts.bstype.bstype));
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+bs_super_block(void)
+{
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_super_block *super_block;
+ struct spdk_bs_opts opts;
+ struct spdk_bs_super_block_ver1 super_block_v1;
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+
+ /* Load an existing blob store with version newer than supported */
+ super_block = (struct spdk_bs_super_block *)g_dev_buffer;
+ super_block->version++;
+
+ dev = init_dev();
+ memset(opts.bstype.bstype, 0, sizeof(opts.bstype.bstype));
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+
+ /* Create a new blob store with super block version 1 */
+ dev = init_dev();
+ super_block_v1.version = 1;
+ memcpy(super_block_v1.signature, "SPDKBLOB", sizeof(super_block_v1.signature));
+ super_block_v1.length = 0x1000;
+ super_block_v1.clean = 1;
+ super_block_v1.super_blob = 0xFFFFFFFFFFFFFFFF;
+ super_block_v1.cluster_size = 0x100000;
+ super_block_v1.used_page_mask_start = 0x01;
+ super_block_v1.used_page_mask_len = 0x01;
+ super_block_v1.used_cluster_mask_start = 0x02;
+ super_block_v1.used_cluster_mask_len = 0x01;
+ super_block_v1.md_start = 0x03;
+ super_block_v1.md_len = 0x40;
+ memset(super_block_v1.reserved, 0, 4036);
+ super_block_v1.crc = _spdk_blob_md_page_calc_crc(&super_block_v1);
+ memcpy(g_dev_buffer, &super_block_v1, sizeof(struct spdk_bs_super_block_ver1));
+
+ memset(opts.bstype.bstype, 0, sizeof(opts.bstype.bstype));
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+/*
+ * Create a blobstore and then unload it.
+ */
+static void
+bs_unload(void)
+{
+ struct spdk_bs_dev *dev;
+ struct spdk_blob_store *bs;
+ spdk_blob_id blobid;
+ struct spdk_blob *blob;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ /* Create a blob and open it. */
+ g_bserrno = -1;
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid > 0);
+ blobid = g_blobid;
+
+ g_bserrno = -1;
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ /* Try to unload blobstore, should fail with open blob */
+ g_bserrno = -1;
+ spdk_bs_unload(bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == -EBUSY);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ /* Close the blob, then successfully unload blobstore */
+ g_bserrno = -1;
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ g_bserrno = -1;
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+/*
+ * Create a blobstore with a cluster size different than the default, and ensure it is
+ * persisted.
+ */
+static void
+bs_cluster_sz(void)
+{
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_opts opts;
+ uint32_t cluster_sz;
+
+ /* Set cluster size to zero */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ opts.cluster_sz = 0;
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+ SPDK_CU_ASSERT_FATAL(g_bs == NULL);
+
+ /*
+ * Set cluster size to blobstore page size,
+ * to work it is required to be at least twice the blobstore page size.
+ */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ opts.cluster_sz = SPDK_BS_PAGE_SIZE;
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -ENOMEM);
+ SPDK_CU_ASSERT_FATAL(g_bs == NULL);
+
+ /*
+ * Set cluster size to lower than page size,
+ * to work it is required to be at least twice the blobstore page size.
+ */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ opts.cluster_sz = SPDK_BS_PAGE_SIZE - 1;
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+ SPDK_CU_ASSERT_FATAL(g_bs == NULL);
+
+ /* Set cluster size to twice the default */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ opts.cluster_sz *= 2;
+ cluster_sz = opts.cluster_sz;
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ CU_ASSERT(spdk_bs_get_cluster_size(g_bs) == cluster_sz);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+
+ dev = init_dev();
+ /* Load an existing blob store */
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ CU_ASSERT(spdk_bs_get_cluster_size(g_bs) == cluster_sz);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+/*
+ * Create a blobstore, reload it and ensure total usable cluster count
+ * stays the same.
+ */
+static void
+bs_usable_clusters(void)
+{
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_opts opts;
+ uint32_t clusters;
+ int i;
+
+ /* Init blobstore */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ clusters = spdk_bs_total_data_cluster_count(g_bs);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+ dev = init_dev();
+ /* Load an existing blob store */
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ CU_ASSERT(spdk_bs_total_data_cluster_count(g_bs) == clusters);
+
+ /* Create and resize blobs to make sure that useable cluster count won't change */
+ for (i = 0; i < 4; i++) {
+ g_bserrno = -1;
+ g_blobid = SPDK_BLOBID_INVALID;
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+
+ g_bserrno = -1;
+ g_blob = NULL;
+ spdk_bs_open_blob(g_bs, g_blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+
+ spdk_blob_resize(g_blob, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ g_bserrno = -1;
+ spdk_blob_close(g_blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(spdk_bs_total_data_cluster_count(g_bs) == clusters);
+ }
+
+ /* Reload the blob store to make sure that nothing changed */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+ dev = init_dev();
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ CU_ASSERT(spdk_bs_total_data_cluster_count(g_bs) == clusters);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+/*
+ * Test resizing of the metadata blob. This requires creating enough blobs
+ * so that one cluster is not enough to fit the metadata for those blobs.
+ * To induce this condition to happen more quickly, we reduce the cluster
+ * size to 16KB, which means only 4 4KB blob metadata pages can fit.
+ */
+static void
+bs_resize_md(void)
+{
+ const int CLUSTER_PAGE_COUNT = 4;
+ const int NUM_BLOBS = CLUSTER_PAGE_COUNT * 4;
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_opts opts;
+ uint32_t cluster_sz;
+ spdk_blob_id blobids[NUM_BLOBS];
+ int i;
+
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ opts.cluster_sz = CLUSTER_PAGE_COUNT * 4096;
+ cluster_sz = opts.cluster_sz;
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ CU_ASSERT(spdk_bs_get_cluster_size(g_bs) == cluster_sz);
+
+ for (i = 0; i < NUM_BLOBS; i++) {
+ g_bserrno = -1;
+ g_blobid = SPDK_BLOBID_INVALID;
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobids[i] = g_blobid;
+ }
+
+ /* Unload the blob store */
+ g_bserrno = -1;
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Load an existing blob store */
+ g_bserrno = -1;
+ g_bs = NULL;
+ dev = init_dev();
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ CU_ASSERT(spdk_bs_get_cluster_size(g_bs) == cluster_sz);
+
+ for (i = 0; i < NUM_BLOBS; i++) {
+ g_bserrno = -1;
+ g_blob = NULL;
+ spdk_bs_open_blob(g_bs, blobids[i], blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ g_bserrno = -1;
+ spdk_blob_close(g_blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ }
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+bs_destroy(void)
+{
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_opts opts;
+
+ /* Initialize a new blob store */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ /* Destroy the blob store */
+ g_bserrno = -1;
+ spdk_bs_destroy(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Loading an non-existent blob store should fail. */
+ g_bs = NULL;
+ dev = init_dev();
+
+ g_bserrno = 0;
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+}
+
+/* Try to hit all of the corner cases associated with serializing
+ * a blob to disk
+ */
+static void
+blob_serialize(void)
+{
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_opts opts;
+ struct spdk_blob_store *bs;
+ spdk_blob_id blobid[2];
+ struct spdk_blob *blob[2];
+ uint64_t i;
+ char *value;
+ int rc;
+
+ dev = init_dev();
+
+ /* Initialize a new blobstore with very small clusters */
+ spdk_bs_opts_init(&opts);
+ opts.cluster_sz = dev->blocklen * 8;
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ /* Create and open two blobs */
+ for (i = 0; i < 2; i++) {
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid[i] = g_blobid;
+
+ /* Open a blob */
+ spdk_bs_open_blob(bs, blobid[i], blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob[i] = g_blob;
+
+ /* Set a fairly large xattr on both blobs to eat up
+ * metadata space
+ */
+ value = calloc(dev->blocklen - 64, sizeof(char));
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ memset(value, i, dev->blocklen / 2);
+ rc = spdk_blob_set_xattr(blob[i], "name", value, dev->blocklen - 64);
+ CU_ASSERT(rc == 0);
+ free(value);
+ }
+
+ /* Resize the blobs, alternating 1 cluster at a time.
+ * This thwarts run length encoding and will cause spill
+ * over of the extents.
+ */
+ for (i = 0; i < 6; i++) {
+ spdk_blob_resize(blob[i % 2], (i / 2) + 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ }
+
+ for (i = 0; i < 2; i++) {
+ spdk_blob_sync_md(blob[i], blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ }
+
+ /* Close the blobs */
+ for (i = 0; i < 2; i++) {
+ spdk_blob_close(blob[i], blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ }
+
+ /* Unload the blobstore */
+ spdk_bs_unload(bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+ bs = NULL;
+
+ dev = init_dev();
+ /* Load an existing blob store */
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ for (i = 0; i < 2; i++) {
+ blob[i] = NULL;
+
+ spdk_bs_open_blob(bs, blobid[i], blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob[i] = g_blob;
+
+ CU_ASSERT(spdk_blob_get_num_clusters(blob[i]) == 3);
+
+ spdk_blob_close(blob[i], blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ }
+
+ spdk_bs_unload(bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_crc(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ spdk_blob_id blobid;
+ uint32_t page_num;
+ int index;
+ struct spdk_blob_md_page *page;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ spdk_bs_create_blob(bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ page_num = _spdk_bs_blobid_to_page(blobid);
+ index = DEV_BUFFER_BLOCKLEN * (bs->md_start + page_num);
+ page = (struct spdk_blob_md_page *)&g_dev_buffer[index];
+ page->crc = 0;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+ CU_ASSERT(g_blob == NULL);
+ g_bserrno = 0;
+
+ spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+super_block_crc(void)
+{
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_super_block *super_block;
+ struct spdk_bs_opts opts;
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+ super_block = (struct spdk_bs_super_block *)g_dev_buffer;
+ super_block->crc = 0;
+ dev = init_dev();
+
+ /* Load an existing blob store */
+ g_bserrno = 0;
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == -EILSEQ);
+}
+
+/* For blob dirty shutdown test case we do the following sub-test cases:
+ * 1 Initialize new blob store and create 1 super blob with some xattrs, then we
+ * dirty shutdown and reload the blob store and verify the xattrs.
+ * 2 Resize the blob from 10 clusters to 20 clusters and then dirty shutdown,
+ * reload the blob store and verify the clusters number.
+ * 3 Create the second blob and then dirty shutdown, reload the blob store
+ * and verify the second blob.
+ * 4 Delete the second blob and then dirty shutdown, reload the blob store
+ * and verify the second blob is invalid.
+ * 5 Create the second blob again and also create the third blob, modify the
+ * md of second blob which makes the md invalid, and then dirty shutdown,
+ * reload the blob store verify the second blob, it should invalid and also
+ * verify the third blob, it should correct.
+ */
+static void
+blob_dirty_shutdown(void)
+{
+ int rc;
+ int index;
+ struct spdk_bs_dev *dev;
+ spdk_blob_id blobid1, blobid2, blobid3;
+ struct spdk_blob *blob;
+ uint64_t length;
+ uint64_t free_clusters;
+ const void *value;
+ size_t value_len;
+ uint32_t page_num;
+ struct spdk_blob_md_page *page;
+ struct spdk_bs_opts opts;
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ /* Create first blob */
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid1 = g_blobid;
+
+ spdk_bs_open_blob(g_bs, blobid1, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ /* Set some xattrs */
+ rc = spdk_blob_set_xattr(blob, "name", "log.txt", strlen("log.txt") + 1);
+ CU_ASSERT(rc == 0);
+
+ length = 2345;
+ rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length));
+ CU_ASSERT(rc == 0);
+
+ /* Resize the blob */
+ spdk_blob_resize(blob, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Set the blob as the super blob */
+ spdk_bs_set_super(g_bs, blobid1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ free_clusters = spdk_bs_free_cluster_count(g_bs);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ blob = NULL;
+ g_blob = NULL;
+ g_blobid = SPDK_BLOBID_INVALID;
+
+ /* Dirty shutdown */
+ _spdk_bs_free(g_bs);
+
+ /* reload blobstore */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Get the super blob */
+ spdk_bs_get_super(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(blobid1 == g_blobid);
+
+ spdk_bs_open_blob(g_bs, blobid1, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(g_bs));
+
+ /* Get the xattrs */
+ value = NULL;
+ rc = spdk_blob_get_xattr_value(blob, "length", &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(*(uint64_t *)value == length);
+ CU_ASSERT(value_len == 8);
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10);
+
+ /* Resize the blob */
+ spdk_blob_resize(blob, 20, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ free_clusters = spdk_bs_free_cluster_count(g_bs);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob = NULL;
+ g_blob = NULL;
+ g_blobid = SPDK_BLOBID_INVALID;
+
+ /* Dirty shutdown */
+ _spdk_bs_free(g_bs);
+
+ /* reload the blobstore */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ /* Load an existing blob store */
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ spdk_bs_open_blob(g_bs, blobid1, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 20);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(g_bs));
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob = NULL;
+ g_blob = NULL;
+ g_blobid = SPDK_BLOBID_INVALID;
+
+ /* Create second blob */
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid2 = g_blobid;
+
+ spdk_bs_open_blob(g_bs, blobid2, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ /* Set some xattrs */
+ rc = spdk_blob_set_xattr(blob, "name", "log1.txt", strlen("log1.txt") + 1);
+ CU_ASSERT(rc == 0);
+
+ length = 5432;
+ rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length));
+ CU_ASSERT(rc == 0);
+
+ /* Resize the blob */
+ spdk_blob_resize(blob, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ free_clusters = spdk_bs_free_cluster_count(g_bs);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ blob = NULL;
+ g_blob = NULL;
+ g_blobid = SPDK_BLOBID_INVALID;
+
+ /* Dirty shutdown */
+ _spdk_bs_free(g_bs);
+
+ /* reload the blobstore */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_open_blob(g_bs, blobid2, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ /* Get the xattrs */
+ value = NULL;
+ rc = spdk_blob_get_xattr_value(blob, "length", &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(*(uint64_t *)value == length);
+ CU_ASSERT(value_len == 8);
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(g_bs));
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ spdk_bs_delete_blob(g_bs, blobid2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ free_clusters = spdk_bs_free_cluster_count(g_bs);
+
+ /* Dirty shutdown */
+ _spdk_bs_free(g_bs);
+ /* reload the blobstore */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_open_blob(g_bs, blobid2, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+ CU_ASSERT(g_blob == NULL);
+
+ spdk_bs_open_blob(g_bs, blobid1, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(g_bs));
+ spdk_blob_close(g_blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+ /* reload the blobstore */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Create second blob */
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid2 = g_blobid;
+
+ /* Create third blob */
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid3 = g_blobid;
+
+ spdk_bs_open_blob(g_bs, blobid2, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ /* Set some xattrs for second blob */
+ rc = spdk_blob_set_xattr(blob, "name", "log1.txt", strlen("log1.txt") + 1);
+ CU_ASSERT(rc == 0);
+
+ length = 5432;
+ rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length));
+ CU_ASSERT(rc == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ blob = NULL;
+ g_blob = NULL;
+ g_blobid = SPDK_BLOBID_INVALID;
+
+ spdk_bs_open_blob(g_bs, blobid3, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ /* Set some xattrs for third blob */
+ rc = spdk_blob_set_xattr(blob, "name", "log2.txt", strlen("log2.txt") + 1);
+ CU_ASSERT(rc == 0);
+
+ length = 5432;
+ rc = spdk_blob_set_xattr(blob, "length", &length, sizeof(length));
+ CU_ASSERT(rc == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ blob = NULL;
+ g_blob = NULL;
+ g_blobid = SPDK_BLOBID_INVALID;
+
+ /* Mark second blob as invalid */
+ page_num = _spdk_bs_blobid_to_page(blobid2);
+
+ index = DEV_BUFFER_BLOCKLEN * (g_bs->md_start + page_num);
+ page = (struct spdk_blob_md_page *)&g_dev_buffer[index];
+ page->sequence_num = 1;
+ page->crc = _spdk_blob_md_page_calc_crc(page);
+
+ free_clusters = spdk_bs_free_cluster_count(g_bs);
+
+ /* Dirty shutdown */
+ _spdk_bs_free(g_bs);
+ /* reload the blobstore */
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_open_blob(g_bs, blobid2, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+ CU_ASSERT(g_blob == NULL);
+
+ spdk_bs_open_blob(g_bs, blobid3, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(g_bs));
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ blob = NULL;
+ g_blob = NULL;
+ g_blobid = SPDK_BLOBID_INVALID;
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_flags(void)
+{
+ struct spdk_bs_dev *dev;
+ spdk_blob_id blobid_invalid, blobid_data_ro, blobid_md_ro;
+ struct spdk_blob *blob_invalid, *blob_data_ro, *blob_md_ro;
+ struct spdk_bs_opts opts;
+ int rc;
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ /* Create three blobs - one each for testing invalid, data_ro and md_ro flags. */
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid_invalid = g_blobid;
+
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid_data_ro = g_blobid;
+
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid_md_ro = g_blobid;
+
+ spdk_bs_open_blob(g_bs, blobid_invalid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob_invalid = g_blob;
+
+ spdk_bs_open_blob(g_bs, blobid_data_ro, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob_data_ro = g_blob;
+
+ spdk_bs_open_blob(g_bs, blobid_md_ro, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob_md_ro = g_blob;
+
+ /* Change the size of blob_data_ro to check if flags are serialized
+ * when blob has non zero number of extents */
+ spdk_blob_resize(blob_data_ro, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Set the xattr to check if flags are serialized
+ * when blob has non zero number of xattrs */
+ rc = spdk_blob_set_xattr(blob_md_ro, "name", "log.txt", strlen("log.txt") + 1);
+ CU_ASSERT(rc == 0);
+
+ blob_invalid->invalid_flags = (1ULL << 63);
+ blob_invalid->state = SPDK_BLOB_STATE_DIRTY;
+ blob_data_ro->data_ro_flags = (1ULL << 62);
+ blob_data_ro->state = SPDK_BLOB_STATE_DIRTY;
+ blob_md_ro->md_ro_flags = (1ULL << 61);
+ blob_md_ro->state = SPDK_BLOB_STATE_DIRTY;
+
+ g_bserrno = -1;
+ spdk_blob_sync_md(blob_invalid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bserrno = -1;
+ spdk_blob_sync_md(blob_data_ro, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bserrno = -1;
+ spdk_blob_sync_md(blob_md_ro, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ g_bserrno = -1;
+ spdk_blob_close(blob_invalid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob_invalid = NULL;
+ g_bserrno = -1;
+ spdk_blob_close(blob_data_ro, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob_data_ro = NULL;
+ g_bserrno = -1;
+ spdk_blob_close(blob_md_ro, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob_md_ro = NULL;
+
+ g_blob = NULL;
+ g_blobid = SPDK_BLOBID_INVALID;
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+ /* Load an existing blob store */
+ dev = init_dev();
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ g_blob = NULL;
+ g_bserrno = 0;
+ spdk_bs_open_blob(g_bs, blobid_invalid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+ CU_ASSERT(g_blob == NULL);
+
+ g_blob = NULL;
+ g_bserrno = -1;
+ spdk_bs_open_blob(g_bs, blobid_data_ro, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob_data_ro = g_blob;
+ /* If an unknown data_ro flag was found, the blob should be marked both data and md read-only. */
+ CU_ASSERT(blob_data_ro->data_ro == true);
+ CU_ASSERT(blob_data_ro->md_ro == true);
+ CU_ASSERT(spdk_blob_get_num_clusters(blob_data_ro) == 10);
+
+ g_blob = NULL;
+ g_bserrno = -1;
+ spdk_bs_open_blob(g_bs, blobid_md_ro, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob_md_ro = g_blob;
+ CU_ASSERT(blob_md_ro->data_ro == false);
+ CU_ASSERT(blob_md_ro->md_ro == true);
+
+ g_bserrno = -1;
+ spdk_blob_sync_md(blob_md_ro, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(blob_data_ro, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ spdk_blob_close(blob_md_ro, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+}
+
+static void
+bs_version(void)
+{
+ struct spdk_bs_super_block *super;
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_opts opts;
+ spdk_blob_id blobid;
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+ /*
+ * Change the bs version on disk. This will allow us to
+ * test that the version does not get modified automatically
+ * when loading and unloading the blobstore.
+ */
+ super = (struct spdk_bs_super_block *)&g_dev_buffer[0];
+ CU_ASSERT(super->version == SPDK_BS_VERSION);
+ CU_ASSERT(super->clean == 1);
+ super->version = 2;
+ /*
+ * Version 2 metadata does not have a used blobid mask, so clear
+ * those fields in the super block and zero the corresponding
+ * region on "disk". We will use this to ensure blob IDs are
+ * correctly reconstructed.
+ */
+ memset(&g_dev_buffer[super->used_blobid_mask_start * SPDK_BS_PAGE_SIZE], 0,
+ super->used_blobid_mask_len * SPDK_BS_PAGE_SIZE);
+ super->used_blobid_mask_start = 0;
+ super->used_blobid_mask_len = 0;
+ super->crc = _spdk_blob_md_page_calc_crc(super);
+
+ /* Load an existing blob store */
+ dev = init_dev();
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ CU_ASSERT(super->clean == 1);
+
+ /*
+ * Create a blob - just to make sure that when we unload it
+ * results in writing the super block (since metadata pages
+ * were allocated.
+ */
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ CU_ASSERT(super->version == 2);
+ CU_ASSERT(super->used_blobid_mask_start == 0);
+ CU_ASSERT(super->used_blobid_mask_len == 0);
+
+ dev = init_dev();
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ g_blob = NULL;
+ spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+
+ spdk_blob_close(g_blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ CU_ASSERT(super->version == 2);
+ CU_ASSERT(super->used_blobid_mask_start == 0);
+ CU_ASSERT(super->used_blobid_mask_len == 0);
+}
+
+static void
+blob_set_xattrs(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid;
+ const void *value;
+ size_t value_len;
+ int rc;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ /* Create blob with extra attributes */
+ spdk_blob_opts_init(&opts);
+
+ opts.xattrs.names = g_xattr_names;
+ opts.xattrs.get_value = _get_xattr_value;
+ opts.xattrs.count = 3;
+ opts.xattrs.ctx = &g_ctx;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ /* Get the xattrs */
+ value = NULL;
+
+ rc = spdk_blob_get_xattr_value(blob, g_xattr_names[0], &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[0]));
+ CU_ASSERT_NSTRING_EQUAL_FATAL(value, g_xattr_values[0], value_len);
+
+ rc = spdk_blob_get_xattr_value(blob, g_xattr_names[1], &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[1]));
+ CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[1], value_len);
+
+ rc = spdk_blob_get_xattr_value(blob, g_xattr_names[2], &value, &value_len);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(value_len == strlen(g_xattr_values[2]));
+ CU_ASSERT_NSTRING_EQUAL((char *)value, g_xattr_values[2], value_len);
+
+ /* Try to get non existing attribute */
+
+ rc = spdk_blob_get_xattr_value(blob, "foobar", &value, &value_len);
+ CU_ASSERT(rc == -ENOENT);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob = NULL;
+ g_blob = NULL;
+ g_blobid = SPDK_BLOBID_INVALID;
+
+ /* NULL callback */
+ spdk_blob_opts_init(&opts);
+ opts.xattrs.names = g_xattr_names;
+ opts.xattrs.get_value = NULL;
+ opts.xattrs.count = 1;
+ opts.xattrs.ctx = &g_ctx;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+
+ /* NULL values */
+ spdk_blob_opts_init(&opts);
+ opts.xattrs.names = g_xattr_names;
+ opts.xattrs.get_value = _get_xattr_value_null;
+ opts.xattrs.count = 1;
+ opts.xattrs.ctx = NULL;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == -EINVAL);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+}
+
+static void
+blob_thin_prov_alloc(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid;
+ uint64_t free_clusters;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+ free_clusters = spdk_bs_free_cluster_count(bs);
+
+ /* Set blob as thin provisioned */
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = true;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(blob->active.num_clusters == 0);
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 0);
+
+ /* The blob started at 0 clusters. Resize it to be 5, but still unallocated. */
+ spdk_blob_resize(blob, 5, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ CU_ASSERT(blob->active.num_clusters == 5);
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5);
+
+ /* Grow it to 1TB - still unallocated */
+ spdk_blob_resize(blob, 262144, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ CU_ASSERT(blob->active.num_clusters == 262144);
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 262144);
+
+ spdk_blob_sync_md(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ /* Sync must not change anything */
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ CU_ASSERT(blob->active.num_clusters == 262144);
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 262144);
+ /* Since clusters are not allocated,
+ * number of metadata pages is expected to be minimal.
+ */
+ CU_ASSERT(blob->active.num_pages == 1);
+
+ /* Shrink the blob to 3 clusters - still unallocated */
+ spdk_blob_resize(blob, 3, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ CU_ASSERT(blob->active.num_clusters == 3);
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 3);
+
+ spdk_blob_sync_md(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ /* Sync must not change anything */
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ CU_ASSERT(blob->active.num_clusters == 3);
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 3);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+
+ /* Load an existing blob store */
+ dev = init_dev();
+ spdk_bs_load(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ bs = g_bs;
+
+ spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ /* Check that clusters allocation and size is still the same */
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ CU_ASSERT(blob->active.num_clusters == 3);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_insert_cluster_msg(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid;
+ uint64_t free_clusters;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+ free_clusters = spdk_bs_free_cluster_count(bs);
+
+ /* Set blob as thin provisioned */
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = true;
+ opts.num_clusters = 4;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(blob->active.num_clusters == 4);
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 4);
+ CU_ASSERT(blob->active.clusters[1] == 0);
+
+ _spdk_bs_claim_cluster(bs, 0xF);
+ _spdk_blob_insert_cluster_on_md_thread(blob, 1, 0xF, blob_op_complete, NULL);
+
+ CU_ASSERT(blob->active.clusters[1] != 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+
+ /* Load an existing blob store */
+ dev = init_dev();
+ spdk_bs_load(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ bs = g_bs;
+
+ spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(blob->active.clusters[1] != 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_thin_prov_rw(void)
+{
+ static const uint8_t zero[10 * 4096] = { 0 };
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_io_channel *channel;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid;
+ uint64_t free_clusters;
+ uint64_t page_size;
+ uint8_t payload_read[10 * 4096];
+ uint8_t payload_write[10 * 4096];
+ uint64_t write_bytes;
+ uint64_t read_bytes;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+ free_clusters = spdk_bs_free_cluster_count(bs);
+ page_size = spdk_bs_get_page_size(bs);
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = true;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(blob->active.num_clusters == 0);
+
+ /* The blob started at 0 clusters. Resize it to be 5, but still unallocated. */
+ spdk_blob_resize(blob, 5, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ CU_ASSERT(blob->active.num_clusters == 5);
+
+ spdk_blob_sync_md(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ /* Sync must not change anything */
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ CU_ASSERT(blob->active.num_clusters == 5);
+
+ /* Payload should be all zeros from unallocated clusters */
+ memset(payload_read, 0xFF, sizeof(payload_read));
+ spdk_blob_io_read(blob, channel, payload_read, 4, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(zero, payload_read, 10 * 4096) == 0);
+
+ write_bytes = g_dev_write_bytes;
+ read_bytes = g_dev_read_bytes;
+
+ memset(payload_write, 0xE5, sizeof(payload_write));
+ spdk_blob_io_write(blob, channel, payload_write, 4, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs));
+ /* For thin-provisioned blob we need to write 10 pages plus one page metadata and
+ * read 0 bytes */
+ CU_ASSERT(g_dev_write_bytes - write_bytes == page_size * 11);
+ CU_ASSERT(g_dev_read_bytes - read_bytes == 0);
+
+ spdk_blob_io_read(blob, channel, payload_read, 4, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+
+ spdk_bs_free_io_channel(channel);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+}
+
+static void
+blob_thin_prov_rw_iov(void)
+{
+ static const uint8_t zero[10 * 4096] = { 0 };
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob;
+ struct spdk_io_channel *channel;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid;
+ uint64_t free_clusters;
+ uint8_t payload_read[10 * 4096];
+ uint8_t payload_write[10 * 4096];
+ struct iovec iov_read[3];
+ struct iovec iov_write[3];
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+ free_clusters = spdk_bs_free_cluster_count(bs);
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = true;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(blob->active.num_clusters == 0);
+
+ /* The blob started at 0 clusters. Resize it to be 5, but still unallocated. */
+ spdk_blob_resize(blob, 5, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ CU_ASSERT(blob->active.num_clusters == 5);
+
+ spdk_blob_sync_md(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ /* Sync must not change anything */
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ CU_ASSERT(blob->active.num_clusters == 5);
+
+ /* Payload should be all zeros from unallocated clusters */
+ memset(payload_read, 0xAA, sizeof(payload_read));
+ iov_read[0].iov_base = payload_read;
+ iov_read[0].iov_len = 3 * 4096;
+ iov_read[1].iov_base = payload_read + 3 * 4096;
+ iov_read[1].iov_len = 4 * 4096;
+ iov_read[2].iov_base = payload_read + 7 * 4096;
+ iov_read[2].iov_len = 3 * 4096;
+ spdk_blob_io_readv(blob, channel, iov_read, 3, 250, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(zero, payload_read, 10 * 4096) == 0);
+
+ memset(payload_write, 0xE5, sizeof(payload_write));
+ iov_write[0].iov_base = payload_write;
+ iov_write[0].iov_len = 1 * 4096;
+ iov_write[1].iov_base = payload_write + 1 * 4096;
+ iov_write[1].iov_len = 5 * 4096;
+ iov_write[2].iov_base = payload_write + 6 * 4096;
+ iov_write[2].iov_len = 4 * 4096;
+
+ spdk_blob_io_writev(blob, channel, iov_write, 3, 250, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ memset(payload_read, 0xAA, sizeof(payload_read));
+ iov_read[0].iov_base = payload_read;
+ iov_read[0].iov_len = 3 * 4096;
+ iov_read[1].iov_base = payload_read + 3 * 4096;
+ iov_read[1].iov_len = 4 * 4096;
+ iov_read[2].iov_base = payload_read + 7 * 4096;
+ iov_read[2].iov_len = 3 * 4096;
+ spdk_blob_io_readv(blob, channel, iov_read, 3, 250, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+}
+
+struct iter_ctx {
+ int current_iter;
+ spdk_blob_id blobid[4];
+};
+
+static void
+test_iter(void *arg, struct spdk_blob *blob, int bserrno)
+{
+ struct iter_ctx *iter_ctx = arg;
+ spdk_blob_id blobid;
+
+ CU_ASSERT(bserrno == 0);
+ blobid = spdk_blob_get_id(blob);
+ CU_ASSERT(blobid == iter_ctx->blobid[iter_ctx->current_iter++]);
+}
+
+static void
+bs_load_iter(void)
+{
+ struct spdk_bs_dev *dev;
+ struct iter_ctx iter_ctx = { 0 };
+ struct spdk_blob *blob;
+ int i, rc;
+ struct spdk_bs_opts opts;
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ for (i = 0; i < 4; i++) {
+ g_bserrno = -1;
+ g_blobid = SPDK_BLOBID_INVALID;
+ spdk_bs_create_blob(g_bs, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ iter_ctx.blobid[i] = g_blobid;
+
+ g_bserrno = -1;
+ g_blob = NULL;
+ spdk_bs_open_blob(g_bs, g_blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ /* Just save the blobid as an xattr for testing purposes. */
+ rc = spdk_blob_set_xattr(blob, "blobid", &g_blobid, sizeof(g_blobid));
+ CU_ASSERT(rc == 0);
+
+ /* Resize the blob */
+ spdk_blob_resize(blob, i, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ }
+
+ g_bserrno = -1;
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+ opts.iter_cb_fn = test_iter;
+ opts.iter_cb_arg = &iter_ctx;
+
+ /* Test blob iteration during load after a clean shutdown. */
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ /* Dirty shutdown */
+ _spdk_bs_free(g_bs);
+
+ dev = init_dev();
+ spdk_bs_opts_init(&opts);
+ snprintf(opts.bstype.bstype, sizeof(opts.bstype.bstype), "TESTTYPE");
+ opts.iter_cb_fn = test_iter;
+ iter_ctx.current_iter = 0;
+ opts.iter_cb_arg = &iter_ctx;
+
+ /* Test blob iteration during load after a dirty shutdown. */
+ spdk_bs_load(dev, &opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+}
+
+static void
+blob_snapshot_rw(void)
+{
+ static const uint8_t zero[10 * 4096] = { 0 };
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob, *snapshot;
+ struct spdk_io_channel *channel;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid, snapshotid;
+ uint64_t free_clusters;
+ uint64_t cluster_size;
+ uint64_t page_size;
+ uint8_t payload_read[10 * 4096];
+ uint8_t payload_write[10 * 4096];
+ uint64_t write_bytes;
+ uint64_t read_bytes;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+ free_clusters = spdk_bs_free_cluster_count(bs);
+ cluster_size = spdk_bs_get_cluster_size(bs);
+ page_size = spdk_bs_get_page_size(bs);
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = true;
+ opts.num_clusters = 5;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5);
+
+ memset(payload_read, 0xFF, sizeof(payload_read));
+ spdk_blob_io_read(blob, channel, payload_read, 4, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(zero, payload_read, 10 * 4096) == 0);
+
+ memset(payload_write, 0xE5, sizeof(payload_write));
+ spdk_blob_io_write(blob, channel, payload_write, 4, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs));
+
+ /* Create snapshot from blob */
+ spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ snapshotid = g_blobid;
+
+ spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ snapshot = g_blob;
+ CU_ASSERT(snapshot->data_ro == true)
+ CU_ASSERT(snapshot->md_ro == true)
+
+ CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 5)
+
+ write_bytes = g_dev_write_bytes;
+ read_bytes = g_dev_read_bytes;
+
+ memset(payload_write, 0xAA, sizeof(payload_write));
+ spdk_blob_io_write(blob, channel, payload_write, 4, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs));
+
+ /* For a clone we need to allocate and copy one cluster, update one page of metadata
+ * and then write 10 pages of payload.
+ */
+ CU_ASSERT(g_dev_write_bytes - write_bytes == page_size * 11 + cluster_size);
+ CU_ASSERT(g_dev_read_bytes - read_bytes == cluster_size);
+
+ spdk_blob_io_read(blob, channel, payload_read, 4, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0);
+
+ /* Data on snapshot should not change after write to clone */
+ memset(payload_write, 0xE5, sizeof(payload_write));
+ spdk_blob_io_read(snapshot, channel, payload_read, 4, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(snapshot, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+}
+
+static void
+blob_snapshot_rw_iov(void)
+{
+ static const uint8_t zero[10 * 4096] = { 0 };
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob, *snapshot;
+ struct spdk_io_channel *channel;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid, snapshotid;
+ uint64_t free_clusters;
+ uint8_t payload_read[10 * 4096];
+ uint8_t payload_write[10 * 4096];
+ struct iovec iov_read[3];
+ struct iovec iov_write[3];
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+ free_clusters = spdk_bs_free_cluster_count(bs);
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = true;
+ opts.num_clusters = 5;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5);
+
+ /* Create snapshot from blob */
+ spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ snapshotid = g_blobid;
+
+ spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ snapshot = g_blob;
+ CU_ASSERT(snapshot->data_ro == true)
+ CU_ASSERT(snapshot->md_ro == true)
+ CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 5);
+
+ /* Payload should be all zeros from unallocated clusters */
+ memset(payload_read, 0xAA, sizeof(payload_read));
+ iov_read[0].iov_base = payload_read;
+ iov_read[0].iov_len = 3 * 4096;
+ iov_read[1].iov_base = payload_read + 3 * 4096;
+ iov_read[1].iov_len = 4 * 4096;
+ iov_read[2].iov_base = payload_read + 7 * 4096;
+ iov_read[2].iov_len = 3 * 4096;
+ spdk_blob_io_readv(blob, channel, iov_read, 3, 250, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(zero, payload_read, 10 * 4096) == 0);
+
+ memset(payload_write, 0xE5, sizeof(payload_write));
+ iov_write[0].iov_base = payload_write;
+ iov_write[0].iov_len = 1 * 4096;
+ iov_write[1].iov_base = payload_write + 1 * 4096;
+ iov_write[1].iov_len = 5 * 4096;
+ iov_write[2].iov_base = payload_write + 6 * 4096;
+ iov_write[2].iov_len = 4 * 4096;
+
+ spdk_blob_io_writev(blob, channel, iov_write, 3, 250, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ memset(payload_read, 0xAA, sizeof(payload_read));
+ iov_read[0].iov_base = payload_read;
+ iov_read[0].iov_len = 3 * 4096;
+ iov_read[1].iov_base = payload_read + 3 * 4096;
+ iov_read[1].iov_len = 4 * 4096;
+ iov_read[2].iov_base = payload_read + 7 * 4096;
+ iov_read[2].iov_len = 3 * 4096;
+ spdk_blob_io_readv(blob, channel, iov_read, 3, 250, 10, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_write, payload_read, 10 * 4096) == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(snapshot, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+}
+
+/**
+ * Inflate / decouple parent rw unit tests.
+ *
+ * --------------
+ * original blob: 0 1 2 3 4
+ * ,---------+---------+---------+---------+---------.
+ * snapshot |xxxxxxxxx|xxxxxxxxx|xxxxxxxxx|xxxxxxxxx| - |
+ * +---------+---------+---------+---------+---------+
+ * snapshot2 | - |yyyyyyyyy| - |yyyyyyyyy| - |
+ * +---------+---------+---------+---------+---------+
+ * blob | - |zzzzzzzzz| - | - | - |
+ * '---------+---------+---------+---------+---------'
+ * . . . . . .
+ * -------- . . . . . .
+ * inflate: . . . . . .
+ * ,---------+---------+---------+---------+---------.
+ * blob |xxxxxxxxx|zzzzzzzzz|xxxxxxxxx|yyyyyyyyy|000000000|
+ * '---------+---------+---------+---------+---------'
+ *
+ * NOTE: needs to allocate 4 clusters, thin provisioning removed, dependency
+ * on snapshot2 and snapshot removed . . .
+ * . . . . . .
+ * ---------------- . . . . . .
+ * decouple parent: . . . . . .
+ * ,---------+---------+---------+---------+---------.
+ * snapshot |xxxxxxxxx|xxxxxxxxx|xxxxxxxxx|xxxxxxxxx| - |
+ * +---------+---------+---------+---------+---------+
+ * blob | - |zzzzzzzzz| - |yyyyyyyyy| - |
+ * '---------+---------+---------+---------+---------'
+ *
+ * NOTE: needs to allocate 1 cluster, 3 clusters unallocated, dependency
+ * on snapshot2 removed and on snapshot still exists. Snapshot2
+ * should remain a clone of snapshot.
+ */
+static void
+_blob_inflate_rw(bool decouple_parent)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob, *snapshot, *snapshot2;
+ struct spdk_io_channel *channel;
+ struct spdk_blob_opts opts;
+ spdk_blob_id blobid, snapshotid, snapshot2id;
+ uint64_t free_clusters;
+ uint64_t cluster_size;
+
+ uint64_t payload_size;
+ uint8_t *payload_read;
+ uint8_t *payload_write;
+ uint8_t *payload_clone;
+
+ uint64_t pages_per_cluster;
+ uint64_t pages_per_payload;
+
+ int i;
+ spdk_blob_id ids[2];
+ size_t count;
+
+ dev = init_dev();
+
+ spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ free_clusters = spdk_bs_free_cluster_count(bs);
+ cluster_size = spdk_bs_get_cluster_size(bs);
+ pages_per_cluster = cluster_size / spdk_bs_get_page_size(bs);
+ pages_per_payload = pages_per_cluster * 5;
+
+ payload_size = cluster_size * 5;
+
+ payload_read = malloc(payload_size);
+ SPDK_CU_ASSERT_FATAL(payload_read != NULL);
+
+ payload_write = malloc(payload_size);
+ SPDK_CU_ASSERT_FATAL(payload_write != NULL);
+
+ payload_clone = malloc(payload_size);
+ SPDK_CU_ASSERT_FATAL(payload_clone != NULL);
+
+ channel = spdk_bs_alloc_io_channel(bs);
+ SPDK_CU_ASSERT_FATAL(channel != NULL);
+
+ /* Create blob */
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = true;
+ opts.num_clusters = 5;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs));
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5);
+
+ /* 1) Initial read should return zeroed payload */
+ memset(payload_read, 0xFF, payload_size);
+ spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(spdk_mem_all_zero(payload_read, payload_size));
+
+ /* Fill whole blob with a pattern, except last cluster (to be sure it
+ * isn't allocated) */
+ memset(payload_write, 0xE5, payload_size - cluster_size);
+ spdk_blob_io_write(blob, channel, payload_write, 0, pages_per_payload -
+ pages_per_cluster, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs));
+
+ /* 2) Create snapshot from blob (first level) */
+ spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ snapshotid = g_blobid;
+
+ spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ snapshot = g_blob;
+ CU_ASSERT(snapshot->data_ro == true)
+ CU_ASSERT(snapshot->md_ro == true)
+
+ CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 5)
+
+ /* Write every second cluster with a pattern.
+ *
+ * Last cluster shouldn't be written, to be sure that snapshot nor clone
+ * doesn't allocate it.
+ *
+ * payload_clone stores expected result on "blob" read at the time and
+ * is used only to check data consistency on clone before and after
+ * inflation. Initially we fill it with a backing snapshots pattern
+ * used before.
+ */
+ memset(payload_clone, 0xE5, payload_size - cluster_size);
+ memset(payload_clone + payload_size - cluster_size, 0x00, cluster_size);
+ memset(payload_write, 0xAA, payload_size);
+ for (i = 1; i < 5; i += 2) {
+ spdk_blob_io_write(blob, channel, payload_write, i * pages_per_cluster,
+ pages_per_cluster, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Update expected result */
+ memcpy(payload_clone + (cluster_size * i), payload_write,
+ cluster_size);
+ }
+ CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs));
+
+ /* Check data consistency on clone */
+ memset(payload_read, 0xFF, payload_size);
+ spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_clone, payload_read, payload_size) == 0);
+
+ /* 3) Create second levels snapshot from blob */
+ spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ snapshot2id = g_blobid;
+
+ spdk_bs_open_blob(bs, snapshot2id, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ snapshot2 = g_blob;
+ CU_ASSERT(snapshot2->data_ro == true)
+ CU_ASSERT(snapshot2->md_ro == true)
+
+ CU_ASSERT(spdk_blob_get_num_clusters(snapshot2) == 5)
+
+ CU_ASSERT(snapshot2->parent_id == snapshotid);
+
+ /* Write one cluster on the top level blob. This cluster (1) covers
+ * already allocated cluster in the snapshot2, so shouldn't be inflated
+ * at all */
+ spdk_blob_io_write(blob, channel, payload_write, pages_per_cluster,
+ pages_per_cluster, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Update expected result */
+ memcpy(payload_clone + cluster_size, payload_write, cluster_size);
+
+ /* Check data consistency on clone */
+ memset(payload_read, 0xFF, payload_size);
+ spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_clone, payload_read, payload_size) == 0);
+
+
+ /* Close all blobs */
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(snapshot2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(snapshot, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Check snapshot-clone relations */
+ count = 2;
+ CU_ASSERT(spdk_blob_get_clones(bs, snapshotid, ids, &count) == 0);
+ CU_ASSERT(count == 1);
+ CU_ASSERT(ids[0] == snapshot2id);
+
+ count = 2;
+ CU_ASSERT(spdk_blob_get_clones(bs, snapshot2id, ids, &count) == 0);
+ CU_ASSERT(count == 1);
+ CU_ASSERT(ids[0] == blobid);
+
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshot2id);
+
+ free_clusters = spdk_bs_free_cluster_count(bs);
+ if (!decouple_parent) {
+ /* Do full blob inflation */
+ spdk_bs_inflate_blob(bs, channel, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* All clusters should be inflated (except one already allocated
+ * in a top level blob) */
+ CU_ASSERT(spdk_bs_free_cluster_count(bs) == free_clusters - 4);
+
+ /* Check if relation tree updated correctly */
+ count = 2;
+ CU_ASSERT(spdk_blob_get_clones(bs, snapshotid, ids, &count) == 0);
+
+ /* snapshotid have one clone */
+ CU_ASSERT(count == 1);
+ CU_ASSERT(ids[0] == snapshot2id);
+
+ /* snapshot2id have no clones */
+ count = 2;
+ CU_ASSERT(spdk_blob_get_clones(bs, snapshot2id, ids, &count) == 0);
+ CU_ASSERT(count == 0);
+
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == SPDK_BLOBID_INVALID);
+ } else {
+ /* Decouple parent of blob */
+ spdk_bs_blob_decouple_parent(bs, channel, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Only one cluster from a parent should be inflated (second one
+ * is covered by a cluster written on a top level blob, and
+ * already allocated) */
+ CU_ASSERT(spdk_bs_free_cluster_count(bs) == free_clusters - 1);
+
+ /* Check if relation tree updated correctly */
+ count = 2;
+ CU_ASSERT(spdk_blob_get_clones(bs, snapshotid, ids, &count) == 0);
+
+ /* snapshotid have two clones now */
+ CU_ASSERT(count == 2);
+ CU_ASSERT(ids[0] == blobid || ids[1] == blobid);
+ CU_ASSERT(ids[0] == snapshot2id || ids[1] == snapshot2id);
+
+ /* snapshot2id have no clones */
+ count = 2;
+ CU_ASSERT(spdk_blob_get_clones(bs, snapshot2id, ids, &count) == 0);
+ CU_ASSERT(count == 0);
+
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshotid);
+ }
+
+ /* Try to delete snapshot2 (should pass) */
+ spdk_bs_delete_blob(bs, snapshot2id, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Try to delete base snapshot (for decouple_parent should fail while
+ * dependency still exists) */
+ spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
+ CU_ASSERT(decouple_parent || g_bserrno == 0);
+ CU_ASSERT(!decouple_parent || g_bserrno != 0);
+
+ /* Reopen blob after snapshot deletion */
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5);
+
+ /* Check data consistency on inflated blob */
+ memset(payload_read, 0xFF, payload_size);
+ spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload,
+ blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_clone, payload_read, payload_size) == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_free_io_channel(channel);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+
+ free(payload_read);
+ free(payload_write);
+ free(payload_clone);
+}
+
+static void
+blob_inflate_rw(void)
+{
+ _blob_inflate_rw(false);
+ _blob_inflate_rw(true);
+}
+
+/**
+ * Snapshot-clones relation test
+ *
+ * snapshot
+ * |
+ * +-----+-----+
+ * | |
+ * blob(ro) snapshot2
+ * | |
+ * clone2 clone
+ */
+static void
+blob_relations(void)
+{
+ struct spdk_blob_store *bs;
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_opts bs_opts;
+ struct spdk_blob_opts opts;
+ struct spdk_blob *blob, *snapshot, *snapshot2, *clone, *clone2;
+ spdk_blob_id blobid, cloneid, snapshotid, cloneid2, snapshotid2;
+ int rc;
+ size_t count;
+ spdk_blob_id ids[10] = {};
+
+ dev = init_dev();
+ spdk_bs_opts_init(&bs_opts);
+ snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "TESTTYPE");
+
+ spdk_bs_init(dev, &bs_opts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+ /* 1. Create blob with 10 clusters */
+
+ spdk_blob_opts_init(&opts);
+ opts.num_clusters = 10;
+
+ spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ blob = g_blob;
+
+ CU_ASSERT(!spdk_blob_is_read_only(blob));
+ CU_ASSERT(!spdk_blob_is_snapshot(blob));
+ CU_ASSERT(!spdk_blob_is_clone(blob));
+ CU_ASSERT(!spdk_blob_is_thin_provisioned(blob));
+
+ /* blob should not have underlying snapshot nor clones */
+ CU_ASSERT(blob->parent_id == SPDK_BLOBID_INVALID);
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == SPDK_BLOBID_INVALID);
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, blobid, ids, &count);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(count == 0);
+
+
+ /* 2. Create snapshot */
+
+ spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ snapshotid = g_blobid;
+
+ spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ snapshot = g_blob;
+
+ CU_ASSERT(spdk_blob_is_read_only(snapshot));
+ CU_ASSERT(spdk_blob_is_snapshot(snapshot));
+ CU_ASSERT(!spdk_blob_is_clone(snapshot));
+ CU_ASSERT(snapshot->parent_id == SPDK_BLOBID_INVALID);
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid) == SPDK_BLOBID_INVALID);
+
+ /* Check if original blob is converted to the clone of snapshot */
+ CU_ASSERT(!spdk_blob_is_read_only(blob));
+ CU_ASSERT(!spdk_blob_is_snapshot(blob));
+ CU_ASSERT(spdk_blob_is_clone(blob));
+ CU_ASSERT(spdk_blob_is_thin_provisioned(blob));
+ CU_ASSERT(blob->parent_id == snapshotid);
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshotid);
+
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, snapshotid, ids, &count);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(count == 1);
+ CU_ASSERT(ids[0] == blobid);
+
+
+ /* 3. Create clone from snapshot */
+
+ spdk_bs_create_clone(bs, snapshotid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ cloneid = g_blobid;
+
+ spdk_bs_open_blob(bs, cloneid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ clone = g_blob;
+
+ CU_ASSERT(!spdk_blob_is_read_only(clone));
+ CU_ASSERT(!spdk_blob_is_snapshot(clone));
+ CU_ASSERT(spdk_blob_is_clone(clone));
+ CU_ASSERT(spdk_blob_is_thin_provisioned(clone));
+ CU_ASSERT(clone->parent_id == snapshotid);
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid) == snapshotid);
+
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, cloneid, ids, &count);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(count == 0);
+
+ /* Check if clone is on the snapshot's list */
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, snapshotid, ids, &count);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(ids[0] == blobid || ids[1] == blobid);
+ CU_ASSERT(ids[0] == cloneid || ids[1] == cloneid);
+
+
+ /* 4. Create snapshot of the clone */
+
+ spdk_bs_create_snapshot(bs, cloneid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ snapshotid2 = g_blobid;
+
+ spdk_bs_open_blob(bs, snapshotid2, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ snapshot2 = g_blob;
+
+ CU_ASSERT(spdk_blob_is_read_only(snapshot2));
+ CU_ASSERT(spdk_blob_is_snapshot(snapshot2));
+ CU_ASSERT(spdk_blob_is_clone(snapshot2));
+ CU_ASSERT(snapshot2->parent_id == snapshotid);
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid2) == snapshotid);
+
+ /* Check if clone is converted to the clone of snapshot2 and snapshot2
+ * is a child of snapshot */
+ CU_ASSERT(!spdk_blob_is_read_only(clone));
+ CU_ASSERT(!spdk_blob_is_snapshot(clone));
+ CU_ASSERT(spdk_blob_is_clone(clone));
+ CU_ASSERT(spdk_blob_is_thin_provisioned(clone));
+ CU_ASSERT(clone->parent_id == snapshotid2);
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid) == snapshotid2);
+
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, snapshotid2, ids, &count);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(count == 1);
+ CU_ASSERT(ids[0] == cloneid);
+
+
+ /* 5. Try to create clone from read only blob */
+
+ /* Mark blob as read only */
+ spdk_blob_set_read_only(blob);
+ spdk_blob_sync_md(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Check if previously created blob is read only clone */
+ CU_ASSERT(spdk_blob_is_read_only(blob));
+ CU_ASSERT(!spdk_blob_is_snapshot(blob));
+ CU_ASSERT(spdk_blob_is_clone(blob));
+ CU_ASSERT(spdk_blob_is_thin_provisioned(blob));
+
+ /* Create clone from read only blob */
+ spdk_bs_create_clone(bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ cloneid2 = g_blobid;
+
+ spdk_bs_open_blob(bs, cloneid2, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_blob != NULL);
+ clone2 = g_blob;
+
+ CU_ASSERT(!spdk_blob_is_read_only(clone2));
+ CU_ASSERT(!spdk_blob_is_snapshot(clone2));
+ CU_ASSERT(spdk_blob_is_clone(clone2));
+ CU_ASSERT(spdk_blob_is_thin_provisioned(clone2));
+
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid2) == blobid);
+
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, blobid, ids, &count);
+ CU_ASSERT(rc == 0);
+
+ CU_ASSERT(count == 1);
+ CU_ASSERT(ids[0] == cloneid2);
+
+ /* Close blobs */
+
+ spdk_blob_close(clone2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(clone, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(snapshot, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_blob_close(snapshot2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Try to delete snapshot with created clones */
+ spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+
+ spdk_bs_delete_blob(bs, snapshotid2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+
+ spdk_bs_unload(bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+
+ /* Load an existing blob store */
+ dev = init_dev();
+ snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "TESTTYPE");
+
+ spdk_bs_load(dev, NULL, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+ bs = g_bs;
+
+
+ /* NULL ids array should return number of clones in count */
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, snapshotid, NULL, &count);
+ CU_ASSERT(rc == -ENOMEM);
+ CU_ASSERT(count == 2);
+
+ /* incorrect array size */
+ count = 1;
+ rc = spdk_blob_get_clones(bs, snapshotid, ids, &count);
+ CU_ASSERT(rc == -ENOMEM);
+ CU_ASSERT(count == 2);
+
+
+ /* Verify structure of loaded blob store */
+
+ /* snapshot */
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid) == SPDK_BLOBID_INVALID);
+
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, snapshotid, ids, &count);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(count == 2);
+ CU_ASSERT(ids[0] == blobid || ids[1] == blobid);
+ CU_ASSERT(ids[0] == snapshotid2 || ids[1] == snapshotid2);
+
+ /* blob */
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshotid);
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, blobid, ids, &count);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(count == 1);
+ CU_ASSERT(ids[0] == cloneid2);
+
+ /* clone */
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid) == snapshotid2);
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, cloneid, ids, &count);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(count == 0);
+
+ /* snapshot2 */
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid2) == snapshotid);
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, snapshotid2, ids, &count);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(count == 1);
+ CU_ASSERT(ids[0] == cloneid);
+
+ /* clone2 */
+ CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid2) == blobid);
+ count = SPDK_COUNTOF(ids);
+ rc = spdk_blob_get_clones(bs, cloneid2, ids, &count);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(count == 0);
+
+ /* Try to delete all blobs in the worse possible order */
+
+ spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+
+ spdk_bs_delete_blob(bs, snapshotid2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+
+ spdk_bs_delete_blob(bs, cloneid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_delete_blob(bs, snapshotid2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+
+ spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno != 0);
+
+ spdk_bs_delete_blob(bs, cloneid2, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ spdk_bs_unload(bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ g_bs = NULL;
+}
+
+static void
+test_io_write(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel)
+{
+ uint8_t payload_ff[64 * 512];
+ uint8_t payload_aa[64 * 512];
+ uint8_t payload_00[64 * 512];
+ uint8_t *cluster0, *cluster1;
+
+ memset(payload_ff, 0xFF, sizeof(payload_ff));
+ memset(payload_aa, 0xAA, sizeof(payload_aa));
+ memset(payload_00, 0x00, sizeof(payload_00));
+
+ /* Try to perform I/O with io unit = 512 */
+ spdk_blob_io_write(blob, channel, payload_ff, 0, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* If thin provisioned is set cluster should be allocated now */
+ SPDK_CU_ASSERT_FATAL(blob->active.clusters[0] != 0);
+ cluster0 = &g_dev_buffer[blob->active.clusters[0] * dev->blocklen];
+
+ /* Each character 0-F symbolizes single io_unit containing 512 bytes block filled with that character.
+ * Each page is separated by |. Whole block [...] symbolizes one cluster (containing 4 pages). */
+ /* cluster0: [ F000 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 31 * 512) == 0);
+
+ /* Verify write with offset on first page */
+ spdk_blob_io_write(blob, channel, payload_ff, 2, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* cluster0: [ F0F0 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_00, 28 * 512) == 0);
+
+ /* Verify write with offset on first page */
+ spdk_blob_io_write(blob, channel, payload_ff, 4, 4, blob_op_complete, NULL);
+
+ /* cluster0: [ F0F0 FFFF | 0000 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_ff, 4 * 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 8 * 512, payload_00, 24 * 512) == 0);
+
+ /* Verify write with offset on second page */
+ spdk_blob_io_write(blob, channel, payload_ff, 8, 4, blob_op_complete, NULL);
+
+ /* cluster0: [ F0F0 FFFF | FFFF 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_ff, 8 * 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 12 * 512, payload_00, 20 * 512) == 0);
+
+ /* Verify write across multiple pages */
+ spdk_blob_io_write(blob, channel, payload_aa, 4, 8, blob_op_complete, NULL);
+
+ /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 12 * 512, payload_00, 20 * 512) == 0);
+
+ /* Verify write across multiple clusters */
+ spdk_blob_io_write(blob, channel, payload_ff, 28, 8, blob_op_complete, NULL);
+
+ SPDK_CU_ASSERT_FATAL(blob->active.clusters[1] != 0);
+ cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen];
+
+ /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 28 * 512, payload_ff, 4 * 512) == 0);
+
+ CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_ff, 4 * 512) == 0);
+ CU_ASSERT(memcmp(cluster1 + 4 * 512, payload_00, 28 * 512) == 0);
+
+ /* Verify write to second cluster */
+ spdk_blob_io_write(blob, channel, payload_ff, 32 + 12, 2, blob_op_complete, NULL);
+
+ SPDK_CU_ASSERT_FATAL(blob->active.clusters[1] != 0);
+ cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen];
+
+ /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 28 * 512, payload_ff, 4 * 512) == 0);
+
+ CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_ff, 4 * 512) == 0);
+ CU_ASSERT(memcmp(cluster1 + 4 * 512, payload_00, 8 * 512) == 0);
+ CU_ASSERT(memcmp(cluster1 + 12 * 512, payload_ff, 2 * 512) == 0);
+ CU_ASSERT(memcmp(cluster1 + 14 * 512, payload_00, 18 * 512) == 0);
+}
+
+static void
+test_io_read(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel)
+{
+ uint8_t payload_read[64 * 512];
+ uint8_t payload_ff[64 * 512];
+ uint8_t payload_aa[64 * 512];
+ uint8_t payload_00[64 * 512];
+
+ memset(payload_ff, 0xFF, sizeof(payload_ff));
+ memset(payload_aa, 0xAA, sizeof(payload_aa));
+ memset(payload_00, 0x00, sizeof(payload_00));
+
+ /* Read only first io unit */
+ /* cluster0: [ (F)0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ]
+ * payload_read: F000 0000 | 0000 0000 ... */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ spdk_blob_io_read(blob, channel, payload_read, 0, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 31 * 512) == 0);
+
+ /* Read four io_units starting from offset = 2
+ * cluster0: [ F0(F0 AA)AA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ]
+ * payload_read: F0AA 0000 | 0000 0000 ... */
+
+ memset(payload_read, 0x00, sizeof(payload_read));
+ spdk_blob_io_read(blob, channel, payload_read, 2, 4, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 2 * 512, payload_aa, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 3 * 512, payload_aa, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 28 * 512) == 0);
+
+ /* Read eight io_units across multiple pages
+ * cluster0: [ F0F0 (AAAA | AAAA) 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ]
+ * payload_read: AAAA AAAA | 0000 0000 ... */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ spdk_blob_io_read(blob, channel, payload_read, 4, 8, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_aa, 8 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 8 * 512, payload_00, 24 * 512) == 0);
+
+ /* Read eight io_units across multiple clusters
+ * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 (FFFF ]
+ * cluster1: [ FFFF) 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ]
+ * payload_read: FFFF FFFF | 0000 0000 ... */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ spdk_blob_io_read(blob, channel, payload_read, 28, 8, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 8 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 8 * 512, payload_00, 24 * 512) == 0);
+
+ /* Read four io_units from second cluster
+ * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 00(00 FF)00 | 0000 0000 | 0000 0000 ]
+ * payload_read: 00FF 0000 | 0000 0000 ... */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ spdk_blob_io_read(blob, channel, payload_read, 32 + 10, 4, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_00, 2 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 2 * 512, payload_ff, 2 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 28 * 512) == 0);
+
+ /* Read second cluster
+ * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ (FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000) ]
+ * payload_read: FFFF 0000 | 0000 FF00 ... */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ spdk_blob_io_read(blob, channel, payload_read, 32, 32, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 4 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 8 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 12 * 512, payload_ff, 2 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 14 * 512, payload_00, 18 * 512) == 0);
+
+ /* Read whole two clusters
+ * cluster0: [ (F0F0 AAAA | AAAA) 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000) ] */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ spdk_blob_io_read(blob, channel, payload_read, 0, 64, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 4 * 512, payload_aa, 8 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 28 * 512, payload_ff, 4 * 512) == 0);
+
+ CU_ASSERT(memcmp(payload_read + (32 + 0) * 512, payload_ff, 4 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + (32 + 4) * 512, payload_00, 8 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + (32 + 12) * 512, payload_ff, 2 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + (32 + 14) * 512, payload_00, 18 * 512) == 0);
+}
+
+
+static void
+test_io_unmap(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel)
+{
+ uint8_t payload_ff[64 * 512];
+ uint8_t payload_aa[64 * 512];
+ uint8_t payload_00[64 * 512];
+ uint8_t *cluster0, *cluster1;
+
+ memset(payload_ff, 0xFF, sizeof(payload_ff));
+ memset(payload_aa, 0xAA, sizeof(payload_aa));
+ memset(payload_00, 0x00, sizeof(payload_00));
+
+ cluster0 = &g_dev_buffer[blob->active.clusters[0] * dev->blocklen];
+ cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen];
+
+ /* Unmap */
+ spdk_blob_io_unmap(blob, channel, 0, 64, blob_op_complete, NULL);
+
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_00, 32 * 512) == 0);
+ CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_00, 32 * 512) == 0);
+}
+
+static void
+test_io_zeroes(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel)
+{
+ uint8_t payload_ff[64 * 512];
+ uint8_t payload_aa[64 * 512];
+ uint8_t payload_00[64 * 512];
+ uint8_t *cluster0, *cluster1;
+
+ memset(payload_ff, 0xFF, sizeof(payload_ff));
+ memset(payload_aa, 0xAA, sizeof(payload_aa));
+ memset(payload_00, 0x00, sizeof(payload_00));
+
+ cluster0 = &g_dev_buffer[blob->active.clusters[0] * dev->blocklen];
+ cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen];
+
+ /* Write zeroes */
+ spdk_blob_io_write_zeroes(blob, channel, 0, 64, blob_op_complete, NULL);
+
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_00, 32 * 512) == 0);
+ CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_00, 32 * 512) == 0);
+}
+
+
+static void
+test_iov_write(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel)
+{
+ uint8_t payload_ff[64 * 512];
+ uint8_t payload_aa[64 * 512];
+ uint8_t payload_00[64 * 512];
+ uint8_t *cluster0, *cluster1;
+ struct iovec iov[4];
+
+ memset(payload_ff, 0xFF, sizeof(payload_ff));
+ memset(payload_aa, 0xAA, sizeof(payload_aa));
+ memset(payload_00, 0x00, sizeof(payload_00));
+
+ /* Try to perform I/O with io unit = 512 */
+ iov[0].iov_base = payload_ff;
+ iov[0].iov_len = 1 * 512;
+ spdk_blob_io_writev(blob, channel, iov, 1, 0, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* If thin provisioned is set cluster should be allocated now */
+ SPDK_CU_ASSERT_FATAL(blob->active.clusters[0] != 0);
+ cluster0 = &g_dev_buffer[blob->active.clusters[0] * dev->blocklen];
+
+ /* Each character 0-F symbolizes single io_unit containing 512 bytes block filled with that character.
+ * Each page is separated by |. Whole block [...] symbolizes one cluster (containing 4 pages). */
+ /* cluster0: [ F000 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 31 * 512) == 0);
+
+ /* Verify write with offset on first page */
+ iov[0].iov_base = payload_ff;
+ iov[0].iov_len = 1 * 512;
+ spdk_blob_io_writev(blob, channel, iov, 1, 2, 1, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* cluster0: [ F0F0 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_00, 28 * 512) == 0);
+
+ /* Verify write with offset on first page */
+ iov[0].iov_base = payload_ff;
+ iov[0].iov_len = 4 * 512;
+ spdk_blob_io_writev(blob, channel, iov, 1, 4, 4, blob_op_complete, NULL);
+
+ /* cluster0: [ F0F0 FFFF | 0000 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_ff, 4 * 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 8 * 512, payload_00, 24 * 512) == 0);
+
+ /* Verify write with offset on second page */
+ iov[0].iov_base = payload_ff;
+ iov[0].iov_len = 4 * 512;
+ spdk_blob_io_writev(blob, channel, iov, 1, 8, 4, blob_op_complete, NULL);
+
+ /* cluster0: [ F0F0 FFFF | FFFF 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_ff, 8 * 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 12 * 512, payload_00, 20 * 512) == 0);
+
+ /* Verify write across multiple pages */
+ iov[0].iov_base = payload_aa;
+ iov[0].iov_len = 8 * 512;
+ spdk_blob_io_writev(blob, channel, iov, 1, 4, 8, blob_op_complete, NULL);
+
+ /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 12 * 512, payload_00, 20 * 512) == 0);
+
+ /* Verify write across multiple clusters */
+
+ iov[0].iov_base = payload_ff;
+ iov[0].iov_len = 8 * 512;
+ spdk_blob_io_writev(blob, channel, iov, 1, 28, 8, blob_op_complete, NULL);
+
+ SPDK_CU_ASSERT_FATAL(blob->active.clusters[1] != 0);
+ cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen];
+
+ /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 0000 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 12 * 512, payload_00, 16 * 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 28 * 512, payload_ff, 4 * 512) == 0);
+
+ CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_ff, 4 * 512) == 0);
+ CU_ASSERT(memcmp(cluster1 + 4 * 512, payload_00, 28 * 512) == 0);
+
+ /* Verify write to second cluster */
+
+ iov[0].iov_base = payload_ff;
+ iov[0].iov_len = 2 * 512;
+ spdk_blob_io_writev(blob, channel, iov, 1, 32 + 12, 2, blob_op_complete, NULL);
+
+ SPDK_CU_ASSERT_FATAL(blob->active.clusters[1] != 0);
+ cluster1 = &g_dev_buffer[blob->active.clusters[1] * dev->blocklen];
+
+ /* cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ] */
+ CU_ASSERT(memcmp(cluster0 + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 4 * 512, payload_aa, 8 * 512) == 0);
+ CU_ASSERT(memcmp(cluster0 + 28 * 512, payload_ff, 4 * 512) == 0);
+
+ CU_ASSERT(memcmp(cluster1 + 0 * 512, payload_ff, 4 * 512) == 0);
+ CU_ASSERT(memcmp(cluster1 + 4 * 512, payload_00, 8 * 512) == 0);
+ CU_ASSERT(memcmp(cluster1 + 12 * 512, payload_ff, 2 * 512) == 0);
+ CU_ASSERT(memcmp(cluster1 + 14 * 512, payload_00, 18 * 512) == 0);
+}
+
+static void
+test_iov_read(struct spdk_bs_dev *dev, struct spdk_blob *blob, struct spdk_io_channel *channel)
+{
+ uint8_t payload_read[64 * 512];
+ uint8_t payload_ff[64 * 512];
+ uint8_t payload_aa[64 * 512];
+ uint8_t payload_00[64 * 512];
+ struct iovec iov[4];
+
+ memset(payload_ff, 0xFF, sizeof(payload_ff));
+ memset(payload_aa, 0xAA, sizeof(payload_aa));
+ memset(payload_00, 0x00, sizeof(payload_00));
+
+ /* Read only first io unit */
+ /* cluster0: [ (F)0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ]
+ * payload_read: F000 0000 | 0000 0000 ... */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ iov[0].iov_base = payload_read;
+ iov[0].iov_len = 1 * 512;
+ spdk_blob_io_readv(blob, channel, iov, 1, 0, 1, blob_op_complete, NULL);
+
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 31 * 512) == 0);
+
+ /* Read four io_units starting from offset = 2
+ * cluster0: [ F0(F0 AA)AA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ]
+ * payload_read: F0AA 0000 | 0000 0000 ... */
+
+ memset(payload_read, 0x00, sizeof(payload_read));
+ iov[0].iov_base = payload_read;
+ iov[0].iov_len = 4 * 512;
+ spdk_blob_io_readv(blob, channel, iov, 1, 2, 4, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 2 * 512, payload_aa, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 3 * 512, payload_aa, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 28 * 512) == 0);
+
+ /* Read eight io_units across multiple pages
+ * cluster0: [ F0F0 (AAAA | AAAA) 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ]
+ * payload_read: AAAA AAAA | 0000 0000 ... */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ iov[0].iov_base = payload_read;
+ iov[0].iov_len = 4 * 512;
+ iov[1].iov_base = payload_read + 4 * 512;
+ iov[1].iov_len = 4 * 512;
+ spdk_blob_io_readv(blob, channel, iov, 2, 4, 8, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_aa, 8 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 8 * 512, payload_00, 24 * 512) == 0);
+
+ /* Read eight io_units across multiple clusters
+ * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 (FFFF ]
+ * cluster1: [ FFFF) 0000 | 0000 FF00 | 0000 0000 | 0000 0000 ]
+ * payload_read: FFFF FFFF | 0000 0000 ... */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ iov[0].iov_base = payload_read;
+ iov[0].iov_len = 2 * 512;
+ iov[1].iov_base = payload_read + 2 * 512;
+ iov[1].iov_len = 2 * 512;
+ iov[2].iov_base = payload_read + 4 * 512;
+ iov[2].iov_len = 2 * 512;
+ iov[3].iov_base = payload_read + 6 * 512;
+ iov[3].iov_len = 2 * 512;
+ spdk_blob_io_readv(blob, channel, iov, 4, 28, 8, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 8 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 8 * 512, payload_00, 24 * 512) == 0);
+
+ /* Read four io_units from second cluster
+ * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 00(00 FF)00 | 0000 0000 | 0000 0000 ]
+ * payload_read: 00FF 0000 | 0000 0000 ... */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ iov[0].iov_base = payload_read;
+ iov[0].iov_len = 1 * 512;
+ iov[1].iov_base = payload_read + 1 * 512;
+ iov[1].iov_len = 3 * 512;
+ spdk_blob_io_readv(blob, channel, iov, 2, 32 + 10, 4, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_00, 2 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 2 * 512, payload_ff, 2 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 28 * 512) == 0);
+
+ /* Read second cluster
+ * cluster0: [ F0F0 AAAA | AAAA 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ (FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000) ]
+ * payload_read: FFFF 0000 | 0000 FF00 ... */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ iov[0].iov_base = payload_read;
+ iov[0].iov_len = 1 * 512;
+ iov[1].iov_base = payload_read + 1 * 512;
+ iov[1].iov_len = 2 * 512;
+ iov[2].iov_base = payload_read + 3 * 512;
+ iov[2].iov_len = 4 * 512;
+ iov[3].iov_base = payload_read + 7 * 512;
+ iov[3].iov_len = 25 * 512;
+ spdk_blob_io_readv(blob, channel, iov, 4, 32, 32, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 4 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 4 * 512, payload_00, 8 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 12 * 512, payload_ff, 2 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 14 * 512, payload_00, 18 * 512) == 0);
+
+ /* Read whole two clusters
+ * cluster0: [ (F0F0 AAAA | AAAA) 0000 | 0000 0000 | 0000 FFFF ]
+ * cluster1: [ FFFF 0000 | 0000 FF00 | 0000 0000 | 0000 0000) ] */
+ memset(payload_read, 0x00, sizeof(payload_read));
+ iov[0].iov_base = payload_read;
+ iov[0].iov_len = 1 * 512;
+ iov[1].iov_base = payload_read + 1 * 512;
+ iov[1].iov_len = 8 * 512;
+ iov[2].iov_base = payload_read + 9 * 512;
+ iov[2].iov_len = 16 * 512;
+ iov[3].iov_base = payload_read + 25 * 512;
+ iov[3].iov_len = 39 * 512;
+ spdk_blob_io_readv(blob, channel, iov, 4, 0, 64, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ CU_ASSERT(memcmp(payload_read + 0 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 1 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 2 * 512, payload_ff, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 3 * 512, payload_00, 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 4 * 512, payload_aa, 8 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + 28 * 512, payload_ff, 4 * 512) == 0);
+
+ CU_ASSERT(memcmp(payload_read + (32 + 0) * 512, payload_ff, 4 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + (32 + 4) * 512, payload_00, 8 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + (32 + 12) * 512, payload_ff, 2 * 512) == 0);
+ CU_ASSERT(memcmp(payload_read + (32 + 14) * 512, payload_00, 18 * 512) == 0);
+}
+
+static void
+blob_io_unit(void)
+{
+ struct spdk_bs_opts bsopts;
+ struct spdk_blob_opts opts;
+ struct spdk_bs_dev *dev;
+ struct spdk_blob *blob, *snapshot, *clone;
+ spdk_blob_id blobid;
+ struct spdk_io_channel *channel;
+
+ /* Create dev with 512 bytes io unit size */
+
+ spdk_bs_opts_init(&bsopts);
+ bsopts.cluster_sz = SPDK_BS_PAGE_SIZE * 4; // 8 * 4 = 32 io_unit
+ snprintf(bsopts.bstype.bstype, sizeof(bsopts.bstype.bstype), "TESTTYPE");
+
+ /* Try to initialize a new blob store with unsupported io_unit */
+ dev = init_dev();
+ dev->blocklen = 512;
+ dev->blockcnt = DEV_BUFFER_SIZE / dev->blocklen;
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &bsopts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ CU_ASSERT(spdk_bs_get_io_unit_size(g_bs) == 512);
+ channel = spdk_bs_alloc_io_channel(g_bs);
+
+ /* Create thick provisioned blob */
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = false;
+ opts.num_clusters = 32;
+
+ spdk_bs_create_blob_ext(g_bs, &opts, blob_op_with_id_complete, NULL);
+
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ test_io_write(dev, blob, channel);
+ test_io_read(dev, blob, channel);
+ test_io_zeroes(dev, blob, channel);
+
+ test_iov_write(dev, blob, channel);
+ test_iov_read(dev, blob, channel);
+
+ test_io_unmap(dev, blob, channel);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob = NULL;
+ g_blob = NULL;
+
+ /* Create thin provisioned blob */
+
+ spdk_blob_opts_init(&opts);
+ opts.thin_provision = true;
+ opts.num_clusters = 32;
+
+ spdk_bs_create_blob_ext(g_bs, &opts, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ blob = g_blob;
+
+ test_io_write(dev, blob, channel);
+ test_io_read(dev, blob, channel);
+
+ test_io_zeroes(dev, blob, channel);
+
+ test_iov_write(dev, blob, channel);
+ test_iov_read(dev, blob, channel);
+
+ /* Create snapshot */
+
+ spdk_bs_create_snapshot(g_bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ snapshot = g_blob;
+
+ spdk_bs_create_clone(g_bs, blobid, NULL, blob_op_with_id_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
+ blobid = g_blobid;
+
+ spdk_bs_open_blob(g_bs, blobid, blob_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ CU_ASSERT(g_blob != NULL);
+ clone = g_blob;
+
+ test_io_read(dev, blob, channel);
+ test_io_read(dev, snapshot, channel);
+ test_io_read(dev, clone, channel);
+
+ test_iov_read(dev, blob, channel);
+ test_iov_read(dev, snapshot, channel);
+ test_iov_read(dev, clone, channel);
+
+ /* Inflate clone */
+
+ spdk_bs_inflate_blob(g_bs, channel, blobid, blob_op_complete, NULL);
+
+ CU_ASSERT(g_bserrno == 0);
+
+ test_io_read(dev, clone, channel);
+
+ test_io_unmap(dev, clone, channel);
+
+ test_iov_write(dev, clone, channel);
+ test_iov_read(dev, clone, channel);
+
+ spdk_blob_close(blob, blob_op_complete, NULL);
+ spdk_blob_close(snapshot, blob_op_complete, NULL);
+ spdk_blob_close(clone, blob_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ blob = NULL;
+ g_blob = NULL;
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+}
+
+static void
+blob_io_unit_compatiblity(void)
+{
+ struct spdk_bs_opts bsopts;
+ struct spdk_bs_dev *dev;
+ struct spdk_bs_super_block *super;
+
+ /* Create dev with 512 bytes io unit size */
+
+ spdk_bs_opts_init(&bsopts);
+ bsopts.cluster_sz = SPDK_BS_PAGE_SIZE * 4; // 8 * 4 = 32 io_unit
+ snprintf(bsopts.bstype.bstype, sizeof(bsopts.bstype.bstype), "TESTTYPE");
+
+ /* Try to initialize a new blob store with unsupported io_unit */
+ dev = init_dev();
+ dev->blocklen = 512;
+ dev->blockcnt = DEV_BUFFER_SIZE / dev->blocklen;
+
+ /* Initialize a new blob store */
+ spdk_bs_init(dev, &bsopts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ CU_ASSERT(spdk_bs_get_io_unit_size(g_bs) == 512);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ /* Modify super block to behave like older version.
+ * Check if loaded io unit size equals SPDK_BS_PAGE_SIZE */
+ super = (struct spdk_bs_super_block *)&g_dev_buffer[0];
+ super->io_unit_size = 0;
+ super->crc = _spdk_blob_md_page_calc_crc(super);
+
+ dev = init_dev();
+ dev->blocklen = 512;
+ dev->blockcnt = DEV_BUFFER_SIZE / dev->blocklen;
+
+ spdk_bs_load(dev, &bsopts, bs_op_with_handle_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_bs != NULL);
+
+ CU_ASSERT(spdk_bs_get_io_unit_size(g_bs) == SPDK_BS_PAGE_SIZE);
+
+ /* Unload the blob store */
+ spdk_bs_unload(g_bs, bs_op_complete, NULL);
+ CU_ASSERT(g_bserrno == 0);
+
+ g_bs = NULL;
+ g_blob = NULL;
+ g_blobid = 0;
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("blob", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "blob_init", blob_init) == NULL ||
+ CU_add_test(suite, "blob_open", blob_open) == NULL ||
+ CU_add_test(suite, "blob_create", blob_create) == NULL ||
+ CU_add_test(suite, "blob_create_internal", blob_create_internal) == NULL ||
+ CU_add_test(suite, "blob_thin_provision", blob_thin_provision) == NULL ||
+ CU_add_test(suite, "blob_snapshot", blob_snapshot) == NULL ||
+ CU_add_test(suite, "blob_clone", blob_clone) == NULL ||
+ CU_add_test(suite, "blob_inflate", blob_inflate) == NULL ||
+ CU_add_test(suite, "blob_delete", blob_delete) == NULL ||
+ CU_add_test(suite, "blob_resize", blob_resize) == NULL ||
+ CU_add_test(suite, "blob_read_only", blob_read_only) == NULL ||
+ CU_add_test(suite, "channel_ops", channel_ops) == NULL ||
+ CU_add_test(suite, "blob_super", blob_super) == NULL ||
+ CU_add_test(suite, "blob_write", blob_write) == NULL ||
+ CU_add_test(suite, "blob_read", blob_read) == NULL ||
+ CU_add_test(suite, "blob_rw_verify", blob_rw_verify) == NULL ||
+ CU_add_test(suite, "blob_rw_verify_iov", blob_rw_verify_iov) == NULL ||
+ CU_add_test(suite, "blob_rw_verify_iov_nomem", blob_rw_verify_iov_nomem) == NULL ||
+ CU_add_test(suite, "blob_rw_iov_read_only", blob_rw_iov_read_only) == NULL ||
+ CU_add_test(suite, "blob_unmap", blob_unmap) == NULL ||
+ CU_add_test(suite, "blob_iter", blob_iter) == NULL ||
+ CU_add_test(suite, "blob_xattr", blob_xattr) == NULL ||
+ CU_add_test(suite, "bs_load", bs_load) == NULL ||
+ CU_add_test(suite, "bs_load_custom_cluster_size", bs_load_custom_cluster_size) == NULL ||
+ CU_add_test(suite, "bs_unload", bs_unload) == NULL ||
+ CU_add_test(suite, "bs_cluster_sz", bs_cluster_sz) == NULL ||
+ CU_add_test(suite, "bs_usable_clusters", bs_usable_clusters) == NULL ||
+ CU_add_test(suite, "bs_resize_md", bs_resize_md) == NULL ||
+ CU_add_test(suite, "bs_destroy", bs_destroy) == NULL ||
+ CU_add_test(suite, "bs_type", bs_type) == NULL ||
+ CU_add_test(suite, "bs_super_block", bs_super_block) == NULL ||
+ CU_add_test(suite, "blob_serialize", blob_serialize) == NULL ||
+ CU_add_test(suite, "blob_crc", blob_crc) == NULL ||
+ CU_add_test(suite, "super_block_crc", super_block_crc) == NULL ||
+ CU_add_test(suite, "blob_dirty_shutdown", blob_dirty_shutdown) == NULL ||
+ CU_add_test(suite, "blob_flags", blob_flags) == NULL ||
+ CU_add_test(suite, "bs_version", bs_version) == NULL ||
+ CU_add_test(suite, "blob_set_xattrs", blob_set_xattrs) == NULL ||
+ CU_add_test(suite, "blob_thin_prov_alloc", blob_thin_prov_alloc) == NULL ||
+ CU_add_test(suite, "blob_insert_cluster_msg", blob_insert_cluster_msg) == NULL ||
+ CU_add_test(suite, "blob_thin_prov_rw", blob_thin_prov_rw) == NULL ||
+ CU_add_test(suite, "blob_thin_prov_rw_iov", blob_thin_prov_rw_iov) == NULL ||
+ CU_add_test(suite, "bs_load_iter", bs_load_iter) == NULL ||
+ CU_add_test(suite, "blob_snapshot_rw", blob_snapshot_rw) == NULL ||
+ CU_add_test(suite, "blob_snapshot_rw_iov", blob_snapshot_rw_iov) == NULL ||
+ CU_add_test(suite, "blob_relations", blob_relations) == NULL ||
+ CU_add_test(suite, "blob_inflate_rw", blob_inflate_rw) == NULL ||
+ CU_add_test(suite, "blob_snapshot_freeze_io", blob_snapshot_freeze_io) == NULL ||
+ CU_add_test(suite, "blob_operation_split_rw", blob_operation_split_rw) == NULL ||
+ CU_add_test(suite, "blob_operation_split_rw_iov", blob_operation_split_rw_iov) == NULL ||
+ CU_add_test(suite, "blob_io_unit", blob_io_unit) == NULL ||
+ CU_add_test(suite, "blob_io_unit_compatiblity", blob_io_unit_compatiblity) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ g_dev_buffer = calloc(1, DEV_BUFFER_SIZE);
+ spdk_allocate_thread(_bs_send_msg, NULL, NULL, NULL, "thread0");
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ spdk_free_thread();
+ free(g_dev_buffer);
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/blob/bs_dev_common.c b/src/spdk/test/unit/lib/blob/bs_dev_common.c
new file mode 100644
index 00000000..fe310526
--- /dev/null
+++ b/src/spdk/test/unit/lib/blob/bs_dev_common.c
@@ -0,0 +1,225 @@
+/*-
+ * 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/thread.h"
+#include "bs_scheduler.c"
+
+
+#define DEV_BUFFER_SIZE (64 * 1024 * 1024)
+#define DEV_BUFFER_BLOCKLEN (4096)
+#define DEV_BUFFER_BLOCKCNT (DEV_BUFFER_SIZE / DEV_BUFFER_BLOCKLEN)
+uint8_t *g_dev_buffer;
+uint64_t g_dev_write_bytes;
+uint64_t g_dev_read_bytes;
+
+/* Define here for UT only. */
+struct spdk_io_channel g_io_channel;
+
+static struct spdk_io_channel *
+dev_create_channel(struct spdk_bs_dev *dev)
+{
+ return &g_io_channel;
+}
+
+static void
+dev_destroy_channel(struct spdk_bs_dev *dev, struct spdk_io_channel *channel)
+{
+}
+
+static void
+dev_destroy(struct spdk_bs_dev *dev)
+{
+ free(dev);
+}
+
+
+static void
+dev_complete_cb(void *arg)
+{
+ struct spdk_bs_dev_cb_args *cb_args = arg;
+
+ cb_args->cb_fn(cb_args->channel, cb_args->cb_arg, 0);
+}
+
+static void
+dev_complete(void *arg)
+{
+ _bs_send_msg(dev_complete_cb, arg, NULL);
+}
+
+static void
+dev_read(struct spdk_bs_dev *dev, struct spdk_io_channel *channel, void *payload,
+ uint64_t lba, uint32_t lba_count,
+ struct spdk_bs_dev_cb_args *cb_args)
+{
+ uint64_t offset, length;
+
+ offset = lba * dev->blocklen;
+ length = lba_count * dev->blocklen;
+ SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE);
+ memcpy(payload, &g_dev_buffer[offset], length);
+ g_dev_read_bytes += length;
+ spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args);
+}
+
+static void
+dev_write(struct spdk_bs_dev *dev, struct spdk_io_channel *channel, void *payload,
+ uint64_t lba, uint32_t lba_count,
+ struct spdk_bs_dev_cb_args *cb_args)
+{
+ uint64_t offset, length;
+
+ offset = lba * dev->blocklen;
+ length = lba_count * dev->blocklen;
+ SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE);
+ memcpy(&g_dev_buffer[offset], payload, length);
+ g_dev_write_bytes += length;
+ spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args);
+}
+
+static void
+__check_iov(struct iovec *iov, int iovcnt, uint64_t length)
+{
+ int i;
+
+ for (i = 0; i < iovcnt; i++) {
+ length -= iov[i].iov_len;
+ }
+
+ CU_ASSERT(length == 0);
+}
+
+static void
+dev_readv(struct spdk_bs_dev *dev, struct spdk_io_channel *channel,
+ struct iovec *iov, int iovcnt,
+ uint64_t lba, uint32_t lba_count,
+ struct spdk_bs_dev_cb_args *cb_args)
+{
+ uint64_t offset, length;
+ int i;
+
+ offset = lba * dev->blocklen;
+ length = lba_count * dev->blocklen;
+ SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE);
+ __check_iov(iov, iovcnt, length);
+
+ for (i = 0; i < iovcnt; i++) {
+ memcpy(iov[i].iov_base, &g_dev_buffer[offset], iov[i].iov_len);
+ offset += iov[i].iov_len;
+ }
+
+ g_dev_read_bytes += length;
+ spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args);
+}
+
+static void
+dev_writev(struct spdk_bs_dev *dev, struct spdk_io_channel *channel,
+ struct iovec *iov, int iovcnt,
+ uint64_t lba, uint32_t lba_count,
+ struct spdk_bs_dev_cb_args *cb_args)
+{
+ uint64_t offset, length;
+ int i;
+
+ offset = lba * dev->blocklen;
+ length = lba_count * dev->blocklen;
+ SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE);
+ __check_iov(iov, iovcnt, length);
+
+ for (i = 0; i < iovcnt; i++) {
+ memcpy(&g_dev_buffer[offset], iov[i].iov_base, iov[i].iov_len);
+ offset += iov[i].iov_len;
+ }
+
+ g_dev_write_bytes += length;
+ spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args);
+}
+
+static void
+dev_flush(struct spdk_bs_dev *dev, struct spdk_io_channel *channel,
+ struct spdk_bs_dev_cb_args *cb_args)
+{
+ spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args);
+}
+
+static void
+dev_unmap(struct spdk_bs_dev *dev, struct spdk_io_channel *channel,
+ uint64_t lba, uint32_t lba_count,
+ struct spdk_bs_dev_cb_args *cb_args)
+{
+ uint64_t offset, length;
+
+ offset = lba * dev->blocklen;
+ length = lba_count * dev->blocklen;
+ SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE);
+ memset(&g_dev_buffer[offset], 0, length);
+ spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args);
+}
+
+static void
+dev_write_zeroes(struct spdk_bs_dev *dev, struct spdk_io_channel *channel,
+ uint64_t lba, uint32_t lba_count,
+ struct spdk_bs_dev_cb_args *cb_args)
+{
+ uint64_t offset, length;
+
+ offset = lba * dev->blocklen;
+ length = lba_count * dev->blocklen;
+ SPDK_CU_ASSERT_FATAL(offset + length <= DEV_BUFFER_SIZE);
+ memset(&g_dev_buffer[offset], 0, length);
+ g_dev_write_bytes += length;
+ spdk_thread_send_msg(spdk_get_thread(), dev_complete, cb_args);
+}
+
+static struct spdk_bs_dev *
+init_dev(void)
+{
+ struct spdk_bs_dev *dev = calloc(1, sizeof(*dev));
+
+ SPDK_CU_ASSERT_FATAL(dev != NULL);
+
+ dev->create_channel = dev_create_channel;
+ dev->destroy_channel = dev_destroy_channel;
+ dev->destroy = dev_destroy;
+ dev->read = dev_read;
+ dev->write = dev_write;
+ dev->readv = dev_readv;
+ dev->writev = dev_writev;
+ dev->flush = dev_flush;
+ dev->unmap = dev_unmap;
+ dev->write_zeroes = dev_write_zeroes;
+ dev->blockcnt = DEV_BUFFER_BLOCKCNT;
+ dev->blocklen = DEV_BUFFER_BLOCKLEN;
+
+ return dev;
+}
diff --git a/src/spdk/test/unit/lib/blob/bs_scheduler.c b/src/spdk/test/unit/lib/blob/bs_scheduler.c
new file mode 100644
index 00000000..76fa067e
--- /dev/null
+++ b/src/spdk/test/unit/lib/blob/bs_scheduler.c
@@ -0,0 +1,87 @@
+/*-
+ * 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.
+ */
+
+bool g_scheduler_delay = false;
+
+struct scheduled_ops {
+ spdk_thread_fn fn;
+ void *ctx;
+
+ TAILQ_ENTRY(scheduled_ops) ops_queue;
+};
+
+static TAILQ_HEAD(, scheduled_ops) g_scheduled_ops = TAILQ_HEAD_INITIALIZER(g_scheduled_ops);
+
+void _bs_flush_scheduler(uint32_t);
+
+static void
+_bs_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx)
+{
+ if (g_scheduler_delay) {
+ struct scheduled_ops *ops = calloc(1, sizeof(*ops));
+
+ SPDK_CU_ASSERT_FATAL(ops != NULL);
+ ops->fn = fn;
+ ops->ctx = ctx;
+ TAILQ_INSERT_TAIL(&g_scheduled_ops, ops, ops_queue);
+
+ } else {
+ fn(ctx);
+ }
+}
+
+static void
+_bs_flush_scheduler_single(void)
+{
+ struct scheduled_ops *op;
+ TAILQ_HEAD(, scheduled_ops) ops;
+ TAILQ_INIT(&ops);
+
+ TAILQ_SWAP(&g_scheduled_ops, &ops, scheduled_ops, ops_queue);
+
+ while (!TAILQ_EMPTY(&ops)) {
+ op = TAILQ_FIRST(&ops);
+ TAILQ_REMOVE(&ops, op, ops_queue);
+
+ op->fn(op->ctx);
+ free(op);
+ }
+}
+
+void
+_bs_flush_scheduler(uint32_t n)
+{
+ while (n--) {
+ _bs_flush_scheduler_single();
+ }
+}
diff --git a/src/spdk/test/unit/lib/blobfs/Makefile b/src/spdk/test/unit/lib/blobfs/Makefile
new file mode 100644
index 00000000..dfb98f23
--- /dev/null
+++ b/src/spdk/test/unit/lib/blobfs/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 = tree.c blobfs_async_ut blobfs_sync_ut
+
+.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/unit/lib/blobfs/blobfs_async_ut/.gitignore b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/.gitignore
new file mode 100644
index 00000000..aea3b021
--- /dev/null
+++ b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/.gitignore
@@ -0,0 +1 @@
+blobfs_async_ut
diff --git a/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/Makefile b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/Makefile
new file mode 100644
index 00000000..e6dd0ce2
--- /dev/null
+++ b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/Makefile
@@ -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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+SPDK_LIB_LIST = blob
+TEST_FILE = blobfs_async_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/blobfs_async_ut.c b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/blobfs_async_ut.c
new file mode 100644
index 00000000..baf4bc7f
--- /dev/null
+++ b/src/spdk/test/unit/lib/blobfs/blobfs_async_ut/blobfs_async_ut.c
@@ -0,0 +1,522 @@
+/*-
+ * 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 "CUnit/Basic.h"
+
+#include "common/lib/test_env.c"
+
+#include "spdk_cunit.h"
+#include "blobfs/blobfs.c"
+#include "blobfs/tree.c"
+
+#include "unit/lib/blob/bs_dev_common.c"
+
+struct spdk_filesystem *g_fs;
+struct spdk_file *g_file;
+int g_fserrno;
+
+/* Return NULL to test hardcoded defaults. */
+struct spdk_conf_section *
+spdk_conf_find_section(struct spdk_conf *cp, const char *name)
+{
+ return NULL;
+}
+
+/* Return -1 to test hardcoded defaults. */
+int
+spdk_conf_section_get_intval(struct spdk_conf_section *sp, const char *key)
+{
+ return -1;
+}
+
+static void
+_fs_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx)
+{
+ fn(ctx);
+}
+
+static void
+fs_op_complete(void *ctx, int fserrno)
+{
+ g_fserrno = fserrno;
+}
+
+static void
+fs_op_with_handle_complete(void *ctx, struct spdk_filesystem *fs, int fserrno)
+{
+ g_fs = fs;
+ g_fserrno = fserrno;
+}
+
+static void
+fs_init(void)
+{
+ struct spdk_filesystem *fs;
+ struct spdk_bs_dev *dev;
+
+ dev = init_dev();
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+
+ spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(g_fs != NULL);
+ CU_ASSERT(g_fserrno == 0);
+ fs = g_fs;
+
+ g_fserrno = 1;
+ spdk_fs_unload(fs, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+
+ spdk_free_thread();
+}
+
+static void
+create_cb(void *ctx, int fserrno)
+{
+ g_fserrno = fserrno;
+}
+
+static void
+open_cb(void *ctx, struct spdk_file *f, int fserrno)
+{
+ g_fserrno = fserrno;
+ g_file = f;
+}
+
+static void
+delete_cb(void *ctx, int fserrno)
+{
+ g_fserrno = fserrno;
+}
+
+static void
+fs_open(void)
+{
+ struct spdk_filesystem *fs;
+ spdk_fs_iter iter;
+ struct spdk_bs_dev *dev;
+ struct spdk_file *file;
+ char name[257] = {'\0'};
+
+ dev = init_dev();
+ memset(name, 'a', sizeof(name) - 1);
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+
+ spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(g_fs != NULL);
+ CU_ASSERT(g_fserrno == 0);
+ fs = g_fs;
+
+ g_fserrno = 0;
+ /* Open should fail, because the file name is too long. */
+ spdk_fs_open_file_async(fs, name, SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL);
+ CU_ASSERT(g_fserrno == -ENAMETOOLONG);
+
+ g_fserrno = 0;
+ spdk_fs_open_file_async(fs, "file1", 0, open_cb, NULL);
+ CU_ASSERT(g_fserrno == -ENOENT);
+
+ g_file = NULL;
+ g_fserrno = 1;
+ spdk_fs_open_file_async(fs, "file1", SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_file != NULL);
+ CU_ASSERT(!strcmp("file1", g_file->name));
+ CU_ASSERT(g_file->ref_count == 1);
+
+ iter = spdk_fs_iter_first(fs);
+ CU_ASSERT(iter != NULL);
+ file = spdk_fs_iter_get_file(iter);
+ SPDK_CU_ASSERT_FATAL(file != NULL);
+ CU_ASSERT(!strcmp("file1", file->name));
+ iter = spdk_fs_iter_next(iter);
+ CU_ASSERT(iter == NULL);
+
+ g_fserrno = 0;
+ /* Delete should successful, we will mark the file as deleted. */
+ spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ CU_ASSERT(!TAILQ_EMPTY(&fs->files));
+
+ g_fserrno = 1;
+ spdk_file_close_async(g_file, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ CU_ASSERT(TAILQ_EMPTY(&fs->files));
+
+ g_fserrno = 1;
+ spdk_fs_unload(fs, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+
+ spdk_free_thread();
+}
+
+static void
+fs_create(void)
+{
+ struct spdk_filesystem *fs;
+ struct spdk_bs_dev *dev;
+ char name[257] = {'\0'};
+
+ dev = init_dev();
+ memset(name, 'a', sizeof(name) - 1);
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+
+ spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(g_fs != NULL);
+ CU_ASSERT(g_fserrno == 0);
+ fs = g_fs;
+
+ g_fserrno = 0;
+ /* Create should fail, because the file name is too long. */
+ spdk_fs_create_file_async(fs, name, create_cb, NULL);
+ CU_ASSERT(g_fserrno == -ENAMETOOLONG);
+
+ g_fserrno = 1;
+ spdk_fs_create_file_async(fs, "file1", create_cb, NULL);
+ CU_ASSERT(g_fserrno == 0);
+
+ g_fserrno = 1;
+ spdk_fs_create_file_async(fs, "file1", create_cb, NULL);
+ CU_ASSERT(g_fserrno == -EEXIST);
+
+ g_fserrno = 1;
+ spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ CU_ASSERT(TAILQ_EMPTY(&fs->files));
+
+ g_fserrno = 1;
+ spdk_fs_unload(fs, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+
+ spdk_free_thread();
+}
+
+static void
+fs_truncate(void)
+{
+ struct spdk_filesystem *fs;
+ struct spdk_bs_dev *dev;
+
+ dev = init_dev();
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+
+ spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(g_fs != NULL);
+ CU_ASSERT(g_fserrno == 0);
+ fs = g_fs;
+
+ g_file = NULL;
+ g_fserrno = 1;
+ spdk_fs_open_file_async(fs, "file1", SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_file != NULL);
+
+ g_fserrno = 1;
+ spdk_file_truncate_async(g_file, 18 * 1024 * 1024 + 1, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ CU_ASSERT(g_file->length == 18 * 1024 * 1024 + 1);
+
+ g_fserrno = 1;
+ spdk_file_truncate_async(g_file, 1, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ CU_ASSERT(g_file->length == 1);
+
+ g_fserrno = 1;
+ spdk_file_truncate_async(g_file, 18 * 1024 * 1024 + 1, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ CU_ASSERT(g_file->length == 18 * 1024 * 1024 + 1);
+
+ g_fserrno = 1;
+ spdk_file_close_async(g_file, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ CU_ASSERT(g_file->ref_count == 0);
+
+ g_fserrno = 1;
+ spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ CU_ASSERT(TAILQ_EMPTY(&fs->files));
+
+ g_fserrno = 1;
+ spdk_fs_unload(fs, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+
+ spdk_free_thread();
+}
+
+static void
+fs_rename(void)
+{
+ struct spdk_filesystem *fs;
+ struct spdk_file *file, *file2;
+ struct spdk_bs_dev *dev;
+
+ dev = init_dev();
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+
+ spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(g_fs != NULL);
+ CU_ASSERT(g_fserrno == 0);
+ fs = g_fs;
+
+ g_fserrno = 1;
+ spdk_fs_create_file_async(fs, "file1", create_cb, NULL);
+ CU_ASSERT(g_fserrno == 0);
+
+ g_file = NULL;
+ g_fserrno = 1;
+ spdk_fs_open_file_async(fs, "file1", 0, open_cb, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_file != NULL);
+ CU_ASSERT(g_file->ref_count == 1);
+
+ file = g_file;
+ g_file = NULL;
+ g_fserrno = 1;
+ spdk_file_close_async(file, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ SPDK_CU_ASSERT_FATAL(file->ref_count == 0);
+
+ g_file = NULL;
+ g_fserrno = 1;
+ spdk_fs_open_file_async(fs, "file2", SPDK_BLOBFS_OPEN_CREATE, open_cb, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_file != NULL);
+ CU_ASSERT(g_file->ref_count == 1);
+
+ file2 = g_file;
+ g_file = NULL;
+ g_fserrno = 1;
+ spdk_file_close_async(file2, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ SPDK_CU_ASSERT_FATAL(file2->ref_count == 0);
+
+ /*
+ * Do a 3-way rename. This should delete the old "file2", then rename
+ * "file1" to "file2".
+ */
+ g_fserrno = 1;
+ spdk_fs_rename_file_async(fs, "file1", "file2", fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ CU_ASSERT(file->ref_count == 0);
+ CU_ASSERT(!strcmp(file->name, "file2"));
+ CU_ASSERT(TAILQ_FIRST(&fs->files) == file);
+ CU_ASSERT(TAILQ_NEXT(file, tailq) == NULL);
+
+ g_fserrno = 0;
+ spdk_fs_delete_file_async(fs, "file1", delete_cb, NULL);
+ CU_ASSERT(g_fserrno == -ENOENT);
+ CU_ASSERT(!TAILQ_EMPTY(&fs->files));
+
+ g_fserrno = 1;
+ spdk_fs_delete_file_async(fs, "file2", delete_cb, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ CU_ASSERT(TAILQ_EMPTY(&fs->files));
+
+ g_fserrno = 1;
+ spdk_fs_unload(fs, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+
+ spdk_free_thread();
+}
+
+static void
+tree_find_buffer_ut(void)
+{
+ struct cache_tree *root;
+ struct cache_tree *level1_0;
+ struct cache_tree *level0_0_0;
+ struct cache_tree *level0_0_12;
+ struct cache_buffer *leaf_0_0_4;
+ struct cache_buffer *leaf_0_12_8;
+ struct cache_buffer *leaf_9_23_15;
+ struct cache_buffer *buffer;
+
+ level1_0 = calloc(1, sizeof(struct cache_tree));
+ SPDK_CU_ASSERT_FATAL(level1_0 != NULL);
+ level0_0_0 = calloc(1, sizeof(struct cache_tree));
+ SPDK_CU_ASSERT_FATAL(level0_0_0 != NULL);
+ level0_0_12 = calloc(1, sizeof(struct cache_tree));
+ SPDK_CU_ASSERT_FATAL(level0_0_12 != NULL);
+ leaf_0_0_4 = calloc(1, sizeof(struct cache_buffer));
+ SPDK_CU_ASSERT_FATAL(leaf_0_0_4 != NULL);
+ leaf_0_12_8 = calloc(1, sizeof(struct cache_buffer));
+ SPDK_CU_ASSERT_FATAL(leaf_0_12_8 != NULL);
+ leaf_9_23_15 = calloc(1, sizeof(struct cache_buffer));
+ SPDK_CU_ASSERT_FATAL(leaf_9_23_15 != NULL);
+
+ level1_0->level = 1;
+ level0_0_0->level = 0;
+ level0_0_12->level = 0;
+
+ leaf_0_0_4->offset = CACHE_BUFFER_SIZE * 4;
+ level0_0_0->u.buffer[4] = leaf_0_0_4;
+ level0_0_0->present_mask |= (1ULL << 4);
+
+ leaf_0_12_8->offset = CACHE_TREE_LEVEL_SIZE(1) * 12 + CACHE_BUFFER_SIZE * 8;
+ level0_0_12->u.buffer[8] = leaf_0_12_8;
+ level0_0_12->present_mask |= (1ULL << 8);
+
+ level1_0->u.tree[0] = level0_0_0;
+ level1_0->present_mask |= (1ULL << 0);
+ level1_0->u.tree[12] = level0_0_12;
+ level1_0->present_mask |= (1ULL << 12);
+
+ buffer = spdk_tree_find_buffer(NULL, 0);
+ CU_ASSERT(buffer == NULL);
+
+ buffer = spdk_tree_find_buffer(level0_0_0, 0);
+ CU_ASSERT(buffer == NULL);
+
+ buffer = spdk_tree_find_buffer(level0_0_0, CACHE_TREE_LEVEL_SIZE(0) + 1);
+ CU_ASSERT(buffer == NULL);
+
+ buffer = spdk_tree_find_buffer(level0_0_0, leaf_0_0_4->offset);
+ CU_ASSERT(buffer == leaf_0_0_4);
+
+ buffer = spdk_tree_find_buffer(level1_0, leaf_0_0_4->offset);
+ CU_ASSERT(buffer == leaf_0_0_4);
+
+ buffer = spdk_tree_find_buffer(level1_0, leaf_0_12_8->offset);
+ CU_ASSERT(buffer == leaf_0_12_8);
+
+ buffer = spdk_tree_find_buffer(level1_0, leaf_0_12_8->offset + CACHE_BUFFER_SIZE - 1);
+ CU_ASSERT(buffer == leaf_0_12_8);
+
+ buffer = spdk_tree_find_buffer(level1_0, leaf_0_12_8->offset - 1);
+ CU_ASSERT(buffer == NULL);
+
+ leaf_9_23_15->offset = CACHE_TREE_LEVEL_SIZE(2) * 9 +
+ CACHE_TREE_LEVEL_SIZE(1) * 23 +
+ CACHE_BUFFER_SIZE * 15;
+ root = spdk_tree_insert_buffer(level1_0, leaf_9_23_15);
+ CU_ASSERT(root != level1_0);
+ buffer = spdk_tree_find_buffer(root, leaf_9_23_15->offset);
+ CU_ASSERT(buffer == leaf_9_23_15);
+ spdk_tree_free_buffers(root);
+ free(root);
+}
+
+static void
+channel_ops(void)
+{
+ struct spdk_filesystem *fs;
+ struct spdk_bs_dev *dev;
+ struct spdk_io_channel *channel;
+
+ dev = init_dev();
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+
+ spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(g_fs != NULL);
+ CU_ASSERT(g_fserrno == 0);
+ fs = g_fs;
+
+ channel = spdk_fs_alloc_io_channel(fs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_fs_free_io_channel(channel);
+
+ g_fserrno = 1;
+ spdk_fs_unload(fs, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ g_fs = NULL;
+
+ spdk_free_thread();
+}
+
+static void
+channel_ops_sync(void)
+{
+ struct spdk_filesystem *fs;
+ struct spdk_bs_dev *dev;
+ struct spdk_io_channel *channel;
+
+ dev = init_dev();
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+
+ spdk_fs_init(dev, NULL, NULL, fs_op_with_handle_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(g_fs != NULL);
+ CU_ASSERT(g_fserrno == 0);
+ fs = g_fs;
+
+ channel = spdk_fs_alloc_io_channel_sync(fs);
+ CU_ASSERT(channel != NULL);
+
+ spdk_fs_free_io_channel(channel);
+
+ g_fserrno = 1;
+ spdk_fs_unload(fs, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ g_fs = NULL;
+
+ spdk_free_thread();
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("blobfs_async_ut", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "fs_init", fs_init) == NULL ||
+ CU_add_test(suite, "fs_open", fs_open) == NULL ||
+ CU_add_test(suite, "fs_create", fs_create) == NULL ||
+ CU_add_test(suite, "fs_truncate", fs_truncate) == NULL ||
+ CU_add_test(suite, "fs_rename", fs_rename) == NULL ||
+ CU_add_test(suite, "tree_find_buffer", tree_find_buffer_ut) == NULL ||
+ CU_add_test(suite, "channel_ops", channel_ops) == NULL ||
+ CU_add_test(suite, "channel_ops_sync", channel_ops_sync) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ g_dev_buffer = calloc(1, DEV_BUFFER_SIZE);
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ free(g_dev_buffer);
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/.gitignore b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/.gitignore
new file mode 100644
index 00000000..93ef643f
--- /dev/null
+++ b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/.gitignore
@@ -0,0 +1 @@
+blobfs_sync_ut
diff --git a/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/Makefile b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/Makefile
new file mode 100644
index 00000000..28f38421
--- /dev/null
+++ b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/Makefile
@@ -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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+SPDK_LIB_LIST = blob
+TEST_FILE = blobfs_sync_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/blobfs_sync_ut.c b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/blobfs_sync_ut.c
new file mode 100644
index 00000000..140d99bd
--- /dev/null
+++ b/src/spdk/test/unit/lib/blobfs/blobfs_sync_ut/blobfs_sync_ut.c
@@ -0,0 +1,410 @@
+/*-
+ * 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/blobfs.h"
+#include "spdk/env.h"
+#include "spdk/log.h"
+#include "spdk/thread.h"
+#include "spdk/barrier.h"
+
+#include "spdk_cunit.h"
+#include "unit/lib/blob/bs_dev_common.c"
+#include "common/lib/test_env.c"
+#include "blobfs/blobfs.c"
+#include "blobfs/tree.c"
+
+struct spdk_filesystem *g_fs;
+struct spdk_file *g_file;
+int g_fserrno;
+
+/* Return NULL to test hardcoded defaults. */
+struct spdk_conf_section *
+spdk_conf_find_section(struct spdk_conf *cp, const char *name)
+{
+ return NULL;
+}
+
+/* Return -1 to test hardcoded defaults. */
+int
+spdk_conf_section_get_intval(struct spdk_conf_section *sp, const char *key)
+{
+ return -1;
+}
+
+static void
+_fs_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx)
+{
+ fn(ctx);
+}
+
+struct ut_request {
+ fs_request_fn fn;
+ void *arg;
+ volatile int done;
+ int from_ut;
+};
+
+static struct ut_request *g_req = NULL;
+static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void
+send_request(fs_request_fn fn, void *arg)
+{
+ struct ut_request *req;
+
+ req = calloc(1, sizeof(*req));
+ assert(req != NULL);
+ req->fn = fn;
+ req->arg = arg;
+ req->done = 0;
+ req->from_ut = 0;
+
+ pthread_mutex_lock(&g_mutex);
+ g_req = req;
+ pthread_mutex_unlock(&g_mutex);
+}
+
+static void
+ut_send_request(fs_request_fn fn, void *arg)
+{
+ struct ut_request req;
+
+
+ req.fn = fn;
+ req.arg = arg;
+ req.done = 0;
+ req.from_ut = 1;
+
+ pthread_mutex_lock(&g_mutex);
+ g_req = &req;
+ pthread_mutex_unlock(&g_mutex);
+
+ while (1) {
+ pthread_mutex_lock(&g_mutex);
+ if (req.done == 1) {
+ pthread_mutex_unlock(&g_mutex);
+ break;
+ }
+ pthread_mutex_unlock(&g_mutex);
+ }
+
+ /*
+ * Make sure the address of the local req variable is not in g_req when we exit this
+ * function to make static analysis tools happy.
+ */
+ g_req = NULL;
+}
+
+static void
+fs_op_complete(void *ctx, int fserrno)
+{
+ g_fserrno = fserrno;
+}
+
+static void
+fs_op_with_handle_complete(void *ctx, struct spdk_filesystem *fs, int fserrno)
+{
+ g_fs = fs;
+ g_fserrno = fserrno;
+}
+
+static void
+_fs_init(void *arg)
+{
+ struct spdk_bs_dev *dev;
+
+ g_fs = NULL;
+ g_fserrno = -1;
+ dev = init_dev();
+ spdk_fs_init(dev, NULL, send_request, fs_op_with_handle_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(g_fs != NULL);
+ CU_ASSERT(g_fserrno == 0);
+}
+
+static void
+_fs_unload(void *arg)
+{
+ g_fserrno = -1;
+ spdk_fs_unload(g_fs, fs_op_complete, NULL);
+ CU_ASSERT(g_fserrno == 0);
+ g_fs = NULL;
+}
+
+static void
+cache_write(void)
+{
+ uint64_t length;
+ int rc;
+ char buf[100];
+ struct spdk_io_channel *channel;
+
+ ut_send_request(_fs_init, NULL);
+
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+ channel = spdk_fs_alloc_io_channel_sync(g_fs);
+
+ rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_file != NULL);
+
+ length = (4 * 1024 * 1024);
+ rc = spdk_file_truncate(g_file, channel, length);
+ CU_ASSERT(rc == 0);
+
+ spdk_file_write(g_file, channel, buf, 0, sizeof(buf));
+
+ CU_ASSERT(spdk_file_get_length(g_file) == length);
+
+ rc = spdk_file_truncate(g_file, channel, sizeof(buf));
+ CU_ASSERT(rc == 0);
+
+ spdk_file_close(g_file, channel);
+ rc = spdk_fs_delete_file(g_fs, channel, "testfile");
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_fs_delete_file(g_fs, channel, "testfile");
+ CU_ASSERT(rc == -ENOENT);
+
+ spdk_fs_free_io_channel(channel);
+ spdk_free_thread();
+
+ ut_send_request(_fs_unload, NULL);
+}
+
+static void
+cache_write_null_buffer(void)
+{
+ uint64_t length;
+ int rc;
+ struct spdk_io_channel *channel;
+
+ ut_send_request(_fs_init, NULL);
+
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+ channel = spdk_fs_alloc_io_channel_sync(g_fs);
+
+ rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_file != NULL);
+
+ length = 0;
+ rc = spdk_file_truncate(g_file, channel, length);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_file_write(g_file, channel, NULL, 0, 0);
+ CU_ASSERT(rc == 0);
+
+ spdk_file_close(g_file, channel);
+ rc = spdk_fs_delete_file(g_fs, channel, "testfile");
+ CU_ASSERT(rc == 0);
+
+ spdk_fs_free_io_channel(channel);
+ spdk_free_thread();
+
+ ut_send_request(_fs_unload, NULL);
+}
+
+static void
+fs_create_sync(void)
+{
+ int rc;
+ struct spdk_io_channel *channel;
+
+ ut_send_request(_fs_init, NULL);
+
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+ channel = spdk_fs_alloc_io_channel_sync(g_fs);
+ CU_ASSERT(channel != NULL);
+
+ rc = spdk_fs_create_file(g_fs, channel, "testfile");
+ CU_ASSERT(rc == 0);
+
+ /* Create should fail, because the file already exists. */
+ rc = spdk_fs_create_file(g_fs, channel, "testfile");
+ CU_ASSERT(rc != 0);
+
+ rc = spdk_fs_delete_file(g_fs, channel, "testfile");
+ CU_ASSERT(rc == 0);
+
+ spdk_fs_free_io_channel(channel);
+ spdk_free_thread();
+
+ ut_send_request(_fs_unload, NULL);
+}
+
+static void
+cache_append_no_cache(void)
+{
+ int rc;
+ char buf[100];
+ struct spdk_io_channel *channel;
+
+ ut_send_request(_fs_init, NULL);
+
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+ channel = spdk_fs_alloc_io_channel_sync(g_fs);
+
+ rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_file != NULL);
+
+ spdk_file_write(g_file, channel, buf, 0 * sizeof(buf), sizeof(buf));
+ CU_ASSERT(spdk_file_get_length(g_file) == 1 * sizeof(buf));
+ spdk_file_write(g_file, channel, buf, 1 * sizeof(buf), sizeof(buf));
+ CU_ASSERT(spdk_file_get_length(g_file) == 2 * sizeof(buf));
+ spdk_file_sync(g_file, channel);
+ cache_free_buffers(g_file);
+ spdk_file_write(g_file, channel, buf, 2 * sizeof(buf), sizeof(buf));
+ CU_ASSERT(spdk_file_get_length(g_file) == 3 * sizeof(buf));
+ spdk_file_write(g_file, channel, buf, 3 * sizeof(buf), sizeof(buf));
+ CU_ASSERT(spdk_file_get_length(g_file) == 4 * sizeof(buf));
+ spdk_file_write(g_file, channel, buf, 4 * sizeof(buf), sizeof(buf));
+ CU_ASSERT(spdk_file_get_length(g_file) == 5 * sizeof(buf));
+
+ spdk_file_close(g_file, channel);
+ rc = spdk_fs_delete_file(g_fs, channel, "testfile");
+ CU_ASSERT(rc == 0);
+
+ spdk_fs_free_io_channel(channel);
+ spdk_free_thread();
+
+ ut_send_request(_fs_unload, NULL);
+}
+
+static void
+fs_delete_file_without_close(void)
+{
+ int rc;
+ struct spdk_io_channel *channel;
+ struct spdk_file *file;
+
+ ut_send_request(_fs_init, NULL);
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+ channel = spdk_fs_alloc_io_channel_sync(g_fs);
+ CU_ASSERT(channel != NULL);
+
+ rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_file != NULL);
+
+ rc = spdk_fs_delete_file(g_fs, channel, "testfile");
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_file->ref_count != 0);
+ CU_ASSERT(g_file->is_deleted == true);
+
+ rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &file);
+ CU_ASSERT(rc != 0);
+
+ spdk_file_close(g_file, channel);
+
+ rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &file);
+ CU_ASSERT(rc != 0);
+
+ spdk_fs_free_io_channel(channel);
+ spdk_free_thread();
+
+ ut_send_request(_fs_unload, NULL);
+
+}
+
+static void
+terminate_spdk_thread(void *arg)
+{
+ spdk_free_thread();
+ pthread_exit(NULL);
+}
+
+static void *
+spdk_thread(void *arg)
+{
+ struct ut_request *req;
+
+ spdk_allocate_thread(_fs_send_msg, NULL, NULL, NULL, "thread0");
+
+ while (1) {
+ pthread_mutex_lock(&g_mutex);
+ if (g_req != NULL) {
+ req = g_req;
+ req->fn(req->arg);
+ req->done = 1;
+ if (!req->from_ut) {
+ free(req);
+ }
+ g_req = NULL;
+ }
+ pthread_mutex_unlock(&g_mutex);
+ }
+
+ return NULL;
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ pthread_t spdk_tid;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("blobfs_sync_ut", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "write", cache_write) == NULL ||
+ CU_add_test(suite, "write_null_buffer", cache_write_null_buffer) == NULL ||
+ CU_add_test(suite, "create_sync", fs_create_sync) == NULL ||
+ CU_add_test(suite, "append_no_cache", cache_append_no_cache) == NULL ||
+ CU_add_test(suite, "delete_file_without_close", fs_delete_file_without_close) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ pthread_create(&spdk_tid, NULL, spdk_thread, NULL);
+ g_dev_buffer = calloc(1, DEV_BUFFER_SIZE);
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ free(g_dev_buffer);
+ send_request(terminate_spdk_thread, NULL);
+ pthread_join(spdk_tid, NULL);
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/blobfs/tree.c/.gitignore b/src/spdk/test/unit/lib/blobfs/tree.c/.gitignore
new file mode 100644
index 00000000..57e77bf7
--- /dev/null
+++ b/src/spdk/test/unit/lib/blobfs/tree.c/.gitignore
@@ -0,0 +1 @@
+tree_ut
diff --git a/src/spdk/test/unit/lib/blobfs/tree.c/Makefile b/src/spdk/test/unit/lib/blobfs/tree.c/Makefile
new file mode 100644
index 00000000..64bc202a
--- /dev/null
+++ b/src/spdk/test/unit/lib/blobfs/tree.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = tree_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/blobfs/tree.c/tree_ut.c b/src/spdk/test/unit/lib/blobfs/tree.c/tree_ut.c
new file mode 100644
index 00000000..c24aaa78
--- /dev/null
+++ b/src/spdk/test/unit/lib/blobfs/tree.c/tree_ut.c
@@ -0,0 +1,159 @@
+/*-
+ * 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_cunit.h"
+
+#include "blobfs/tree.c"
+
+void
+spdk_cache_buffer_free(struct cache_buffer *cache_buffer)
+{
+ free(cache_buffer);
+}
+
+static void
+blobfs_tree_op_test(void)
+{
+ struct cache_tree *tree;
+ struct cache_buffer *buffer[5];
+ struct cache_buffer *tmp_buffer;
+ int i;
+
+ for (i = 0; i < 5; i ++) {
+ buffer[i] = calloc(1, sizeof(struct cache_buffer));
+ SPDK_CU_ASSERT_FATAL(buffer[i]);
+ }
+
+ tree = calloc(1, sizeof(*tree));
+ SPDK_CU_ASSERT_FATAL(tree != NULL);
+
+ /* insert buffer[0] */
+ buffer[0]->offset = 0;
+ tree = spdk_tree_insert_buffer(tree, buffer[0]);
+ SPDK_CU_ASSERT_FATAL(tree != NULL);
+ CU_ASSERT(tree->level == 0);
+ tmp_buffer = spdk_tree_find_buffer(tree, buffer[0]->offset);
+ CU_ASSERT(tmp_buffer == buffer[0]);
+
+ /* insert buffer[1] */
+ buffer[1]->offset = CACHE_BUFFER_SIZE;
+ /* set the bytes_filled equal = bytes_filled with same non zero value, e.g., 32 */
+ buffer[1]->bytes_filled = buffer[1]->bytes_flushed = 32;
+ tree = spdk_tree_insert_buffer(tree, buffer[1]);
+ SPDK_CU_ASSERT_FATAL(tree != NULL);
+ CU_ASSERT(tree->level == 0);
+ tmp_buffer = spdk_tree_find_filled_buffer(tree, buffer[1]->offset);
+ CU_ASSERT(tmp_buffer == buffer[1]);
+
+ /* insert buffer[2] */
+ buffer[2]->offset = (CACHE_TREE_WIDTH - 1) * CACHE_BUFFER_SIZE;
+ tree = spdk_tree_insert_buffer(tree, buffer[2]);
+ SPDK_CU_ASSERT_FATAL(tree != NULL);
+ CU_ASSERT(tree->level == 0);
+ tmp_buffer = spdk_tree_find_buffer(tree, buffer[2]->offset);
+ CU_ASSERT(tmp_buffer == buffer[2]);
+ tmp_buffer = spdk_tree_find_filled_buffer(tree, buffer[2]->offset);
+ CU_ASSERT(tmp_buffer == NULL);
+
+ /* insert buffer[3], set an offset which can not be fit level 0 */
+ buffer[3]->offset = CACHE_TREE_LEVEL_SIZE(1);
+ tree = spdk_tree_insert_buffer(tree, buffer[3]);
+ SPDK_CU_ASSERT_FATAL(tree != NULL);
+ CU_ASSERT(tree->level == 1);
+ tmp_buffer = spdk_tree_find_buffer(tree, buffer[3]->offset);
+ CU_ASSERT(tmp_buffer == buffer[3]);
+
+ /* insert buffer[4], set an offset which can not be fit level 1 */
+ buffer[4]->offset = CACHE_TREE_LEVEL_SIZE(2);
+ tree = spdk_tree_insert_buffer(tree, buffer[4]);
+ SPDK_CU_ASSERT_FATAL(tree != NULL);
+ CU_ASSERT(tree->level == 2);
+ tmp_buffer = spdk_tree_find_buffer(tree, buffer[4]->offset);
+ CU_ASSERT(tmp_buffer == buffer[4]);
+
+ /* delete buffer[0] */
+ spdk_tree_remove_buffer(tree, buffer[0]);
+ /* check whether buffer[0] is still existed or not */
+ tmp_buffer = spdk_tree_find_buffer(tree, 0);
+ CU_ASSERT(tmp_buffer == NULL);
+
+ /* delete buffer[3] */
+ spdk_tree_remove_buffer(tree, buffer[3]);
+ /* check whether buffer[3] is still existed or not */
+ tmp_buffer = spdk_tree_find_buffer(tree, CACHE_TREE_LEVEL_SIZE(1));
+ CU_ASSERT(tmp_buffer == NULL);
+
+ /* free all buffers in the tree */
+ spdk_tree_free_buffers(tree);
+
+ /* check whether buffer[1] is still existed or not */
+ tmp_buffer = spdk_tree_find_buffer(tree, CACHE_BUFFER_SIZE);
+ CU_ASSERT(tmp_buffer == NULL);
+ /* check whether buffer[2] is still existed or not */
+ tmp_buffer = spdk_tree_find_buffer(tree, (CACHE_TREE_WIDTH - 1) * CACHE_BUFFER_SIZE);
+ CU_ASSERT(tmp_buffer == NULL);
+ /* check whether buffer[4] is still existed or not */
+ tmp_buffer = spdk_tree_find_buffer(tree, CACHE_TREE_LEVEL_SIZE(2));
+ CU_ASSERT(tmp_buffer == NULL);
+
+ /* According to spdk_tree_free_buffers, root will not be freed */
+ free(tree);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("tree", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (CU_add_test(suite, "blobfs_tree_op_test", blobfs_tree_op_test) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/event/Makefile b/src/spdk/test/unit/lib/event/Makefile
new file mode 100644
index 00000000..a6629af9
--- /dev/null
+++ b/src/spdk/test/unit/lib/event/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 = subsystem.c app.c
+
+.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/unit/lib/event/app.c/.gitignore b/src/spdk/test/unit/lib/event/app.c/.gitignore
new file mode 100644
index 00000000..123e1673
--- /dev/null
+++ b/src/spdk/test/unit/lib/event/app.c/.gitignore
@@ -0,0 +1 @@
+app_ut
diff --git a/src/spdk/test/unit/lib/event/app.c/Makefile b/src/spdk/test/unit/lib/event/app.c/Makefile
new file mode 100644
index 00000000..cce3ad86
--- /dev/null
+++ b/src/spdk/test/unit/lib/event/app.c/Makefile
@@ -0,0 +1,42 @@
+#
+# 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.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.mock.unittest.mk
+
+SPDK_LIB_LIST = conf trace jsonrpc json
+TEST_FILE = app_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/event/app.c/app_ut.c b/src/spdk/test/unit/lib/event/app.c/app_ut.c
new file mode 100644
index 00000000..7d549261
--- /dev/null
+++ b/src/spdk/test/unit/lib/event/app.c/app_ut.c
@@ -0,0 +1,195 @@
+/*-
+ * 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_cunit.h"
+#include "common/lib/test_env.c"
+#include "event/app.c"
+
+#define test_argc 6
+
+DEFINE_STUB_V(spdk_rpc_initialize, (const char *listen_addr));
+DEFINE_STUB_V(spdk_rpc_finish, (void));
+DEFINE_STUB_V(spdk_event_call, (struct spdk_event *event));
+DEFINE_STUB_V(spdk_reactors_start, (void));
+DEFINE_STUB_V(spdk_reactors_stop, (void *arg1, void *arg2));
+DEFINE_STUB(spdk_reactors_init, int, (unsigned int max_delay_us), 0);
+DEFINE_STUB_V(spdk_reactors_fini, (void));
+DEFINE_STUB(spdk_event_allocate, struct spdk_event *, (uint32_t core, spdk_event_fn fn, void *arg1,
+ void *arg2), NULL);
+DEFINE_STUB(spdk_env_get_current_core, uint32_t, (void), 0);
+DEFINE_STUB(spdk_app_get_core_mask, struct spdk_cpuset *, (void), NULL);
+DEFINE_STUB_V(spdk_subsystem_config, (FILE *fp));
+DEFINE_STUB_V(spdk_subsystem_init, (struct spdk_event *app_start_event));
+DEFINE_STUB_V(spdk_subsystem_fini, (struct spdk_event *app_stop_event));
+DEFINE_STUB(spdk_env_init, int, (const struct spdk_env_opts *opts), 0);
+DEFINE_STUB_V(spdk_env_opts_init, (struct spdk_env_opts *opts));
+DEFINE_STUB(spdk_env_get_core_count, uint32_t, (void), 1);
+DEFINE_STUB_V(spdk_rpc_register_method, (const char *method, spdk_rpc_method_handler func,
+ uint32_t state_mask));
+DEFINE_STUB_V(spdk_rpc_set_state, (uint32_t state));
+
+
+static void
+unittest_usage(void)
+{
+}
+
+static void
+unittest_parse_args(int ch, char *arg)
+{
+}
+
+static void
+test_spdk_app_parse_args(void)
+{
+ spdk_app_parse_args_rvals_t rc;
+ struct spdk_app_opts opts = {};
+ struct option my_options[2] = {};
+ char *valid_argv[test_argc] = {"app_ut",
+ "--wait-for-rpc",
+ "-d",
+ "-p0",
+ "-B",
+ "0000:81:00.0"
+ };
+ char *invalid_argv_BW[test_argc] = {"app_ut",
+ "-B",
+ "0000:81:00.0",
+ "-W",
+ "0000:82:00.0",
+ "-cspdk.conf"
+ };
+ /* currently use -z as our new option */
+ char *argv_added_short_opt[test_argc] = {"app_ut",
+ "-z",
+ "-d",
+ "--wait-for-rpc",
+ "-p0",
+ "-cspdk.conf"
+ };
+ char *argv_added_long_opt[test_argc] = {"app_ut",
+ "-cspdk.conf",
+ "-d",
+ "-r/var/tmp/spdk.sock",
+ "--test-long-opt",
+ "--wait-for-rpc"
+ };
+ char *invalid_argv_missing_option[test_argc] = {"app_ut",
+ "-d",
+ "-p",
+ "--wait-for-rpc",
+ "--silence-noticelog"
+ "-R"
+ };
+
+ /* Test valid arguments. Expected result: PASS */
+ rc = spdk_app_parse_args(test_argc, valid_argv, &opts, "", NULL, unittest_parse_args, NULL);
+ CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_SUCCESS);
+ optind = 1;
+
+ /* Test invalid short option Expected result: FAIL */
+ rc = spdk_app_parse_args(test_argc, argv_added_short_opt, &opts, "", NULL, unittest_parse_args,
+ NULL);
+ CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_FAIL);
+ optind = 1;
+
+ /* Test valid global and local options. Expected result: PASS */
+ rc = spdk_app_parse_args(test_argc, argv_added_short_opt, &opts, "z", NULL, unittest_parse_args,
+ unittest_usage);
+ CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_SUCCESS);
+ optind = 1;
+
+ /* Test invalid long option Expected result: FAIL */
+ rc = spdk_app_parse_args(test_argc, argv_added_long_opt, &opts, "", NULL, unittest_parse_args,
+ NULL);
+ CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_FAIL);
+ optind = 1;
+
+ /* Test valid global and local options. Expected result: PASS */
+ my_options[0].name = "test-long-opt";
+ rc = spdk_app_parse_args(test_argc, argv_added_long_opt, &opts, "", my_options, unittest_parse_args,
+ unittest_usage);
+ CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_SUCCESS);
+ optind = 1;
+
+ /* Test overlapping global and local options. Expected result: FAIL */
+ rc = spdk_app_parse_args(test_argc, valid_argv, &opts, SPDK_APP_GETOPT_STRING, NULL,
+ unittest_parse_args, NULL);
+ CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_FAIL);
+ optind = 1;
+
+ /* Specify -B and -W options at the same time. Expected result: FAIL */
+ rc = spdk_app_parse_args(test_argc, invalid_argv_BW, &opts, "", NULL, unittest_parse_args, NULL);
+ SPDK_CU_ASSERT_FATAL(rc == SPDK_APP_PARSE_ARGS_FAIL);
+ optind = 1;
+
+ /* Omit necessary argument to option */
+ rc = spdk_app_parse_args(test_argc, invalid_argv_missing_option, &opts, "", NULL,
+ unittest_parse_args, NULL);
+ CU_ASSERT_EQUAL(rc, SPDK_APP_PARSE_ARGS_FAIL);
+ optind = 1;
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("app_suite", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test_spdk_app_parse_args",
+ test_spdk_app_parse_args) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/event/subsystem.c/.gitignore b/src/spdk/test/unit/lib/event/subsystem.c/.gitignore
new file mode 100644
index 00000000..76ca0d33
--- /dev/null
+++ b/src/spdk/test/unit/lib/event/subsystem.c/.gitignore
@@ -0,0 +1 @@
+subsystem_ut
diff --git a/src/spdk/test/unit/lib/event/subsystem.c/Makefile b/src/spdk/test/unit/lib/event/subsystem.c/Makefile
new file mode 100644
index 00000000..ef76f4fe
--- /dev/null
+++ b/src/spdk/test/unit/lib/event/subsystem.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = subsystem_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/event/subsystem.c/subsystem_ut.c b/src/spdk/test/unit/lib/event/subsystem.c/subsystem_ut.c
new file mode 100644
index 00000000..8663e0e3
--- /dev/null
+++ b/src/spdk/test/unit/lib/event/subsystem.c/subsystem_ut.c
@@ -0,0 +1,304 @@
+/*-
+ * 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_cunit.h"
+
+#include "unit/lib/json_mock.c"
+#include "event/subsystem.c"
+
+static struct spdk_subsystem g_ut_subsystems[8];
+static struct spdk_subsystem_depend g_ut_subsystem_deps[8];
+static int global_rc;
+
+void
+spdk_app_stop(int rc)
+{
+ global_rc = rc;
+}
+
+uint32_t
+spdk_env_get_current_core(void)
+{
+ return 0;
+}
+
+static void
+ut_event_fn(void *arg1, void *arg2)
+{
+}
+
+struct spdk_event *
+spdk_event_allocate(uint32_t core, spdk_event_fn fn, void *arg1, void *arg2)
+{
+ struct spdk_event *event = calloc(1, sizeof(*event));
+
+ SPDK_CU_ASSERT_FATAL(event != NULL);
+
+ event->fn = fn;
+ event->arg1 = arg1;
+ event->arg2 = arg2;
+
+ return event;
+}
+
+void spdk_event_call(struct spdk_event *event)
+{
+ if (event != NULL) {
+ if (event->fn != NULL) {
+ event->fn(event->arg1, event->arg2);
+ }
+ free(event);
+ }
+}
+
+static void
+set_up_subsystem(struct spdk_subsystem *subsystem, const char *name)
+{
+ subsystem->init = NULL;
+ subsystem->fini = NULL;
+ subsystem->config = NULL;
+ subsystem->name = name;
+}
+
+static void
+set_up_depends(struct spdk_subsystem_depend *depend, const char *subsystem_name,
+ const char *dpends_on_name)
+{
+ depend->name = subsystem_name;
+ depend->depends_on = dpends_on_name;
+}
+
+static void
+subsystem_clear(void)
+{
+ struct spdk_subsystem *subsystem, *subsystem_tmp;
+ struct spdk_subsystem_depend *subsystem_dep, *subsystem_dep_tmp;
+
+ TAILQ_FOREACH_SAFE(subsystem, &g_subsystems, tailq, subsystem_tmp) {
+ TAILQ_REMOVE(&g_subsystems, subsystem, tailq);
+ }
+
+ TAILQ_FOREACH_SAFE(subsystem_dep, &g_subsystems_deps, tailq, subsystem_dep_tmp) {
+ TAILQ_REMOVE(&g_subsystems_deps, subsystem_dep, tailq);
+ }
+}
+
+static void
+subsystem_sort_test_depends_on_single(void)
+{
+ struct spdk_subsystem *subsystem;
+ int i;
+ char subsystem_name[16];
+ struct spdk_event *app_start_event;
+
+ global_rc = -1;
+ app_start_event = spdk_event_allocate(0, ut_event_fn, NULL, NULL);
+ spdk_subsystem_init(app_start_event);
+
+ i = 4;
+ TAILQ_FOREACH(subsystem, &g_subsystems, tailq) {
+ snprintf(subsystem_name, sizeof(subsystem_name), "subsystem%d", i);
+ SPDK_CU_ASSERT_FATAL(i > 0);
+ i--;
+ CU_ASSERT(strcmp(subsystem_name, subsystem->name) == 0);
+ }
+}
+
+static void
+subsystem_sort_test_depends_on_multiple(void)
+{
+ int i;
+ struct spdk_subsystem *subsystem;
+ struct spdk_event *app_start_event;
+
+ subsystem_clear();
+ set_up_subsystem(&g_ut_subsystems[0], "iscsi");
+ set_up_subsystem(&g_ut_subsystems[1], "nvmf");
+ set_up_subsystem(&g_ut_subsystems[2], "sock");
+ set_up_subsystem(&g_ut_subsystems[3], "bdev");
+ set_up_subsystem(&g_ut_subsystems[4], "rpc");
+ set_up_subsystem(&g_ut_subsystems[5], "scsi");
+ set_up_subsystem(&g_ut_subsystems[6], "interface");
+ set_up_subsystem(&g_ut_subsystems[7], "copy");
+
+ for (i = 0; i < 8; i++) {
+ spdk_add_subsystem(&g_ut_subsystems[i]);
+ }
+
+ set_up_depends(&g_ut_subsystem_deps[0], "bdev", "copy");
+ set_up_depends(&g_ut_subsystem_deps[1], "scsi", "bdev");
+ set_up_depends(&g_ut_subsystem_deps[2], "rpc", "interface");
+ set_up_depends(&g_ut_subsystem_deps[3], "sock", "interface");
+ set_up_depends(&g_ut_subsystem_deps[4], "nvmf", "interface");
+ set_up_depends(&g_ut_subsystem_deps[5], "iscsi", "scsi");
+ set_up_depends(&g_ut_subsystem_deps[6], "iscsi", "sock");
+ set_up_depends(&g_ut_subsystem_deps[7], "iscsi", "rpc");
+
+ for (i = 0; i < 8; i++) {
+ spdk_add_subsystem_depend(&g_ut_subsystem_deps[i]);
+ }
+
+ global_rc = -1;
+ app_start_event = spdk_event_allocate(0, ut_event_fn, NULL, NULL);
+ spdk_subsystem_init(app_start_event);
+
+ subsystem = TAILQ_FIRST(&g_subsystems);
+ CU_ASSERT(strcmp(subsystem->name, "interface") == 0);
+ TAILQ_REMOVE(&g_subsystems, subsystem, tailq);
+
+ subsystem = TAILQ_FIRST(&g_subsystems);
+ CU_ASSERT(strcmp(subsystem->name, "copy") == 0);
+ TAILQ_REMOVE(&g_subsystems, subsystem, tailq);
+
+ subsystem = TAILQ_FIRST(&g_subsystems);
+ CU_ASSERT(strcmp(subsystem->name, "nvmf") == 0);
+ TAILQ_REMOVE(&g_subsystems, subsystem, tailq);
+
+ subsystem = TAILQ_FIRST(&g_subsystems);
+ CU_ASSERT(strcmp(subsystem->name, "sock") == 0);
+ TAILQ_REMOVE(&g_subsystems, subsystem, tailq);
+
+ subsystem = TAILQ_FIRST(&g_subsystems);
+ CU_ASSERT(strcmp(subsystem->name, "bdev") == 0);
+ TAILQ_REMOVE(&g_subsystems, subsystem, tailq);
+
+ subsystem = TAILQ_FIRST(&g_subsystems);
+ CU_ASSERT(strcmp(subsystem->name, "rpc") == 0);
+ TAILQ_REMOVE(&g_subsystems, subsystem, tailq);
+
+ subsystem = TAILQ_FIRST(&g_subsystems);
+ CU_ASSERT(strcmp(subsystem->name, "scsi") == 0);
+ TAILQ_REMOVE(&g_subsystems, subsystem, tailq);
+
+ subsystem = TAILQ_FIRST(&g_subsystems);
+ CU_ASSERT(strcmp(subsystem->name, "iscsi") == 0);
+ TAILQ_REMOVE(&g_subsystems, subsystem, tailq);
+}
+
+struct spdk_subsystem subsystem1 = {
+ .name = "subsystem1",
+};
+
+struct spdk_subsystem subsystem2 = {
+ .name = "subsystem2",
+};
+struct spdk_subsystem subsystem3 = {
+ .name = "subsystem3",
+};
+
+struct spdk_subsystem subsystem4 = {
+ .name = "subsystem4",
+};
+
+SPDK_SUBSYSTEM_REGISTER(subsystem1);
+SPDK_SUBSYSTEM_REGISTER(subsystem2);
+SPDK_SUBSYSTEM_REGISTER(subsystem3);
+SPDK_SUBSYSTEM_REGISTER(subsystem4);
+
+SPDK_SUBSYSTEM_DEPEND(subsystem1, subsystem2)
+SPDK_SUBSYSTEM_DEPEND(subsystem2, subsystem3)
+SPDK_SUBSYSTEM_DEPEND(subsystem3, subsystem4)
+
+
+static void
+subsystem_sort_test_missing_dependency(void)
+{
+ /*
+ * A depends on B, but B is missing
+ */
+
+ subsystem_clear();
+ set_up_subsystem(&g_ut_subsystems[0], "A");
+ spdk_add_subsystem(&g_ut_subsystems[0]);
+
+ set_up_depends(&g_ut_subsystem_deps[0], "A", "B");
+ spdk_add_subsystem_depend(&g_ut_subsystem_deps[0]);
+
+ global_rc = -1;
+ spdk_subsystem_init(NULL);
+ CU_ASSERT(global_rc != 0);
+
+ /*
+ * Dependency from C to A is defined, but C is missing
+ */
+
+ subsystem_clear();
+ set_up_subsystem(&g_ut_subsystems[0], "A");
+ spdk_add_subsystem(&g_ut_subsystems[0]);
+
+ set_up_depends(&g_ut_subsystem_deps[0], "C", "A");
+ spdk_add_subsystem_depend(&g_ut_subsystem_deps[0]);
+
+ global_rc = -1;
+ spdk_subsystem_init(NULL);
+ CU_ASSERT(global_rc != 0);
+
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("subsystem_suite", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "subsystem_sort_test_depends_on_single",
+ subsystem_sort_test_depends_on_single) == NULL
+ || CU_add_test(suite, "subsystem_sort_test_depends_on_multiple",
+ subsystem_sort_test_depends_on_multiple) == NULL
+ || CU_add_test(suite, "subsystem_sort_test_missing_dependency",
+ subsystem_sort_test_missing_dependency) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/ioat/Makefile b/src/spdk/test/unit/lib/ioat/Makefile
new file mode 100644
index 00000000..8d982710
--- /dev/null
+++ b/src/spdk/test/unit/lib/ioat/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 = ioat.c
+
+.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/unit/lib/ioat/ioat.c/.gitignore b/src/spdk/test/unit/lib/ioat/ioat.c/.gitignore
new file mode 100644
index 00000000..deefbf0c
--- /dev/null
+++ b/src/spdk/test/unit/lib/ioat/ioat.c/.gitignore
@@ -0,0 +1 @@
+ioat_ut
diff --git a/src/spdk/test/unit/lib/ioat/ioat.c/Makefile b/src/spdk/test/unit/lib/ioat/ioat.c/Makefile
new file mode 100644
index 00000000..c3787c3d
--- /dev/null
+++ b/src/spdk/test/unit/lib/ioat/ioat.c/Makefile
@@ -0,0 +1,39 @@
+#
+# 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
+
+TEST_FILE = ioat_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/ioat/ioat.c/ioat_ut.c b/src/spdk/test/unit/lib/ioat/ioat.c/ioat_ut.c
new file mode 100644
index 00000000..92330e32
--- /dev/null
+++ b/src/spdk/test/unit/lib/ioat/ioat.c/ioat_ut.c
@@ -0,0 +1,153 @@
+/*-
+ * 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_cunit.h"
+
+#include "ioat/ioat.c"
+
+#include "spdk_internal/mock.h"
+
+#include "common/lib/test_env.c"
+
+int
+spdk_pci_ioat_enumerate(spdk_pci_enum_cb enum_cb, void *enum_ctx)
+{
+ return -1;
+}
+
+int
+spdk_pci_device_map_bar(struct spdk_pci_device *dev, uint32_t bar,
+ void **mapped_addr, uint64_t *phys_addr, uint64_t *size)
+{
+ *mapped_addr = NULL;
+ *phys_addr = 0;
+ *size = 0;
+ return 0;
+}
+
+int
+spdk_pci_device_unmap_bar(struct spdk_pci_device *dev, uint32_t bar, void *addr)
+{
+ return 0;
+}
+
+int
+spdk_pci_device_cfg_read32(struct spdk_pci_device *dev, uint32_t *value,
+ uint32_t offset)
+{
+ *value = 0xFFFFFFFFu;
+ return 0;
+}
+
+int
+spdk_pci_device_cfg_write32(struct spdk_pci_device *dev, uint32_t value,
+ uint32_t offset)
+{
+ return 0;
+}
+
+static void ioat_state_check(void)
+{
+ /*
+ * CHANSTS's STATUS field is 3 bits (8 possible values), but only has 5 valid states:
+ * ACTIVE 0x0
+ * IDLE 0x1
+ * SUSPENDED 0x2
+ * HALTED 0x3
+ * ARMED 0x4
+ */
+
+ CU_ASSERT(is_ioat_active(0) == 1); /* ACTIVE */
+ CU_ASSERT(is_ioat_active(1) == 0); /* IDLE */
+ CU_ASSERT(is_ioat_active(2) == 0); /* SUSPENDED */
+ CU_ASSERT(is_ioat_active(3) == 0); /* HALTED */
+ CU_ASSERT(is_ioat_active(4) == 0); /* ARMED */
+ CU_ASSERT(is_ioat_active(5) == 0); /* reserved */
+ CU_ASSERT(is_ioat_active(6) == 0); /* reserved */
+ CU_ASSERT(is_ioat_active(7) == 0); /* reserved */
+
+ CU_ASSERT(is_ioat_idle(0) == 0); /* ACTIVE */
+ CU_ASSERT(is_ioat_idle(1) == 1); /* IDLE */
+ CU_ASSERT(is_ioat_idle(2) == 0); /* SUSPENDED */
+ CU_ASSERT(is_ioat_idle(3) == 0); /* HALTED */
+ CU_ASSERT(is_ioat_idle(4) == 0); /* ARMED */
+ CU_ASSERT(is_ioat_idle(5) == 0); /* reserved */
+ CU_ASSERT(is_ioat_idle(6) == 0); /* reserved */
+ CU_ASSERT(is_ioat_idle(7) == 0); /* reserved */
+
+ CU_ASSERT(is_ioat_suspended(0) == 0); /* ACTIVE */
+ CU_ASSERT(is_ioat_suspended(1) == 0); /* IDLE */
+ CU_ASSERT(is_ioat_suspended(2) == 1); /* SUSPENDED */
+ CU_ASSERT(is_ioat_suspended(3) == 0); /* HALTED */
+ CU_ASSERT(is_ioat_suspended(4) == 0); /* ARMED */
+ CU_ASSERT(is_ioat_suspended(5) == 0); /* reserved */
+ CU_ASSERT(is_ioat_suspended(6) == 0); /* reserved */
+ CU_ASSERT(is_ioat_suspended(7) == 0); /* reserved */
+
+ CU_ASSERT(is_ioat_halted(0) == 0); /* ACTIVE */
+ CU_ASSERT(is_ioat_halted(1) == 0); /* IDLE */
+ CU_ASSERT(is_ioat_halted(2) == 0); /* SUSPENDED */
+ CU_ASSERT(is_ioat_halted(3) == 1); /* HALTED */
+ CU_ASSERT(is_ioat_halted(4) == 0); /* ARMED */
+ CU_ASSERT(is_ioat_halted(5) == 0); /* reserved */
+ CU_ASSERT(is_ioat_halted(6) == 0); /* reserved */
+ CU_ASSERT(is_ioat_halted(7) == 0); /* reserved */
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("ioat", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "ioat_state_check", ioat_state_check) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/iscsi/Makefile b/src/spdk/test/unit/lib/iscsi/Makefile
new file mode 100644
index 00000000..396c5a05
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/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 = conn.c init_grp.c iscsi.c param.c portal_grp.c tgt_node.c
+
+.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/unit/lib/iscsi/common.c b/src/spdk/test/unit/lib/iscsi/common.c
new file mode 100644
index 00000000..9ef4f9ab
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/common.c
@@ -0,0 +1,256 @@
+#include "iscsi/task.h"
+#include "iscsi/iscsi.h"
+#include "iscsi/conn.h"
+#include "iscsi/acceptor.h"
+
+#include "spdk/env.h"
+#include "spdk/event.h"
+#include "spdk/sock.h"
+#include "spdk_cunit.h"
+
+#include "spdk_internal/log.h"
+
+#include "scsi/scsi_internal.h"
+
+SPDK_LOG_REGISTER_COMPONENT("iscsi", SPDK_LOG_ISCSI)
+
+TAILQ_HEAD(, spdk_iscsi_pdu) g_write_pdu_list;
+
+struct spdk_iscsi_task *
+spdk_iscsi_task_get(struct spdk_iscsi_conn *conn,
+ struct spdk_iscsi_task *parent,
+ spdk_scsi_task_cpl cpl_fn)
+{
+ struct spdk_iscsi_task *task;
+
+ task = calloc(1, sizeof(*task));
+
+ return task;
+}
+
+void
+spdk_scsi_task_put(struct spdk_scsi_task *task)
+{
+ free(task);
+}
+
+void
+spdk_put_pdu(struct spdk_iscsi_pdu *pdu)
+{
+ if (!pdu) {
+ return;
+ }
+
+ pdu->ref--;
+ if (pdu->ref < 0) {
+ CU_FAIL("negative ref count");
+ pdu->ref = 0;
+ }
+
+ if (pdu->ref == 0) {
+ if (pdu->data && !pdu->data_from_mempool) {
+ free(pdu->data);
+ }
+ free(pdu);
+ }
+}
+
+struct spdk_iscsi_pdu *
+spdk_get_pdu(void)
+{
+ struct spdk_iscsi_pdu *pdu;
+
+ pdu = malloc(sizeof(*pdu));
+ if (!pdu) {
+ return NULL;
+ }
+
+ memset(pdu, 0, offsetof(struct spdk_iscsi_pdu, ahs));
+ pdu->ref = 1;
+
+ return pdu;
+}
+
+void
+spdk_scsi_task_process_null_lun(struct spdk_scsi_task *task)
+{
+}
+
+void
+spdk_scsi_dev_queue_task(struct spdk_scsi_dev *dev,
+ struct spdk_scsi_task *task)
+{
+}
+
+struct spdk_scsi_port *
+spdk_scsi_dev_find_port_by_id(struct spdk_scsi_dev *dev, uint64_t id)
+{
+ return NULL;
+}
+
+void
+spdk_scsi_dev_queue_mgmt_task(struct spdk_scsi_dev *dev,
+ struct spdk_scsi_task *task,
+ enum spdk_scsi_task_func func)
+{
+}
+
+const char *
+spdk_scsi_dev_get_name(const struct spdk_scsi_dev *dev)
+{
+ if (dev != NULL) {
+ return dev->name;
+ }
+
+ return NULL;
+}
+
+void
+spdk_iscsi_acceptor_start(struct spdk_iscsi_portal *p)
+{
+}
+
+void
+spdk_iscsi_acceptor_stop(struct spdk_iscsi_portal *p)
+{
+}
+
+struct spdk_sock *
+spdk_sock_listen(const char *ip, int port)
+{
+ static int g_sock;
+
+ return (struct spdk_sock *)&g_sock;
+}
+
+int
+spdk_sock_close(struct spdk_sock **sock)
+{
+ *sock = NULL;
+
+ return 0;
+}
+
+static struct spdk_cpuset *g_app_core_mask;
+
+struct spdk_cpuset *
+spdk_app_get_core_mask(void)
+{
+ int i;
+ if (!g_app_core_mask) {
+ g_app_core_mask = spdk_cpuset_alloc();
+ for (i = 0; i < SPDK_CPUSET_SIZE; i++) {
+ spdk_cpuset_set_cpu(g_app_core_mask, i, true);
+ }
+ }
+ return g_app_core_mask;
+}
+
+int
+spdk_app_parse_core_mask(const char *mask, struct spdk_cpuset *cpumask)
+{
+ int rc;
+
+ if (mask == NULL || cpumask == NULL) {
+ return -1;
+ }
+
+ rc = spdk_cpuset_parse(cpumask, mask);
+ if (rc < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+uint32_t
+spdk_env_get_current_core(void)
+{
+ return 0;
+}
+
+struct spdk_event *
+spdk_event_allocate(uint32_t core, spdk_event_fn fn, void *arg1, void *arg2)
+{
+ return NULL;
+}
+
+struct spdk_scsi_dev *
+ spdk_scsi_dev_construct(const char *name, const char **bdev_name_list,
+ int *lun_id_list, int num_luns, uint8_t protocol_id,
+ void (*hotremove_cb)(const struct spdk_scsi_lun *, void *),
+ void *hotremove_ctx)
+{
+ return NULL;
+}
+
+void
+spdk_scsi_dev_destruct(struct spdk_scsi_dev *dev)
+{
+}
+
+int
+spdk_scsi_dev_add_port(struct spdk_scsi_dev *dev, uint64_t id, const char *name)
+{
+ return 0;
+}
+
+int
+spdk_iscsi_drop_conns(struct spdk_iscsi_conn *conn, const char *conn_match,
+ int drop_all)
+{
+ return 0;
+}
+
+int
+spdk_scsi_dev_delete_port(struct spdk_scsi_dev *dev, uint64_t id)
+{
+ return 0;
+}
+
+void
+spdk_shutdown_iscsi_conns(void)
+{
+}
+
+void
+spdk_iscsi_task_cpl(struct spdk_scsi_task *scsi_task)
+{
+
+}
+
+void
+spdk_iscsi_task_mgmt_cpl(struct spdk_scsi_task *scsi_task)
+{
+
+}
+
+int
+spdk_iscsi_conn_read_data(struct spdk_iscsi_conn *conn, int bytes,
+ void *buf)
+{
+ return 0;
+}
+
+void
+spdk_iscsi_conn_write_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu)
+{
+ TAILQ_INSERT_TAIL(&g_write_pdu_list, pdu, tailq);
+}
+
+void
+spdk_iscsi_conn_logout(struct spdk_iscsi_conn *conn)
+{
+}
+
+void
+spdk_scsi_task_set_status(struct spdk_scsi_task *task, int sc, int sk, int asc, int ascq)
+{
+}
+
+void
+spdk_scsi_task_set_data(struct spdk_scsi_task *task, void *data, uint32_t len)
+{
+ SPDK_CU_ASSERT_FATAL(task->iovs != NULL);
+ task->iovs[0].iov_base = data;
+ task->iovs[0].iov_len = len;
+}
diff --git a/src/spdk/test/unit/lib/iscsi/conn.c/.gitignore b/src/spdk/test/unit/lib/iscsi/conn.c/.gitignore
new file mode 100644
index 00000000..3bb0afd8
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/conn.c/.gitignore
@@ -0,0 +1 @@
+conn_ut
diff --git a/src/spdk/test/unit/lib/iscsi/conn.c/Makefile b/src/spdk/test/unit/lib/iscsi/conn.c/Makefile
new file mode 100644
index 00000000..96f2f5d7
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/conn.c/Makefile
@@ -0,0 +1,42 @@
+#
+# 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.app.mk
+
+SPDK_LIB_LIST = trace
+
+TEST_FILE = conn_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/iscsi/conn.c/conn_ut.c b/src/spdk/test/unit/lib/iscsi/conn.c/conn_ut.c
new file mode 100644
index 00000000..88d23423
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/conn.c/conn_ut.c
@@ -0,0 +1,404 @@
+/*-
+ * 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 "common/lib/test_env.c"
+#include "spdk_cunit.h"
+
+#include "iscsi/conn.c"
+
+SPDK_LOG_REGISTER_COMPONENT("iscsi", SPDK_LOG_ISCSI)
+
+#define DMIN32(A,B) ((uint32_t) ((uint32_t)(A) > (uint32_t)(B) ? (uint32_t)(B) : (uint32_t)(A)))
+
+struct spdk_iscsi_globals g_spdk_iscsi;
+static TAILQ_HEAD(, spdk_iscsi_task) g_ut_read_tasks = TAILQ_HEAD_INITIALIZER(g_ut_read_tasks);
+
+int
+spdk_app_get_shm_id(void)
+{
+ return 0;
+}
+
+uint32_t
+spdk_env_get_current_core(void)
+{
+ return 0;
+}
+
+uint32_t
+spdk_env_get_first_core(void)
+{
+ return 0;
+}
+
+uint32_t
+spdk_env_get_last_core(void)
+{
+ return 0;
+}
+
+uint32_t
+spdk_env_get_next_core(uint32_t prev_core)
+{
+ return 0;
+}
+
+struct spdk_event *
+spdk_event_allocate(uint32_t lcore, spdk_event_fn fn, void *arg1, void *arg2)
+{
+ return NULL;
+}
+
+void
+spdk_event_call(struct spdk_event *event)
+{
+}
+
+int
+spdk_sock_getaddr(struct spdk_sock *sock, char *saddr, int slen, uint16_t *sport,
+ char *caddr, int clen, uint16_t *cport)
+{
+ return 0;
+}
+
+int
+spdk_sock_close(struct spdk_sock **sock)
+{
+ *sock = NULL;
+ return 0;
+}
+
+ssize_t
+spdk_sock_recv(struct spdk_sock *sock, void *buf, size_t len)
+{
+ return 0;
+}
+
+ssize_t
+spdk_sock_writev(struct spdk_sock *sock, struct iovec *iov, int iovcnt)
+{
+ return 0;
+}
+
+int
+spdk_sock_set_recvlowat(struct spdk_sock *s, int nbytes)
+{
+ return 0;
+}
+
+int
+spdk_sock_set_recvbuf(struct spdk_sock *sock, int sz)
+{
+ return 0;
+}
+
+int
+spdk_sock_set_sendbuf(struct spdk_sock *sock, int sz)
+{
+ return 0;
+}
+
+int
+spdk_sock_group_add_sock(struct spdk_sock_group *group, struct spdk_sock *sock,
+ spdk_sock_cb cb_fn, void *cb_arg)
+{
+ return 0;
+}
+
+int
+spdk_sock_group_remove_sock(struct spdk_sock_group *group, struct spdk_sock *sock)
+{
+ return 0;
+}
+
+void
+spdk_scsi_task_put(struct spdk_scsi_task *task)
+{
+}
+
+struct spdk_scsi_lun *
+spdk_scsi_dev_get_lun(struct spdk_scsi_dev *dev, int lun_id)
+{
+ return NULL;
+}
+
+bool
+spdk_scsi_dev_has_pending_tasks(const struct spdk_scsi_dev *dev)
+{
+ return true;
+}
+
+int
+spdk_scsi_lun_open(struct spdk_scsi_lun *lun, spdk_scsi_remove_cb_t hotremove_cb,
+ void *hotremove_ctx, struct spdk_scsi_desc **desc)
+{
+ return 0;
+}
+
+void
+spdk_scsi_lun_close(struct spdk_scsi_desc *desc)
+{
+}
+
+int spdk_scsi_lun_allocate_io_channel(struct spdk_scsi_desc *desc)
+{
+ return 0;
+}
+
+void spdk_scsi_lun_free_io_channel(struct spdk_scsi_desc *desc)
+{
+}
+
+int
+spdk_scsi_lun_get_id(const struct spdk_scsi_lun *lun)
+{
+ return 0;
+}
+
+const char *
+spdk_scsi_port_get_name(const struct spdk_scsi_port *port)
+{
+ return NULL;
+}
+
+void
+spdk_scsi_task_copy_status(struct spdk_scsi_task *dst,
+ struct spdk_scsi_task *src)
+{
+}
+
+void
+spdk_put_pdu(struct spdk_iscsi_pdu *pdu)
+{
+}
+
+void
+spdk_iscsi_param_free(struct iscsi_param *params)
+{
+}
+
+int
+spdk_iscsi_conn_params_init(struct iscsi_param **params)
+{
+ return 0;
+}
+
+void spdk_clear_all_transfer_task(struct spdk_iscsi_conn *conn,
+ struct spdk_scsi_lun *lun)
+{
+}
+
+int
+spdk_iscsi_build_iovecs(struct spdk_iscsi_conn *conn, struct iovec *iovec,
+ struct spdk_iscsi_pdu *pdu)
+{
+ return 0;
+}
+
+bool spdk_iscsi_is_deferred_free_pdu(struct spdk_iscsi_pdu *pdu)
+{
+ return false;
+}
+
+void spdk_iscsi_task_response(struct spdk_iscsi_conn *conn,
+ struct spdk_iscsi_task *task)
+{
+}
+
+void
+spdk_iscsi_task_mgmt_response(struct spdk_iscsi_conn *conn,
+ struct spdk_iscsi_task *task)
+{
+}
+
+void spdk_iscsi_send_nopin(struct spdk_iscsi_conn *conn)
+{
+}
+
+int
+spdk_iscsi_execute(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu)
+{
+ return 0;
+}
+
+void spdk_del_transfer_task(struct spdk_iscsi_conn *conn, uint32_t task_tag)
+{
+}
+
+int spdk_iscsi_conn_handle_queued_datain_tasks(struct spdk_iscsi_conn *conn)
+{
+ return 0;
+}
+
+int
+spdk_iscsi_read_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu **_pdu)
+{
+ return 0;
+}
+
+void spdk_free_sess(struct spdk_iscsi_sess *sess)
+{
+}
+
+int
+spdk_iscsi_tgt_node_cleanup_luns(struct spdk_iscsi_conn *conn,
+ struct spdk_iscsi_tgt_node *target)
+{
+ return 0;
+}
+
+void
+spdk_shutdown_iscsi_conns_done(void)
+{
+}
+
+static struct spdk_iscsi_task *
+ut_conn_task_get(struct spdk_iscsi_task *parent)
+{
+ struct spdk_iscsi_task *task;
+
+ task = calloc(1, sizeof(*task));
+ SPDK_CU_ASSERT_FATAL(task != NULL);
+
+ if (parent) {
+ task->parent = parent;
+ }
+ return task;
+}
+
+static void
+ut_conn_create_read_tasks(int transfer_len)
+{
+ struct spdk_iscsi_task *task, *subtask;
+ int32_t remaining_size = 0;
+
+ task = ut_conn_task_get(NULL);
+
+ task->scsi.transfer_len = transfer_len;
+ task->scsi.offset = 0;
+ task->scsi.length = DMIN32(SPDK_BDEV_LARGE_BUF_MAX_SIZE, task->scsi.transfer_len);
+ task->scsi.status = SPDK_SCSI_STATUS_GOOD;
+
+ remaining_size = task->scsi.transfer_len - task->scsi.length;
+ task->current_datain_offset = 0;
+
+ if (remaining_size == 0) {
+ TAILQ_INSERT_TAIL(&g_ut_read_tasks, task, link);
+ return;
+ }
+
+ while (1) {
+ if (task->current_datain_offset == 0) {
+ task->current_datain_offset = task->scsi.length;
+ TAILQ_INSERT_TAIL(&g_ut_read_tasks, task, link);
+ continue;
+ }
+
+ if (task->current_datain_offset < task->scsi.transfer_len) {
+ remaining_size = task->scsi.transfer_len - task->current_datain_offset;
+
+ subtask = ut_conn_task_get(task);
+
+ subtask->scsi.offset = task->current_datain_offset;
+ subtask->scsi.length = DMIN32(SPDK_BDEV_LARGE_BUF_MAX_SIZE, remaining_size);
+ subtask->scsi.status = SPDK_SCSI_STATUS_GOOD;
+
+ task->current_datain_offset += subtask->scsi.length;
+
+ TAILQ_INSERT_TAIL(&g_ut_read_tasks, subtask, link);
+ }
+
+ if (task->current_datain_offset == task->scsi.transfer_len) {
+ break;
+ }
+ }
+}
+
+static void
+read_task_split_in_order_case(void)
+{
+ struct spdk_iscsi_task *primary, *task, *tmp;
+
+ ut_conn_create_read_tasks(SPDK_BDEV_LARGE_BUF_MAX_SIZE * 8);
+
+ TAILQ_FOREACH(task, &g_ut_read_tasks, link) {
+ primary = spdk_iscsi_task_get_primary(task);
+ process_read_task_completion(NULL, task, primary);
+ }
+
+ primary = TAILQ_FIRST(&g_ut_read_tasks);
+ SPDK_CU_ASSERT_FATAL(primary != NULL);
+
+ if (primary != NULL) {
+ CU_ASSERT(primary->bytes_completed == primary->scsi.transfer_len);
+ }
+
+ TAILQ_FOREACH_SAFE(task, &g_ut_read_tasks, link, tmp) {
+ TAILQ_REMOVE(&g_ut_read_tasks, task, link);
+ free(task);
+ }
+
+ CU_ASSERT(TAILQ_EMPTY(&g_ut_read_tasks));
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("conn_suite", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "read task split in order", read_task_split_in_order_case) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/iscsi/init_grp.c/.gitignore b/src/spdk/test/unit/lib/iscsi/init_grp.c/.gitignore
new file mode 100644
index 00000000..8fbc2b63
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/init_grp.c/.gitignore
@@ -0,0 +1 @@
+init_grp_ut
diff --git a/src/spdk/test/unit/lib/iscsi/init_grp.c/Makefile b/src/spdk/test/unit/lib/iscsi/init_grp.c/Makefile
new file mode 100644
index 00000000..9c87ef55
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/init_grp.c/Makefile
@@ -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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+SPDK_LIB_LIST = conf
+TEST_FILE = init_grp_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp.conf b/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp.conf
new file mode 100644
index 00000000..aaa660de
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp.conf
@@ -0,0 +1,31 @@
+[IG_Valid0]
+# Success is expected.
+ InitiatorName iqn.2017-10.spdk.io:0001
+ Netmask 192.168.2.0
+
+[IG_Valid1]
+# Success is expected.
+ InitiatorName iqn.2017-10.spdk.io:0001
+ Netmask 192.168.2.0
+ Netmask 192.168.2.1
+
+[IG_Valid2]
+# Success is expected.
+ InitiatorName iqn.2017-10.spdk.io:0001
+ InitiatorName iqn.2017-10.spdk.io:0002
+ Netmask 192.168.2.0
+
+[IG_Valid3]
+# Success is expected.
+ InitiatorName iqn.2017-10.spdk.io:0001
+ InitiatorName iqn.2017-10.spdk.io:0002
+ Netmask 192.168.2.0
+ Netmask 192.168.2.1
+
+[IG_Invalid0]
+# Failure is expected.
+ InitiatorName iqn.2017-10.spdk.io:0001
+
+[IG_Invalid1]
+# Failure is expected.
+ Netmask 192.168.2.0
diff --git a/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp_ut.c b/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp_ut.c
new file mode 100644
index 00000000..5fcce81b
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/init_grp.c/init_grp_ut.c
@@ -0,0 +1,702 @@
+/*-
+ * 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_cunit.h"
+#include "CUnit/Basic.h"
+
+#include "iscsi/init_grp.c"
+#include "unit/lib/json_mock.c"
+
+SPDK_LOG_REGISTER_COMPONENT("iscsi", SPDK_LOG_ISCSI)
+
+struct spdk_iscsi_globals g_spdk_iscsi;
+
+const char *config_file;
+
+static int
+test_setup(void)
+{
+ TAILQ_INIT(&g_spdk_iscsi.ig_head);
+ return 0;
+}
+
+static void
+create_from_config_file_cases(void)
+{
+ struct spdk_conf *config;
+ struct spdk_conf_section *sp;
+ char section_name[64];
+ int section_index;
+ int rc;
+
+ config = spdk_conf_allocate();
+
+ rc = spdk_conf_read(config, config_file);
+ CU_ASSERT(rc == 0);
+
+ section_index = 0;
+ while (true) {
+ snprintf(section_name, sizeof(section_name), "IG_Valid%d", section_index);
+
+ sp = spdk_conf_find_section(config, section_name);
+ if (sp == NULL) {
+ break;
+ }
+
+ rc = spdk_iscsi_parse_init_grp(sp);
+ CU_ASSERT(rc == 0);
+
+ spdk_iscsi_init_grps_destroy();
+
+ section_index++;
+ }
+
+ section_index = 0;
+ while (true) {
+ snprintf(section_name, sizeof(section_name), "IG_Invalid%d", section_index);
+
+ sp = spdk_conf_find_section(config, section_name);
+ if (sp == NULL) {
+ break;
+ }
+
+ rc = spdk_iscsi_parse_init_grp(sp);
+ CU_ASSERT(rc != 0);
+
+ spdk_iscsi_init_grps_destroy();
+
+ section_index++;
+ }
+
+ spdk_conf_free(config);
+}
+
+
+static void
+create_initiator_group_success_case(void)
+{
+ struct spdk_iscsi_init_grp *ig;
+
+ ig = spdk_iscsi_init_grp_create(1);
+ CU_ASSERT(ig != NULL);
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+find_initiator_group_success_case(void)
+{
+ struct spdk_iscsi_init_grp *ig, *tmp;
+ int rc;
+
+ ig = spdk_iscsi_init_grp_create(1);
+ CU_ASSERT(ig != NULL);
+
+ rc = spdk_iscsi_init_grp_register(ig);
+ CU_ASSERT(rc == 0);
+
+ ig = spdk_iscsi_init_grp_find_by_tag(1);
+ CU_ASSERT(ig != NULL);
+
+ tmp = spdk_iscsi_init_grp_unregister(1);
+ CU_ASSERT(ig == tmp);
+ spdk_iscsi_init_grp_destroy(ig);
+
+ ig = spdk_iscsi_init_grp_find_by_tag(1);
+ CU_ASSERT(ig == NULL);
+}
+
+static void
+register_initiator_group_twice_case(void)
+{
+ struct spdk_iscsi_init_grp *ig, *tmp;
+ int rc;
+
+ ig = spdk_iscsi_init_grp_create(1);
+ CU_ASSERT(ig != NULL);
+
+ rc = spdk_iscsi_init_grp_register(ig);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_init_grp_register(ig);
+ CU_ASSERT(rc != 0);
+
+ ig = spdk_iscsi_init_grp_find_by_tag(1);
+ CU_ASSERT(ig != NULL);
+
+ tmp = spdk_iscsi_init_grp_unregister(1);
+ CU_ASSERT(tmp == ig);
+ spdk_iscsi_init_grp_destroy(ig);
+
+ ig = spdk_iscsi_init_grp_find_by_tag(1);
+ CU_ASSERT(ig == NULL);
+}
+
+static void
+add_initiator_name_success_case(void)
+{
+
+ int rc;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_name *iname;
+ char *name1 = "iqn.2017-10.spdk.io:0001";
+ char *name2 = "iqn.2017-10.spdk.io:0002";
+
+ ig = spdk_iscsi_init_grp_create(1);
+ CU_ASSERT(ig != NULL);
+
+ /* add two different names to the empty name list */
+ rc = spdk_iscsi_init_grp_add_initiator(ig, name1);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_init_grp_add_initiator(ig, name2);
+ CU_ASSERT(rc == 0);
+
+ /* check if two names are added correctly. */
+ iname = spdk_iscsi_init_grp_find_initiator(ig, name1);
+ CU_ASSERT(iname != NULL);
+
+ iname = spdk_iscsi_init_grp_find_initiator(ig, name2);
+ CU_ASSERT(iname != NULL);
+
+ /* restore the initial state */
+ rc = spdk_iscsi_init_grp_delete_initiator(ig, name1);
+ CU_ASSERT(rc == 0);
+
+ iname = spdk_iscsi_init_grp_find_initiator(ig, name1);
+ CU_ASSERT(iname == NULL);
+
+ rc = spdk_iscsi_init_grp_delete_initiator(ig, name2);
+ CU_ASSERT(rc == 0);
+
+ iname = spdk_iscsi_init_grp_find_initiator(ig, name2);
+ CU_ASSERT(iname == NULL);
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+add_initiator_name_fail_case(void)
+{
+ int rc;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_name *iname;
+ char *name1 = "iqn.2017-10.spdk.io:0001";
+
+ ig = spdk_iscsi_init_grp_create(1);
+ CU_ASSERT(ig != NULL);
+
+ /* add an name to the full name list */
+ ig->ninitiators = MAX_INITIATOR;
+
+ rc = spdk_iscsi_init_grp_add_initiator(ig, name1);
+ CU_ASSERT(rc != 0);
+
+ ig->ninitiators = 0;
+
+ /* add the same name to the name list twice */
+ rc = spdk_iscsi_init_grp_add_initiator(ig, name1);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_init_grp_add_initiator(ig, name1);
+ CU_ASSERT(rc != 0);
+
+ /* restore the initial state */
+ rc = spdk_iscsi_init_grp_delete_initiator(ig, name1);
+ CU_ASSERT(rc == 0);
+
+ iname = spdk_iscsi_init_grp_find_initiator(ig, name1);
+ CU_ASSERT(iname == NULL);
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+delete_all_initiator_names_success_case(void)
+{
+ int rc;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_name *iname;
+ char *name1 = "iqn.2017-10.spdk.io:0001";
+ char *name2 = "iqn.2017-10.spdk.io:0002";
+
+ ig = spdk_iscsi_init_grp_create(1);
+ CU_ASSERT(ig != NULL);
+
+ /* add two different names to the empty name list */
+ rc = spdk_iscsi_init_grp_add_initiator(ig, name1);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_init_grp_add_initiator(ig, name2);
+ CU_ASSERT(rc == 0);
+
+ /* delete all initiator names */
+ spdk_iscsi_init_grp_delete_all_initiators(ig);
+
+ /* check if two names are deleted correctly. */
+ iname = spdk_iscsi_init_grp_find_initiator(ig, name1);
+ CU_ASSERT(iname == NULL);
+
+ iname = spdk_iscsi_init_grp_find_initiator(ig, name2);
+ CU_ASSERT(iname == NULL);
+
+ /* restore the initial state */
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+add_netmask_success_case(void)
+{
+ int rc;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_netmask *imask;
+ char *netmask1 = "192.168.2.0";
+ char *netmask2 = "192.168.2.1";
+
+ ig = spdk_iscsi_init_grp_create(1);
+ CU_ASSERT(ig != NULL);
+
+ /* add two different netmasks to the empty netmask list */
+ rc = spdk_iscsi_init_grp_add_netmask(ig, netmask1);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_init_grp_add_netmask(ig, netmask2);
+ CU_ASSERT(rc == 0);
+
+ /* check if two netmasks are added correctly. */
+ imask = spdk_iscsi_init_grp_find_netmask(ig, netmask1);
+ CU_ASSERT(imask != NULL);
+
+ imask = spdk_iscsi_init_grp_find_netmask(ig, netmask2);
+ CU_ASSERT(imask != NULL);
+
+ /* restore the initial state */
+ rc = spdk_iscsi_init_grp_delete_netmask(ig, netmask1);
+ CU_ASSERT(rc == 0);
+
+ imask = spdk_iscsi_init_grp_find_netmask(ig, netmask1);
+ CU_ASSERT(imask == NULL);
+
+ rc = spdk_iscsi_init_grp_delete_netmask(ig, netmask2);
+ CU_ASSERT(rc == 0);
+
+ imask = spdk_iscsi_init_grp_find_netmask(ig, netmask2);
+ CU_ASSERT(imask == NULL);
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+add_netmask_fail_case(void)
+{
+ int rc;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_netmask *imask;
+ char *netmask1 = "192.168.2.0";
+
+ ig = spdk_iscsi_init_grp_create(1);
+ CU_ASSERT(ig != NULL);
+
+ /* add an netmask to the full netmask list */
+ ig->nnetmasks = MAX_NETMASK;
+
+ rc = spdk_iscsi_init_grp_add_netmask(ig, netmask1);
+ CU_ASSERT(rc != 0);
+
+ ig->nnetmasks = 0;
+
+ /* add the same netmask to the netmask list twice */
+ rc = spdk_iscsi_init_grp_add_netmask(ig, netmask1);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_init_grp_add_netmask(ig, netmask1);
+ CU_ASSERT(rc != 0);
+
+ /* restore the initial state */
+ rc = spdk_iscsi_init_grp_delete_netmask(ig, netmask1);
+ CU_ASSERT(rc == 0);
+
+ imask = spdk_iscsi_init_grp_find_netmask(ig, netmask1);
+ CU_ASSERT(imask == NULL);
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+delete_all_netmasks_success_case(void)
+{
+ int rc;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_netmask *imask;
+ char *netmask1 = "192.168.2.0";
+ char *netmask2 = "192.168.2.1";
+
+ ig = spdk_iscsi_init_grp_create(1);
+ CU_ASSERT(ig != NULL);
+
+ /* add two different netmasks to the empty netmask list */
+ rc = spdk_iscsi_init_grp_add_netmask(ig, netmask1);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_init_grp_add_netmask(ig, netmask2);
+ CU_ASSERT(rc == 0);
+
+ /* delete all netmasks */
+ spdk_iscsi_init_grp_delete_all_netmasks(ig);
+
+ /* check if two netmasks are deleted correctly. */
+ imask = spdk_iscsi_init_grp_find_netmask(ig, netmask1);
+ CU_ASSERT(imask == NULL);
+
+ imask = spdk_iscsi_init_grp_find_netmask(ig, netmask2);
+ CU_ASSERT(imask == NULL);
+
+ /* restore the initial state */
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+initiator_name_overwrite_all_to_any_case(void)
+{
+ int rc;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_name *iname;
+ char *all = "ALL";
+ char *any = "ANY";
+ char *all_not = "!ALL";
+ char *any_not = "!ANY";
+
+ ig = spdk_iscsi_init_grp_create(1);
+ CU_ASSERT(ig != NULL);
+
+ rc = spdk_iscsi_init_grp_add_initiator(ig, all);
+ CU_ASSERT(rc == 0);
+
+ iname = spdk_iscsi_init_grp_find_initiator(ig, all);
+ CU_ASSERT(iname == NULL);
+
+ iname = spdk_iscsi_init_grp_find_initiator(ig, any);
+ CU_ASSERT(iname != NULL);
+
+ rc = spdk_iscsi_init_grp_delete_initiator(ig, any);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_init_grp_add_initiator(ig, all_not);
+ CU_ASSERT(rc == 0);
+
+ iname = spdk_iscsi_init_grp_find_initiator(ig, all_not);
+ CU_ASSERT(iname == NULL);
+
+ iname = spdk_iscsi_init_grp_find_initiator(ig, any_not);
+ CU_ASSERT(iname != NULL);
+
+ rc = spdk_iscsi_init_grp_delete_initiator(ig, any_not);
+ CU_ASSERT(rc == 0);
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+netmask_overwrite_all_to_any_case(void)
+{
+ int rc;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_netmask *imask;
+ char *all = "ALL";
+ char *any = "ANY";
+
+ ig = spdk_iscsi_init_grp_create(1);
+ CU_ASSERT(ig != NULL);
+
+ rc = spdk_iscsi_init_grp_add_netmask(ig, all);
+ CU_ASSERT(rc == 0);
+
+ imask = spdk_iscsi_init_grp_find_netmask(ig, all);
+ CU_ASSERT(imask == NULL);
+
+ imask = spdk_iscsi_init_grp_find_netmask(ig, any);
+ CU_ASSERT(imask != NULL);
+
+ rc = spdk_iscsi_init_grp_delete_netmask(ig, any);
+ CU_ASSERT(rc == 0);
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+add_delete_initiator_names_case(void)
+{
+ int rc, i;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_name *iname;
+ char *names[3] = {"iqn.2018-02.spdk.io:0001", "iqn.2018-02.spdk.io:0002", "iqn.2018-02.spdk.io:0003"};
+
+ ig = spdk_iscsi_init_grp_create(1);
+ SPDK_CU_ASSERT_FATAL(ig != NULL);
+
+ rc = spdk_iscsi_init_grp_add_initiators(ig, 3, names);
+ CU_ASSERT(rc == 0);
+
+ for (i = 0; i < 3; i++) {
+ iname = spdk_iscsi_init_grp_find_initiator(ig, names[i]);
+ CU_ASSERT(iname != NULL);
+ }
+
+ rc = spdk_iscsi_init_grp_delete_initiators(ig, 3, names);
+ CU_ASSERT(rc == 0);
+
+ if (ig != NULL) {
+ CU_ASSERT(TAILQ_EMPTY(&ig->initiator_head));
+ }
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+add_duplicated_initiator_names_case(void)
+{
+ int rc;
+ struct spdk_iscsi_init_grp *ig;
+ char *names[3] = {"iqn.2018-02.spdk.io:0001", "iqn.2018-02.spdk.io:0002", "iqn.2018-02.spdk.io:0001"};
+
+ ig = spdk_iscsi_init_grp_create(1);
+ SPDK_CU_ASSERT_FATAL(ig != NULL);
+
+ rc = spdk_iscsi_init_grp_add_initiators(ig, 3, names);
+ CU_ASSERT(rc != 0);
+
+ if (ig != NULL) {
+ CU_ASSERT(TAILQ_EMPTY(&ig->initiator_head));
+ }
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+delete_nonexisting_initiator_names_case(void)
+{
+ int rc, i;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_name *iname;
+ char *names1[3] = {"iqn.2018-02.spdk.io:0001", "iqn.2018-02.spdk.io:0002", "iqn.2018-02.spdk.io:0003"};
+ char *names2[3] = {"iqn.2018-02.spdk.io:0001", "iqn.2018-02.spdk.io:0002", "iqn.2018-02.spdk.io:0004"};
+
+ ig = spdk_iscsi_init_grp_create(1);
+ SPDK_CU_ASSERT_FATAL(ig != NULL);
+
+ rc = spdk_iscsi_init_grp_add_initiators(ig, 3, names1);
+ CU_ASSERT(rc == 0);
+
+ for (i = 0; i < 3; i++) {
+ iname = spdk_iscsi_init_grp_find_initiator(ig, names1[i]);
+ CU_ASSERT(iname != NULL);
+ }
+
+ rc = spdk_iscsi_init_grp_delete_initiators(ig, 3, names2);
+ CU_ASSERT(rc != 0);
+
+ for (i = 0; i < 3; i++) {
+ iname = spdk_iscsi_init_grp_find_initiator(ig, names1[i]);
+ CU_ASSERT(iname != NULL);
+ }
+
+ rc = spdk_iscsi_init_grp_delete_initiators(ig, 3, names1);
+ CU_ASSERT(rc == 0);
+
+ if (ig != NULL) {
+ CU_ASSERT(TAILQ_EMPTY(&ig->initiator_head));
+ }
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+add_delete_netmasks_case(void)
+{
+ int rc, i;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_netmask *netmask;
+ char *netmasks[3] = {"192.168.2.0", "192.168.2.1", "192.168.2.2"};
+
+ ig = spdk_iscsi_init_grp_create(1);
+ SPDK_CU_ASSERT_FATAL(ig != NULL);
+
+ rc = spdk_iscsi_init_grp_add_netmasks(ig, 3, netmasks);
+ CU_ASSERT(rc == 0);
+
+ for (i = 0; i < 3; i++) {
+ netmask = spdk_iscsi_init_grp_find_netmask(ig, netmasks[i]);
+ CU_ASSERT(netmask != NULL);
+ }
+
+ rc = spdk_iscsi_init_grp_delete_netmasks(ig, 3, netmasks);
+ CU_ASSERT(rc == 0);
+
+ if (ig != NULL) {
+ CU_ASSERT(TAILQ_EMPTY(&ig->netmask_head));
+ }
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+add_duplicated_netmasks_case(void)
+{
+ int rc;
+ struct spdk_iscsi_init_grp *ig;
+ char *netmasks[3] = {"192.168.2.0", "192.168.2.1", "192.168.2.0"};
+
+ ig = spdk_iscsi_init_grp_create(1);
+ SPDK_CU_ASSERT_FATAL(ig != NULL);
+
+ rc = spdk_iscsi_init_grp_add_netmasks(ig, 3, netmasks);
+ CU_ASSERT(rc != 0);
+
+ if (ig != NULL) {
+ CU_ASSERT(TAILQ_EMPTY(&ig->netmask_head));
+ }
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+static void
+delete_nonexisting_netmasks_case(void)
+{
+ int rc, i;
+ struct spdk_iscsi_init_grp *ig;
+ struct spdk_iscsi_initiator_netmask *netmask;
+ char *netmasks1[3] = {"192.168.2.0", "192.168.2.1", "192.168.2.2"};
+ char *netmasks2[3] = {"192.168.2.0", "192.168.2.1", "192.168.2.3"};
+
+ ig = spdk_iscsi_init_grp_create(1);
+ SPDK_CU_ASSERT_FATAL(ig != NULL);
+
+ rc = spdk_iscsi_init_grp_add_netmasks(ig, 3, netmasks1);
+ CU_ASSERT(rc == 0);
+
+ for (i = 0; i < 3; i++) {
+ netmask = spdk_iscsi_init_grp_find_netmask(ig, netmasks1[i]);
+ CU_ASSERT(netmask != NULL);
+ }
+
+ rc = spdk_iscsi_init_grp_delete_netmasks(ig, 3, netmasks2);
+ CU_ASSERT(rc != 0);
+
+ for (i = 0; i < 3; i++) {
+ netmask = spdk_iscsi_init_grp_find_netmask(ig, netmasks1[i]);
+ CU_ASSERT(netmask != NULL);
+ }
+
+ rc = spdk_iscsi_init_grp_delete_netmasks(ig, 3, netmasks1);
+ CU_ASSERT(rc == 0);
+
+ if (ig != NULL) {
+ CU_ASSERT(TAILQ_EMPTY(&ig->netmask_head));
+ }
+
+ spdk_iscsi_init_grp_destroy(ig);
+}
+
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <config file>\n", argv[0]);
+ exit(1);
+ }
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ config_file = argv[1];
+
+ suite = CU_add_suite("init_grp_suite", test_setup, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "create from config file cases",
+ create_from_config_file_cases) == NULL
+ || CU_add_test(suite, "create initiator group success case",
+ create_initiator_group_success_case) == NULL
+ || CU_add_test(suite, "find initiator group success case",
+ find_initiator_group_success_case) == NULL
+ || CU_add_test(suite, "register initiator group twice case",
+ register_initiator_group_twice_case) == NULL
+ || CU_add_test(suite, "add initiator name success case",
+ add_initiator_name_success_case) == NULL
+ || CU_add_test(suite, "add initiator name fail case",
+ add_initiator_name_fail_case) == NULL
+ || CU_add_test(suite, "delete all initiator names success case",
+ delete_all_initiator_names_success_case) == NULL
+ || CU_add_test(suite, "add initiator netmask success case",
+ add_netmask_success_case) == NULL
+ || CU_add_test(suite, "add initiator netmask fail case",
+ add_netmask_fail_case) == NULL
+ || CU_add_test(suite, "delete all initiator netmasks success case",
+ delete_all_netmasks_success_case) == NULL
+ || CU_add_test(suite, "overwrite all to any for name case",
+ initiator_name_overwrite_all_to_any_case) == NULL
+ || CU_add_test(suite, "overwrite all to any for netmask case",
+ netmask_overwrite_all_to_any_case) == NULL
+ || CU_add_test(suite, "add/delete initiator names case",
+ add_delete_initiator_names_case) == NULL
+ || CU_add_test(suite, "add duplicated initiator names case",
+ add_duplicated_initiator_names_case) == NULL
+ || CU_add_test(suite, "delete nonexisting initiator names case",
+ delete_nonexisting_initiator_names_case) == NULL
+ || CU_add_test(suite, "add/delete netmasks case",
+ add_delete_netmasks_case) == NULL
+ || CU_add_test(suite, "add duplicated netmasks case",
+ add_duplicated_netmasks_case) == NULL
+ || CU_add_test(suite, "delete nonexisting netmasks case",
+ delete_nonexisting_netmasks_case) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/iscsi/iscsi.c/.gitignore b/src/spdk/test/unit/lib/iscsi/iscsi.c/.gitignore
new file mode 100644
index 00000000..4d41887c
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/iscsi.c/.gitignore
@@ -0,0 +1 @@
+iscsi_ut
diff --git a/src/spdk/test/unit/lib/iscsi/iscsi.c/Makefile b/src/spdk/test/unit/lib/iscsi/iscsi.c/Makefile
new file mode 100644
index 00000000..bc9a9d8b
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/iscsi.c/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.app.mk
+
+SPDK_LIB_LIST = trace conf util
+
+SCSI_OBJS = port
+ISCSI_OBJS = md5 param
+LIBS += $(SCSI_OBJS:%=$(SPDK_ROOT_DIR)/lib/scsi/%.o)
+LIBS += $(ISCSI_OBJS:%=$(SPDK_ROOT_DIR)/lib/iscsi/%.o)
+LIBS += -lcunit $(ENV_LINKER_ARGS)
+
+TEST_FILE = iscsi_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c b/src/spdk/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c
new file mode 100644
index 00000000..4038a1e4
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c
@@ -0,0 +1,972 @@
+/*-
+ * 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/endian.h"
+#include "spdk/scsi.h"
+#include "spdk_cunit.h"
+
+#include "CUnit/Basic.h"
+
+#include "iscsi/iscsi.c"
+
+#include "../common.c"
+#include "iscsi/acceptor.h"
+#include "iscsi/portal_grp.h"
+#include "scsi/scsi_internal.h"
+
+#define UT_TARGET_NAME1 "iqn.2017-11.spdk.io:t0001"
+#define UT_TARGET_NAME2 "iqn.2017-11.spdk.io:t0002"
+#define UT_INITIATOR_NAME1 "iqn.2017-11.spdk.io:i0001"
+#define UT_INITIATOR_NAME2 "iqn.2017-11.spdk.io:i0002"
+
+struct spdk_iscsi_tgt_node *
+spdk_iscsi_find_tgt_node(const char *target_name)
+{
+ if (strcasecmp(target_name, UT_TARGET_NAME1) == 0) {
+ return (struct spdk_iscsi_tgt_node *)1;
+ } else {
+ return NULL;
+ }
+}
+
+bool
+spdk_iscsi_tgt_node_access(struct spdk_iscsi_conn *conn,
+ struct spdk_iscsi_tgt_node *target,
+ const char *iqn, const char *addr)
+{
+ if (strcasecmp(conn->initiator_name, UT_INITIATOR_NAME1) == 0) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+int
+spdk_iscsi_send_tgts(struct spdk_iscsi_conn *conn, const char *iiqn,
+ const char *iaddr,
+ const char *tiqn, uint8_t *data, int alloc_len, int data_len)
+{
+ return 0;
+}
+
+void
+spdk_iscsi_portal_grp_close_all(void)
+{
+}
+
+void
+spdk_iscsi_conn_migration(struct spdk_iscsi_conn *conn)
+{
+}
+
+void
+spdk_iscsi_conn_free_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu)
+{
+}
+
+int
+spdk_iscsi_chap_get_authinfo(struct iscsi_chap_auth *auth, const char *authuser,
+ int ag_tag)
+{
+ return 0;
+}
+
+int
+spdk_scsi_lun_get_id(const struct spdk_scsi_lun *lun)
+{
+ return lun->id;
+}
+
+bool
+spdk_scsi_lun_is_removing(const struct spdk_scsi_lun *lun)
+{
+ return true;
+}
+
+struct spdk_scsi_lun *
+spdk_scsi_dev_get_lun(struct spdk_scsi_dev *dev, int lun_id)
+{
+ if (lun_id < 0 || lun_id >= SPDK_SCSI_DEV_MAX_LUN) {
+ return NULL;
+ }
+
+ return dev->lun[lun_id];
+}
+
+static void
+op_login_check_target_test(void)
+{
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_pdu rsp_pdu;
+ struct spdk_iscsi_tgt_node *target;
+ int rc;
+
+ /* expect success */
+ snprintf(conn.initiator_name, sizeof(conn.initiator_name),
+ "%s", UT_INITIATOR_NAME1);
+
+ rc = spdk_iscsi_op_login_check_target(&conn, &rsp_pdu,
+ UT_TARGET_NAME1, &target);
+ CU_ASSERT(rc == 0);
+
+ /* expect failure */
+ snprintf(conn.initiator_name, sizeof(conn.initiator_name),
+ "%s", UT_INITIATOR_NAME1);
+
+ rc = spdk_iscsi_op_login_check_target(&conn, &rsp_pdu,
+ UT_TARGET_NAME2, &target);
+ CU_ASSERT(rc != 0);
+
+ /* expect failure */
+ snprintf(conn.initiator_name, sizeof(conn.initiator_name),
+ "%s", UT_INITIATOR_NAME2);
+
+ rc = spdk_iscsi_op_login_check_target(&conn, &rsp_pdu,
+ UT_TARGET_NAME1, &target);
+ CU_ASSERT(rc != 0);
+}
+
+static void
+maxburstlength_test(void)
+{
+ struct spdk_iscsi_sess sess;
+ struct spdk_iscsi_conn conn;
+ struct spdk_scsi_dev dev;
+ struct spdk_scsi_lun lun;
+ struct spdk_iscsi_pdu *req_pdu, *data_out_pdu, *r2t_pdu;
+ struct iscsi_bhs_scsi_req *req;
+ struct iscsi_bhs_r2t *r2t;
+ struct iscsi_bhs_data_out *data_out;
+ struct spdk_iscsi_pdu *response_pdu;
+ int rc;
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&conn, 0, sizeof(conn));
+ memset(&dev, 0, sizeof(dev));
+ memset(&lun, 0, sizeof(lun));
+
+ req_pdu = spdk_get_pdu();
+ data_out_pdu = spdk_get_pdu();
+
+ sess.ExpCmdSN = 0;
+ sess.MaxCmdSN = 64;
+ sess.session_type = SESSION_TYPE_NORMAL;
+ sess.MaxBurstLength = 1024;
+
+ lun.id = 0;
+
+ dev.lun[0] = &lun;
+
+ conn.full_feature = 1;
+ conn.sess = &sess;
+ conn.dev = &dev;
+ conn.state = ISCSI_CONN_STATE_RUNNING;
+ TAILQ_INIT(&conn.write_pdu_list);
+ TAILQ_INIT(&conn.active_r2t_tasks);
+
+ TAILQ_INIT(&g_write_pdu_list);
+
+ req_pdu->bhs.opcode = ISCSI_OP_SCSI;
+ req_pdu->data_segment_len = 0;
+
+ req = (struct iscsi_bhs_scsi_req *)&req_pdu->bhs;
+
+ to_be32(&req->cmd_sn, 0);
+ to_be32(&req->expected_data_xfer_len, 1028);
+ to_be32(&req->itt, 0x1234);
+ req->write_bit = 1;
+ req->final_bit = 1;
+
+ rc = spdk_iscsi_execute(&conn, req_pdu);
+ CU_ASSERT(rc == 0);
+
+ response_pdu = TAILQ_FIRST(&g_write_pdu_list);
+ SPDK_CU_ASSERT_FATAL(response_pdu != NULL);
+
+ /*
+ * Confirm that a correct R2T reply was sent in response to the
+ * SCSI request.
+ */
+ TAILQ_REMOVE(&g_write_pdu_list, response_pdu, tailq);
+ CU_ASSERT(response_pdu->bhs.opcode == ISCSI_OP_R2T);
+ r2t = (struct iscsi_bhs_r2t *)&response_pdu->bhs;
+ CU_ASSERT(from_be32(&r2t->desired_xfer_len) == 1024);
+ CU_ASSERT(from_be32(&r2t->buffer_offset) == 0);
+ CU_ASSERT(from_be32(&r2t->itt) == 0x1234);
+
+ data_out_pdu->bhs.opcode = ISCSI_OP_SCSI_DATAOUT;
+ data_out_pdu->bhs.flags = ISCSI_FLAG_FINAL;
+ data_out_pdu->data_segment_len = 1028;
+ data_out = (struct iscsi_bhs_data_out *)&data_out_pdu->bhs;
+ data_out->itt = r2t->itt;
+ data_out->ttt = r2t->ttt;
+ DSET24(data_out->data_segment_len, 1028);
+
+ rc = spdk_iscsi_execute(&conn, data_out_pdu);
+ CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL);
+
+ SPDK_CU_ASSERT_FATAL(response_pdu->task != NULL);
+ spdk_iscsi_task_disassociate_pdu(response_pdu->task);
+ spdk_iscsi_task_put(response_pdu->task);
+ spdk_put_pdu(response_pdu);
+
+ r2t_pdu = TAILQ_FIRST(&g_write_pdu_list);
+ CU_ASSERT(r2t_pdu != NULL);
+ TAILQ_REMOVE(&g_write_pdu_list, r2t_pdu, tailq);
+ spdk_put_pdu(r2t_pdu);
+
+ spdk_put_pdu(data_out_pdu);
+ spdk_put_pdu(req_pdu);
+}
+
+static void
+underflow_for_read_transfer_test(void)
+{
+ struct spdk_iscsi_sess sess;
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_task task;
+ struct spdk_iscsi_pdu *pdu;
+ struct iscsi_bhs_scsi_req *scsi_req;
+ struct iscsi_bhs_data_in *datah;
+ uint32_t residual_count = 0;
+
+ TAILQ_INIT(&g_write_pdu_list);
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&conn, 0, sizeof(conn));
+ memset(&task, 0, sizeof(task));
+
+ sess.MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH;
+
+ conn.sess = &sess;
+ conn.MaxRecvDataSegmentLength = 8192;
+
+ pdu = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu != NULL);
+
+ scsi_req = (struct iscsi_bhs_scsi_req *)&pdu->bhs;
+ scsi_req->read_bit = 1;
+
+ spdk_iscsi_task_set_pdu(&task, pdu);
+ task.parent = NULL;
+
+ task.scsi.iovs = &task.scsi.iov;
+ task.scsi.iovcnt = 1;
+ task.scsi.length = 512;
+ task.scsi.transfer_len = 512;
+ task.bytes_completed = 512;
+ task.scsi.data_transferred = 256;
+ task.scsi.status = SPDK_SCSI_STATUS_GOOD;
+
+ spdk_iscsi_task_response(&conn, &task);
+ spdk_put_pdu(pdu);
+
+ /*
+ * In this case, a SCSI Data-In PDU should contain the Status
+ * for the data transfer.
+ */
+ to_be32(&residual_count, 256);
+
+ pdu = TAILQ_FIRST(&g_write_pdu_list);
+ SPDK_CU_ASSERT_FATAL(pdu != NULL);
+
+ CU_ASSERT(pdu->bhs.opcode == ISCSI_OP_SCSI_DATAIN);
+
+ datah = (struct iscsi_bhs_data_in *)&pdu->bhs;
+
+ CU_ASSERT(datah->flags == (ISCSI_DATAIN_UNDERFLOW | ISCSI_FLAG_FINAL | ISCSI_DATAIN_STATUS));
+ CU_ASSERT(datah->res_cnt == residual_count);
+
+ TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq);
+ spdk_put_pdu(pdu);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_write_pdu_list));
+}
+
+static void
+underflow_for_zero_read_transfer_test(void)
+{
+ struct spdk_iscsi_sess sess;
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_task task;
+ struct spdk_iscsi_pdu *pdu;
+ struct iscsi_bhs_scsi_req *scsi_req;
+ struct iscsi_bhs_scsi_resp *resph;
+ uint32_t residual_count = 0, data_segment_len;
+
+ TAILQ_INIT(&g_write_pdu_list);
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&conn, 0, sizeof(conn));
+ memset(&task, 0, sizeof(task));
+
+ sess.MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH;
+
+ conn.sess = &sess;
+ conn.MaxRecvDataSegmentLength = 8192;
+
+ pdu = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu != NULL);
+
+ scsi_req = (struct iscsi_bhs_scsi_req *)&pdu->bhs;
+ scsi_req->read_bit = 1;
+
+ spdk_iscsi_task_set_pdu(&task, pdu);
+ task.parent = NULL;
+
+ task.scsi.length = 512;
+ task.scsi.transfer_len = 512;
+ task.bytes_completed = 512;
+ task.scsi.data_transferred = 0;
+ task.scsi.status = SPDK_SCSI_STATUS_GOOD;
+
+ spdk_iscsi_task_response(&conn, &task);
+ spdk_put_pdu(pdu);
+
+ /*
+ * In this case, only a SCSI Response PDU is expected and
+ * underflow must be set in it.
+ * */
+ to_be32(&residual_count, 512);
+
+ pdu = TAILQ_FIRST(&g_write_pdu_list);
+ SPDK_CU_ASSERT_FATAL(pdu != NULL);
+
+ CU_ASSERT(pdu->bhs.opcode == ISCSI_OP_SCSI_RSP);
+
+ resph = (struct iscsi_bhs_scsi_resp *)&pdu->bhs;
+
+ CU_ASSERT(resph->flags == (ISCSI_SCSI_UNDERFLOW | 0x80));
+
+ data_segment_len = DGET24(resph->data_segment_len);
+ CU_ASSERT(data_segment_len == 0);
+ CU_ASSERT(resph->res_cnt == residual_count);
+
+ TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq);
+ spdk_put_pdu(pdu);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_write_pdu_list));
+}
+
+static void
+underflow_for_request_sense_test(void)
+{
+ struct spdk_iscsi_sess sess;
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_task task;
+ struct spdk_iscsi_pdu *pdu1, *pdu2;
+ struct iscsi_bhs_scsi_req *scsi_req;
+ struct iscsi_bhs_data_in *datah;
+ struct iscsi_bhs_scsi_resp *resph;
+ uint32_t residual_count = 0, data_segment_len;
+
+ TAILQ_INIT(&g_write_pdu_list);
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&conn, 0, sizeof(conn));
+ memset(&task, 0, sizeof(task));
+
+ sess.MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH;
+
+ conn.sess = &sess;
+ conn.MaxRecvDataSegmentLength = 8192;
+
+ pdu1 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu1 != NULL);
+
+ scsi_req = (struct iscsi_bhs_scsi_req *)&pdu1->bhs;
+ scsi_req->read_bit = 1;
+
+ spdk_iscsi_task_set_pdu(&task, pdu1);
+ task.parent = NULL;
+
+ task.scsi.iovs = &task.scsi.iov;
+ task.scsi.iovcnt = 1;
+ task.scsi.length = 512;
+ task.scsi.transfer_len = 512;
+ task.bytes_completed = 512;
+
+ task.scsi.sense_data_len = 18;
+ task.scsi.data_transferred = 18;
+ task.scsi.status = SPDK_SCSI_STATUS_GOOD;
+
+ spdk_iscsi_task_response(&conn, &task);
+ spdk_put_pdu(pdu1);
+
+ /*
+ * In this case, a SCSI Data-In PDU and a SCSI Response PDU are returned.
+ * Sense data are set both in payload and sense area.
+ * The SCSI Data-In PDU sets FINAL and the SCSI Response PDU sets UNDERFLOW.
+ *
+ * Probably there will be different implementation but keeping current SPDK
+ * implementation by adding UT will be valuable for any implementation.
+ */
+ to_be32(&residual_count, 494);
+
+ pdu1 = TAILQ_FIRST(&g_write_pdu_list);
+ SPDK_CU_ASSERT_FATAL(pdu1 != NULL);
+
+ CU_ASSERT(pdu1->bhs.opcode == ISCSI_OP_SCSI_DATAIN);
+
+ datah = (struct iscsi_bhs_data_in *)&pdu1->bhs;
+
+ CU_ASSERT(datah->flags == ISCSI_FLAG_FINAL);
+
+ data_segment_len = DGET24(datah->data_segment_len);
+ CU_ASSERT(data_segment_len == 18);
+ CU_ASSERT(datah->res_cnt == 0);
+
+ TAILQ_REMOVE(&g_write_pdu_list, pdu1, tailq);
+ spdk_put_pdu(pdu1);
+
+ pdu2 = TAILQ_FIRST(&g_write_pdu_list);
+ /* inform scan-build (clang 6) that these pointers are not the same */
+ SPDK_CU_ASSERT_FATAL(pdu1 != pdu2);
+ SPDK_CU_ASSERT_FATAL(pdu2 != NULL);
+
+ CU_ASSERT(pdu2->bhs.opcode == ISCSI_OP_SCSI_RSP);
+
+ resph = (struct iscsi_bhs_scsi_resp *)&pdu2->bhs;
+
+ CU_ASSERT(resph->flags == (ISCSI_SCSI_UNDERFLOW | 0x80));
+
+ data_segment_len = DGET24(resph->data_segment_len);
+ CU_ASSERT(data_segment_len == task.scsi.sense_data_len + 2);
+ CU_ASSERT(resph->res_cnt == residual_count);
+
+ TAILQ_REMOVE(&g_write_pdu_list, pdu2, tailq);
+ spdk_put_pdu(pdu2);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_write_pdu_list));
+}
+
+static void
+underflow_for_check_condition_test(void)
+{
+ struct spdk_iscsi_sess sess;
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_task task;
+ struct spdk_iscsi_pdu *pdu;
+ struct iscsi_bhs_scsi_req *scsi_req;
+ struct iscsi_bhs_scsi_resp *resph;
+ uint32_t data_segment_len;
+
+ TAILQ_INIT(&g_write_pdu_list);
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&conn, 0, sizeof(conn));
+ memset(&task, 0, sizeof(task));
+
+ sess.MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH;
+
+ conn.sess = &sess;
+ conn.MaxRecvDataSegmentLength = 8192;
+
+ pdu = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu != NULL);
+
+ scsi_req = (struct iscsi_bhs_scsi_req *)&pdu->bhs;
+ scsi_req->read_bit = 1;
+
+ spdk_iscsi_task_set_pdu(&task, pdu);
+ task.parent = NULL;
+
+ task.scsi.iovs = &task.scsi.iov;
+ task.scsi.iovcnt = 1;
+ task.scsi.length = 512;
+ task.scsi.transfer_len = 512;
+ task.bytes_completed = 512;
+
+ task.scsi.sense_data_len = 18;
+ task.scsi.data_transferred = 18;
+ task.scsi.status = SPDK_SCSI_STATUS_CHECK_CONDITION;
+
+ spdk_iscsi_task_response(&conn, &task);
+ spdk_put_pdu(pdu);
+
+ /*
+ * In this case, a SCSI Response PDU is returned.
+ * Sense data is set in sense area.
+ * Underflow is not set.
+ */
+ pdu = TAILQ_FIRST(&g_write_pdu_list);
+ SPDK_CU_ASSERT_FATAL(pdu != NULL);
+
+ CU_ASSERT(pdu->bhs.opcode == ISCSI_OP_SCSI_RSP);
+
+ resph = (struct iscsi_bhs_scsi_resp *)&pdu->bhs;
+
+ CU_ASSERT(resph->flags == 0x80);
+
+ data_segment_len = DGET24(resph->data_segment_len);
+ CU_ASSERT(data_segment_len == task.scsi.sense_data_len + 2);
+ CU_ASSERT(resph->res_cnt == 0);
+
+ TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq);
+ spdk_put_pdu(pdu);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_write_pdu_list));
+}
+
+static void
+add_transfer_task_test(void)
+{
+ struct spdk_iscsi_sess sess;
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_task task;
+ struct spdk_iscsi_pdu *pdu, *tmp;
+ struct iscsi_bhs_r2t *r2th;
+ int rc, count = 0;
+ uint32_t buffer_offset, desired_xfer_len;
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&conn, 0, sizeof(conn));
+ memset(&task, 0, sizeof(task));
+
+ sess.MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH; /* 1M */
+ sess.MaxOutstandingR2T = DEFAULT_MAXR2T; /* 4 */
+
+ conn.sess = &sess;
+ TAILQ_INIT(&conn.queued_r2t_tasks);
+ TAILQ_INIT(&conn.active_r2t_tasks);
+
+ pdu = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu != NULL);
+
+ pdu->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; /* 64K */
+ task.scsi.transfer_len = 16 * 1024 * 1024;
+ spdk_iscsi_task_set_pdu(&task, pdu);
+
+ /* The following tests if the task is queued because R2T tasks are full. */
+ conn.pending_r2t = DEFAULT_MAXR2T;
+
+ rc = spdk_add_transfer_task(&conn, &task);
+
+ CU_ASSERT(rc == SPDK_SUCCESS);
+ CU_ASSERT(TAILQ_FIRST(&conn.queued_r2t_tasks) == &task);
+
+ TAILQ_REMOVE(&conn.queued_r2t_tasks, &task, link);
+ CU_ASSERT(TAILQ_EMPTY(&conn.queued_r2t_tasks));
+
+ /* The following tests if multiple R2Ts are issued. */
+ conn.pending_r2t = 0;
+
+ rc = spdk_add_transfer_task(&conn, &task);
+
+ CU_ASSERT(rc == SPDK_SUCCESS);
+ CU_ASSERT(TAILQ_FIRST(&conn.active_r2t_tasks) == &task);
+
+ TAILQ_REMOVE(&conn.active_r2t_tasks, &task, link);
+ CU_ASSERT(TAILQ_EMPTY(&conn.active_r2t_tasks));
+
+ CU_ASSERT(conn.data_out_cnt == 255);
+ CU_ASSERT(conn.pending_r2t == 1);
+ CU_ASSERT(conn.outstanding_r2t_tasks[0] == &task);
+ CU_ASSERT(conn.ttt == 1);
+
+ CU_ASSERT(task.data_out_cnt == 255);
+ CU_ASSERT(task.ttt == 1);
+ CU_ASSERT(task.outstanding_r2t == sess.MaxOutstandingR2T);
+ CU_ASSERT(task.next_r2t_offset ==
+ pdu->data_segment_len + sess.MaxBurstLength * sess.MaxOutstandingR2T);
+
+
+ while (!TAILQ_EMPTY(&g_write_pdu_list)) {
+ tmp = TAILQ_FIRST(&g_write_pdu_list);
+ TAILQ_REMOVE(&g_write_pdu_list, tmp, tailq);
+
+ r2th = (struct iscsi_bhs_r2t *)&tmp->bhs;
+
+ buffer_offset = from_be32(&r2th->buffer_offset);
+ CU_ASSERT(buffer_offset == pdu->data_segment_len + sess.MaxBurstLength * count);
+
+ desired_xfer_len = from_be32(&r2th->desired_xfer_len);
+ CU_ASSERT(desired_xfer_len == sess.MaxBurstLength);
+
+ spdk_put_pdu(tmp);
+ count++;
+ }
+
+ CU_ASSERT(count == DEFAULT_MAXR2T);
+
+ spdk_put_pdu(pdu);
+}
+
+static void
+get_transfer_task_test(void)
+{
+ struct spdk_iscsi_sess sess;
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_task task1, task2, *task;
+ struct spdk_iscsi_pdu *pdu1, *pdu2, *pdu;
+ int rc;
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&conn, 0, sizeof(conn));
+ memset(&task1, 0, sizeof(task1));
+ memset(&task2, 0, sizeof(task2));
+
+ sess.MaxBurstLength = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ sess.MaxOutstandingR2T = 1;
+
+ conn.sess = &sess;
+ TAILQ_INIT(&conn.active_r2t_tasks);
+
+ pdu1 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu1 != NULL);
+
+ pdu1->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task1.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ spdk_iscsi_task_set_pdu(&task1, pdu1);
+
+ rc = spdk_add_transfer_task(&conn, &task1);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ pdu2 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu2 != NULL);
+
+ pdu2->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task2.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ spdk_iscsi_task_set_pdu(&task2, pdu2);
+
+ rc = spdk_add_transfer_task(&conn, &task2);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ task = spdk_get_transfer_task(&conn, 1);
+ CU_ASSERT(task == &task1);
+
+ task = spdk_get_transfer_task(&conn, 2);
+ CU_ASSERT(task == &task2);
+
+ while (!TAILQ_EMPTY(&conn.active_r2t_tasks)) {
+ task = TAILQ_FIRST(&conn.active_r2t_tasks);
+ TAILQ_REMOVE(&conn.active_r2t_tasks, task, link);
+ }
+
+ while (!TAILQ_EMPTY(&g_write_pdu_list)) {
+ pdu = TAILQ_FIRST(&g_write_pdu_list);
+ TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq);
+ spdk_put_pdu(pdu);
+ }
+
+ spdk_put_pdu(pdu2);
+ spdk_put_pdu(pdu1);
+}
+
+static void
+del_transfer_task_test(void)
+{
+ struct spdk_iscsi_sess sess;
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_task task1, task2, task3, task4, task5, *task;
+ struct spdk_iscsi_pdu *pdu1, *pdu2, *pdu3, *pdu4, *pdu5, *pdu;
+ int rc;
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&conn, 0, sizeof(conn));
+ memset(&task1, 0, sizeof(task1));
+ memset(&task2, 0, sizeof(task2));
+ memset(&task3, 0, sizeof(task3));
+ memset(&task4, 0, sizeof(task4));
+ memset(&task5, 0, sizeof(task5));
+
+ sess.MaxBurstLength = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ sess.MaxOutstandingR2T = 1;
+
+ conn.sess = &sess;
+ TAILQ_INIT(&conn.active_r2t_tasks);
+ TAILQ_INIT(&conn.queued_r2t_tasks);
+
+ pdu1 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu1 != NULL);
+
+ pdu1->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task1.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ spdk_iscsi_task_set_pdu(&task1, pdu1);
+ task1.tag = 11;
+
+ rc = spdk_add_transfer_task(&conn, &task1);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ pdu2 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu2 != NULL);
+
+ pdu2->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task2.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ spdk_iscsi_task_set_pdu(&task2, pdu2);
+ task2.tag = 12;
+
+ rc = spdk_add_transfer_task(&conn, &task2);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ pdu3 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu3 != NULL);
+
+ pdu3->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task3.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ spdk_iscsi_task_set_pdu(&task3, pdu3);
+ task3.tag = 13;
+
+ rc = spdk_add_transfer_task(&conn, &task3);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ pdu4 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu4 != NULL);
+
+ pdu4->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task4.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ spdk_iscsi_task_set_pdu(&task4, pdu4);
+ task4.tag = 14;
+
+ rc = spdk_add_transfer_task(&conn, &task4);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ pdu5 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu5 != NULL);
+
+ pdu5->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task5.scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ spdk_iscsi_task_set_pdu(&task5, pdu5);
+ task5.tag = 15;
+
+ rc = spdk_add_transfer_task(&conn, &task5);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ CU_ASSERT(spdk_get_transfer_task(&conn, 1) == &task1);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 5) == NULL);
+ spdk_del_transfer_task(&conn, 11);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 1) == NULL);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 5) == &task5);
+
+ CU_ASSERT(spdk_get_transfer_task(&conn, 2) == &task2);
+ spdk_del_transfer_task(&conn, 12);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 2) == NULL);
+
+ CU_ASSERT(spdk_get_transfer_task(&conn, 3) == &task3);
+ spdk_del_transfer_task(&conn, 13);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 3) == NULL);
+
+ CU_ASSERT(spdk_get_transfer_task(&conn, 4) == &task4);
+ spdk_del_transfer_task(&conn, 14);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 4) == NULL);
+
+ CU_ASSERT(spdk_get_transfer_task(&conn, 5) == &task5);
+ spdk_del_transfer_task(&conn, 15);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 5) == NULL);
+
+ while (!TAILQ_EMPTY(&conn.active_r2t_tasks)) {
+ task = TAILQ_FIRST(&conn.active_r2t_tasks);
+ TAILQ_REMOVE(&conn.active_r2t_tasks, task, link);
+ }
+
+ while (!TAILQ_EMPTY(&g_write_pdu_list)) {
+ pdu = TAILQ_FIRST(&g_write_pdu_list);
+ TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq);
+ spdk_put_pdu(pdu);
+ }
+
+ spdk_put_pdu(pdu5);
+ spdk_put_pdu(pdu4);
+ spdk_put_pdu(pdu3);
+ spdk_put_pdu(pdu2);
+ spdk_put_pdu(pdu1);
+}
+
+static void
+clear_all_transfer_tasks_test(void)
+{
+ struct spdk_iscsi_sess sess;
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_task *task1, *task2, *task3, *task4, *task5;
+ struct spdk_iscsi_pdu *pdu1, *pdu2, *pdu3, *pdu4, *pdu5, *pdu;
+ struct spdk_scsi_lun lun1, lun2;
+ int rc;
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&conn, 0, sizeof(conn));
+ memset(&lun1, 0, sizeof(lun1));
+ memset(&lun2, 0, sizeof(lun2));
+
+ sess.MaxBurstLength = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ sess.MaxOutstandingR2T = 1;
+
+ conn.sess = &sess;
+ TAILQ_INIT(&conn.active_r2t_tasks);
+ TAILQ_INIT(&conn.queued_r2t_tasks);
+
+ task1 = spdk_iscsi_task_get(&conn, NULL, NULL);
+ SPDK_CU_ASSERT_FATAL(task1 != NULL);
+ pdu1 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu1 != NULL);
+
+ pdu1->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task1->scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task1->scsi.lun = &lun1;
+ spdk_iscsi_task_set_pdu(task1, pdu1);
+
+ rc = spdk_add_transfer_task(&conn, task1);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ task2 = spdk_iscsi_task_get(&conn, NULL, NULL);
+ SPDK_CU_ASSERT_FATAL(task2 != NULL);
+ pdu2 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu2 != NULL);
+
+ pdu2->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task2->scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task2->scsi.lun = &lun1;
+ spdk_iscsi_task_set_pdu(task2, pdu2);
+
+ rc = spdk_add_transfer_task(&conn, task2);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ task3 = spdk_iscsi_task_get(&conn, NULL, NULL);
+ SPDK_CU_ASSERT_FATAL(task3 != NULL);
+ pdu3 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu3 != NULL);
+
+ pdu3->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task3->scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task3->scsi.lun = &lun1;
+ spdk_iscsi_task_set_pdu(task3, pdu3);
+
+ rc = spdk_add_transfer_task(&conn, task3);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ task4 = spdk_iscsi_task_get(&conn, NULL, NULL);
+ SPDK_CU_ASSERT_FATAL(task4 != NULL);
+ pdu4 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu4 != NULL);
+
+ pdu4->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task4->scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task4->scsi.lun = &lun2;
+ spdk_iscsi_task_set_pdu(task4, pdu4);
+
+ rc = spdk_add_transfer_task(&conn, task4);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ task5 = spdk_iscsi_task_get(&conn, NULL, NULL);
+ SPDK_CU_ASSERT_FATAL(task5 != NULL);
+ pdu5 = spdk_get_pdu();
+ SPDK_CU_ASSERT_FATAL(pdu5 != NULL);
+
+ pdu5->data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task5->scsi.transfer_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH;
+ task5->scsi.lun = &lun2;
+ spdk_iscsi_task_set_pdu(task5, pdu5);
+
+ rc = spdk_add_transfer_task(&conn, task5);
+ CU_ASSERT(rc == SPDK_SUCCESS);
+
+ CU_ASSERT(conn.ttt == 4);
+
+ CU_ASSERT(spdk_get_transfer_task(&conn, 1) == task1);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 2) == task2);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 3) == task3);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 4) == task4);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 5) == NULL);
+
+ spdk_clear_all_transfer_task(&conn, &lun1);
+
+ CU_ASSERT(TAILQ_EMPTY(&conn.queued_r2t_tasks));
+ CU_ASSERT(spdk_get_transfer_task(&conn, 1) == NULL);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 2) == NULL);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 3) == NULL);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 4) == task4);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 5) == task5);
+
+ spdk_clear_all_transfer_task(&conn, NULL);
+
+ CU_ASSERT(spdk_get_transfer_task(&conn, 4) == NULL);
+ CU_ASSERT(spdk_get_transfer_task(&conn, 5) == NULL);
+
+ CU_ASSERT(TAILQ_EMPTY(&conn.active_r2t_tasks));
+ while (!TAILQ_EMPTY(&g_write_pdu_list)) {
+ pdu = TAILQ_FIRST(&g_write_pdu_list);
+ TAILQ_REMOVE(&g_write_pdu_list, pdu, tailq);
+ spdk_put_pdu(pdu);
+ }
+
+ spdk_put_pdu(pdu5);
+ spdk_put_pdu(pdu4);
+ spdk_put_pdu(pdu3);
+ spdk_put_pdu(pdu2);
+ spdk_put_pdu(pdu1);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("iscsi_suite", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "login check target test", op_login_check_target_test) == NULL
+ || CU_add_test(suite, "maxburstlength test", maxburstlength_test) == NULL
+ || CU_add_test(suite, "underflow for read transfer test",
+ underflow_for_read_transfer_test) == NULL
+ || CU_add_test(suite, "underflow for zero read transfer test",
+ underflow_for_zero_read_transfer_test) == NULL
+ || CU_add_test(suite, "underflow for request sense test",
+ underflow_for_request_sense_test) == NULL
+ || CU_add_test(suite, "underflow for check condition test",
+ underflow_for_check_condition_test) == NULL
+ || CU_add_test(suite, "add transfer task test", add_transfer_task_test) == NULL
+ || CU_add_test(suite, "get transfer task test", get_transfer_task_test) == NULL
+ || CU_add_test(suite, "del transfer task test", del_transfer_task_test) == NULL
+ || CU_add_test(suite, "clear all transfer tasks test",
+ clear_all_transfer_tasks_test) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/iscsi/param.c/.gitignore b/src/spdk/test/unit/lib/iscsi/param.c/.gitignore
new file mode 100644
index 00000000..26992146
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/param.c/.gitignore
@@ -0,0 +1 @@
+param_ut
diff --git a/src/spdk/test/unit/lib/iscsi/param.c/Makefile b/src/spdk/test/unit/lib/iscsi/param.c/Makefile
new file mode 100644
index 00000000..bc944ae9
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/param.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = param_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/iscsi/param.c/param_ut.c b/src/spdk/test/unit/lib/iscsi/param.c/param_ut.c
new file mode 100644
index 00000000..40941923
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/param.c/param_ut.c
@@ -0,0 +1,397 @@
+/*-
+ * 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/scsi.h"
+
+#include "spdk_cunit.h"
+
+#include "../common.c"
+#include "iscsi/param.c"
+
+struct spdk_iscsi_globals g_spdk_iscsi;
+
+struct spdk_iscsi_tgt_node *
+spdk_iscsi_find_tgt_node(const char *target_name)
+{
+ return NULL;
+}
+
+bool
+spdk_iscsi_tgt_node_access(struct spdk_iscsi_conn *conn,
+ struct spdk_iscsi_tgt_node *target,
+ const char *iqn, const char *addr)
+{
+ return false;
+}
+
+int
+spdk_iscsi_send_tgts(struct spdk_iscsi_conn *conn, const char *iiqn,
+ const char *iaddr,
+ const char *tiqn, uint8_t *data, int alloc_len, int data_len)
+{
+ return 0;
+}
+
+static void
+burst_length_param_negotation(int FirstBurstLength, int MaxBurstLength,
+ int initialR2T)
+{
+ struct spdk_iscsi_sess sess;
+ struct spdk_iscsi_conn conn;
+ struct iscsi_param *params;
+ struct iscsi_param **params_p;
+ char data[8192];
+ int rc;
+ int total, len;
+
+ total = 0;
+ params = NULL;
+ params_p = &params;
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&conn, 0, sizeof(conn));
+ memset(data, 0, 8192);
+
+ sess.ExpCmdSN = 0;
+ sess.MaxCmdSN = 64;
+ sess.session_type = SESSION_TYPE_NORMAL;
+ sess.params = NULL;
+ sess.MaxBurstLength = 65536;
+ sess.InitialR2T = true;
+ sess.FirstBurstLength = SPDK_ISCSI_FIRST_BURST_LENGTH;
+ sess.MaxOutstandingR2T = 1;
+
+ /* set default params */
+ rc = spdk_iscsi_sess_params_init(&sess.params);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_param_set_int(sess.params, "FirstBurstLength",
+ sess.FirstBurstLength);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_param_set_int(sess.params, "MaxBurstLength",
+ sess.MaxBurstLength);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_param_set(sess.params, "InitialR2T",
+ sess.InitialR2T ? "Yes" : "No");
+ CU_ASSERT(rc == 0);
+
+ conn.full_feature = 1;
+ conn.sess = &sess;
+ conn.MaxRecvDataSegmentLength = 65536;
+
+ rc = spdk_iscsi_conn_params_init(&conn.params);
+ CU_ASSERT(rc == 0);
+
+ /* construct the data */
+ len = snprintf(data + total, 8192 - total, "%s=%d",
+ "FirstBurstLength", FirstBurstLength);
+ total += len + 1;
+
+ len = snprintf(data + total, 8192 - total, "%s=%d",
+ "MaxBurstLength", MaxBurstLength);
+ total += len + 1;
+
+ len = snprintf(data + total, 8192 - total, "%s=%d",
+ "InitialR2T", initialR2T);
+ total += len + 1;
+
+ /* add one extra NUL byte at the end to match real iSCSI params */
+ total++;
+
+ /* store incoming parameters */
+ rc = spdk_iscsi_parse_params(params_p, data, total, false, NULL);
+ CU_ASSERT(rc == 0);
+
+ /* negotiate parameters */
+ rc = spdk_iscsi_negotiate_params(&conn, params_p,
+ data, 8192, rc);
+ CU_ASSERT(rc > 0);
+
+ rc = spdk_iscsi_copy_param2var(&conn);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(conn.sess->FirstBurstLength <= SPDK_ISCSI_FIRST_BURST_LENGTH);
+ CU_ASSERT(conn.sess->FirstBurstLength <= conn.sess->MaxBurstLength);
+ CU_ASSERT(conn.sess->MaxBurstLength <= SPDK_ISCSI_MAX_BURST_LENGTH);
+ CU_ASSERT(conn.sess->MaxOutstandingR2T == 1);
+
+ spdk_iscsi_param_free(sess.params);
+ spdk_iscsi_param_free(conn.params);
+ spdk_iscsi_param_free(*params_p);
+}
+
+static void
+param_negotiation_test(void)
+{
+ burst_length_param_negotation(8192, 16384, 0);
+ burst_length_param_negotation(8192, 16384, 1);
+ burst_length_param_negotation(8192, 1024, 1);
+ burst_length_param_negotation(8192, 1024, 0);
+ burst_length_param_negotation(512, 1024, 1);
+ burst_length_param_negotation(512, 1024, 0);
+}
+
+static void
+list_negotiation_test(void)
+{
+ int add_param_value = 0;
+ struct iscsi_param param = {};
+ char *new_val;
+ char valid_list_buf[1024];
+ char in_val_buf[1024];
+
+#define TEST_LIST(valid_list, in_val, expected_result) \
+ do { \
+ snprintf(valid_list_buf, sizeof(valid_list_buf), "%s", valid_list); \
+ snprintf(in_val_buf, sizeof(in_val_buf), "%s", in_val); \
+ new_val = spdk_iscsi_negotiate_param_list(&add_param_value, &param, valid_list_buf, in_val_buf, NULL); \
+ if (expected_result) { \
+ SPDK_CU_ASSERT_FATAL(new_val != NULL); \
+ CU_ASSERT_STRING_EQUAL(new_val, expected_result); \
+ } \
+ } while (0)
+
+ TEST_LIST("None", "None", "None");
+ TEST_LIST("CHAP,None", "None", "None");
+ TEST_LIST("CHAP,None", "CHAP", "CHAP");
+ TEST_LIST("KRB5,SRP,CHAP,None", "SRP,CHAP,None", "SRP");
+ TEST_LIST("KRB5,SRP,CHAP,None", "CHAP,SRP,None", "CHAP");
+ TEST_LIST("KRB5,SRP,CHAP,None", "SPKM1,SRP,CHAP,None", "SRP");
+ TEST_LIST("KRB5,SRP,None", "CHAP,None", "None");
+}
+
+#define PARSE(strconst, partial_enabled, partial_text) \
+ data = strconst; \
+ len = sizeof(strconst); \
+ rc = spdk_iscsi_parse_params(&params, data, len, partial_enabled, partial_text)
+
+#define EXPECT_VAL(key, expected_value) \
+ { \
+ const char *val = spdk_iscsi_param_get_val(params, key); \
+ CU_ASSERT(val != NULL); \
+ if (val != NULL) { \
+ CU_ASSERT(strcmp(val, expected_value) == 0); \
+ } \
+ }
+
+#define EXPECT_NULL(key) \
+ CU_ASSERT(spdk_iscsi_param_get_val(params, key) == NULL)
+
+static void
+parse_valid_test(void)
+{
+ struct iscsi_param *params = NULL;
+ int rc;
+ char *data;
+ int len;
+ char *partial_parameter = NULL;
+
+ /* simple test with a single key=value */
+ PARSE("Abc=def\0", false, NULL);
+ CU_ASSERT(rc == 0);
+ EXPECT_VAL("Abc", "def");
+
+ /* multiple key=value pairs */
+ PARSE("Aaa=bbbbbb\0Xyz=test\0", false, NULL);
+ CU_ASSERT(rc == 0);
+ EXPECT_VAL("Aaa", "bbbbbb");
+ EXPECT_VAL("Xyz", "test");
+
+ /* value with embedded '=' */
+ PARSE("A=b=c\0", false, NULL);
+ CU_ASSERT(rc == 0);
+ EXPECT_VAL("A", "b=c");
+
+ /* CHAP_C=AAAA.... with value length 8192 */
+ len = strlen("CHAP_C=") + ISCSI_TEXT_MAX_VAL_LEN + 1/* null terminators */;
+ data = malloc(len);
+ SPDK_CU_ASSERT_FATAL(data != NULL);
+ memset(data, 'A', len);
+ memcpy(data, "CHAP_C", 6);
+ data[6] = '=';
+ data[len - 1] = '\0';
+ rc = spdk_iscsi_parse_params(&params, data, len, false, NULL);
+ CU_ASSERT(rc == 0);
+ free(data);
+
+ /* partial parameter: value is partial */
+ PARSE("C=AAA\0D=B", true, &partial_parameter);
+ SPDK_CU_ASSERT_FATAL(partial_parameter != NULL);
+ CU_ASSERT_STRING_EQUAL(partial_parameter, "D=B");
+ CU_ASSERT(rc == 0);
+ EXPECT_VAL("C", "AAA");
+ EXPECT_NULL("D");
+ PARSE("XXXX\0E=UUUU\0", false, &partial_parameter);
+ CU_ASSERT(rc == 0);
+ EXPECT_VAL("D", "BXXXX");
+ EXPECT_VAL("E", "UUUU");
+ CU_ASSERT_PTR_NULL(partial_parameter);
+
+ /* partial parameter: key is partial */
+ PARSE("IAMAFAK", true, &partial_parameter);
+ CU_ASSERT_STRING_EQUAL(partial_parameter, "IAMAFAK");
+ CU_ASSERT(rc == 0);
+ EXPECT_NULL("IAMAFAK");
+ PARSE("EDKEY=TTTT\0F=IIII", false, &partial_parameter);
+ CU_ASSERT(rc == 0);
+ EXPECT_VAL("IAMAFAKEDKEY", "TTTT");
+ EXPECT_VAL("F", "IIII");
+ CU_ASSERT_PTR_NULL(partial_parameter);
+
+ /* Second partial parameter is the only parameter */
+ PARSE("OOOO", true, &partial_parameter);
+ CU_ASSERT_STRING_EQUAL(partial_parameter, "OOOO");
+ CU_ASSERT(rc == 0);
+ EXPECT_NULL("OOOO");
+ PARSE("LL=MMMM", false, &partial_parameter);
+ CU_ASSERT(rc == 0);
+ EXPECT_VAL("OOOOLL", "MMMM");
+ CU_ASSERT_PTR_NULL(partial_parameter);
+
+ spdk_iscsi_param_free(params);
+}
+
+static void
+parse_invalid_test(void)
+{
+ struct iscsi_param *params = NULL;
+ int rc;
+ char *data;
+ int len;
+
+ /* key without '=' */
+ PARSE("Abc\0", false, NULL);
+ CU_ASSERT(rc != 0);
+ EXPECT_NULL("Abc");
+
+ /* multiple key=value pairs, one missing '=' */
+ PARSE("Abc=def\0Xyz\0Www=test\0", false, NULL);
+ CU_ASSERT(rc != 0);
+ EXPECT_VAL("Abc", "def");
+ EXPECT_NULL("Xyz");
+ EXPECT_NULL("Www");
+
+ /* empty key */
+ PARSE("=abcdef", false, NULL);
+ CU_ASSERT(rc != 0);
+ EXPECT_NULL("");
+
+ /* CHAP_C=AAAA.... with value length 8192 + 1 */
+ len = strlen("CHAP_C=") + ISCSI_TEXT_MAX_VAL_LEN + 1 /* max value len + 1 */ +
+ 1 /* null terminators */;
+ data = malloc(len);
+ SPDK_CU_ASSERT_FATAL(data != NULL);
+ memset(data, 'A', len);
+ memcpy(data, "CHAP_C", 6);
+ data[6] = '=';
+ data[len - 1] = '\0';
+ rc = spdk_iscsi_parse_params(&params, data, len, false, NULL);
+ free(data);
+ CU_ASSERT(rc != 0);
+ EXPECT_NULL("CHAP_C");
+
+ /* Test simple value, length of value bigger than 255 */
+ len = strlen("A=") + ISCSI_TEXT_MAX_SIMPLE_VAL_LEN + 1 /* max simple value len + 1 */ +
+ 1 /* null terminators */;
+ data = malloc(len);
+ SPDK_CU_ASSERT_FATAL(data != NULL);
+ memset(data, 'A', len);
+ data[1] = '=';
+ data[len - 1] = '\0';
+ rc = spdk_iscsi_parse_params(&params, data, len, false, NULL);
+ free(data);
+ CU_ASSERT(rc != 0);
+ EXPECT_NULL("A");
+
+ /* key length bigger than 63 */
+ len = ISCSI_TEXT_MAX_KEY_LEN + 1 /* max key length + 1 */ + 1 /* = */ + 1 /* A */ +
+ 1 /* null terminators */;
+ data = malloc(len);
+ SPDK_CU_ASSERT_FATAL(data != NULL);
+ memset(data, 'A', len);
+ data[64] = '=';
+ data[len - 1] = '\0';
+ rc = spdk_iscsi_parse_params(&params, data, len, false, NULL);
+ free(data);
+ CU_ASSERT(rc != 0);
+ EXPECT_NULL("A");
+
+ /* duplicated key */
+ PARSE("B=BB", false, NULL);
+ CU_ASSERT(rc == 0);
+ PARSE("B=BBBB", false, NULL);
+ CU_ASSERT(rc != 0);
+ EXPECT_VAL("B", "BB");
+
+ spdk_iscsi_param_free(params);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("iscsi_suite", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "param negotiation test",
+ param_negotiation_test) == NULL ||
+ CU_add_test(suite, "list negotiation test",
+ list_negotiation_test) == NULL ||
+ CU_add_test(suite, "parse valid test",
+ parse_valid_test) == NULL ||
+ CU_add_test(suite, "parse invalid test",
+ parse_invalid_test) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/iscsi/portal_grp.c/.gitignore b/src/spdk/test/unit/lib/iscsi/portal_grp.c/.gitignore
new file mode 100644
index 00000000..106ffebc
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/portal_grp.c/.gitignore
@@ -0,0 +1 @@
+portal_grp_ut
diff --git a/src/spdk/test/unit/lib/iscsi/portal_grp.c/Makefile b/src/spdk/test/unit/lib/iscsi/portal_grp.c/Makefile
new file mode 100644
index 00000000..ab28cabb
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/portal_grp.c/Makefile
@@ -0,0 +1,42 @@
+#
+## 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.app.mk
+
+SPDK_LIB_LIST = conf
+
+TEST_FILE = portal_grp_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/iscsi/portal_grp.c/portal_grp_ut.c b/src/spdk/test/unit/lib/iscsi/portal_grp.c/portal_grp_ut.c
new file mode 100644
index 00000000..77351f0a
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/portal_grp.c/portal_grp_ut.c
@@ -0,0 +1,477 @@
+/*-
+ * 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_cunit.h"
+
+#include "../common.c"
+#include "iscsi/portal_grp.c"
+#include "unit/lib/json_mock.c"
+
+struct spdk_iscsi_globals g_spdk_iscsi;
+
+static int
+test_setup(void)
+{
+ TAILQ_INIT(&g_spdk_iscsi.portal_head);
+ TAILQ_INIT(&g_spdk_iscsi.pg_head);
+ pthread_mutex_init(&g_spdk_iscsi.mutex, NULL);
+ return 0;
+}
+
+static void
+portal_create_ipv4_normal_case(void)
+{
+ struct spdk_iscsi_portal *p;
+
+ const char *host = "192.168.2.0";
+ const char *port = "3260";
+ const char *cpumask = "1";
+
+ p = spdk_iscsi_portal_create(host, port, cpumask);
+ CU_ASSERT(p != NULL);
+
+ spdk_iscsi_portal_destroy(p);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+portal_create_ipv6_normal_case(void)
+{
+ struct spdk_iscsi_portal *p;
+
+ const char *host = "[2001:ad6:1234::]";
+ const char *port = "3260";
+ const char *cpumask = "1";
+
+ p = spdk_iscsi_portal_create(host, port, cpumask);
+ CU_ASSERT(p != NULL);
+
+ spdk_iscsi_portal_destroy(p);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+portal_create_ipv4_wildcard_case(void)
+{
+ struct spdk_iscsi_portal *p;
+
+ const char *host = "*";
+ const char *port = "3260";
+ const char *cpumask = "1";
+
+ p = spdk_iscsi_portal_create(host, port, cpumask);
+ CU_ASSERT(p != NULL);
+
+ spdk_iscsi_portal_destroy(p);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+portal_create_ipv6_wildcard_case(void)
+{
+ struct spdk_iscsi_portal *p;
+
+ const char *host = "[*]";
+ const char *port = "3260";
+ const char *cpumask = "1";
+
+ p = spdk_iscsi_portal_create(host, port, cpumask);
+ CU_ASSERT(p != NULL);
+
+ spdk_iscsi_portal_destroy(p);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+portal_create_cpumask_null_case(void)
+{
+ struct spdk_iscsi_portal *p;
+
+ const char *host = "192.168.2.0";
+ const char *port = "3260";
+ const char *cpumask = NULL;
+
+ p = spdk_iscsi_portal_create(host, port, cpumask);
+ CU_ASSERT(p != NULL);
+
+ spdk_iscsi_portal_destroy(p);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+portal_create_cpumask_no_bit_on_case(void)
+{
+ struct spdk_iscsi_portal *p;
+
+ const char *host = "192.168.2.0";
+ const char *port = "3260";
+ const char *cpumask = "0";
+
+ p = spdk_iscsi_portal_create(host, port, cpumask);
+ CU_ASSERT(p == NULL);
+}
+
+static void
+portal_create_twice_case(void)
+{
+ struct spdk_iscsi_portal *p1, *p2;
+
+ const char *host = "192.168.2.0";
+ const char *port = "3260";
+ const char *cpumask = "1";
+
+ p1 = spdk_iscsi_portal_create(host, port, cpumask);
+ CU_ASSERT(p1 != NULL);
+
+ p2 = spdk_iscsi_portal_create(host, port, cpumask);
+ CU_ASSERT(p2 == NULL);
+
+ spdk_iscsi_portal_destroy(p1);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+parse_portal_ipv4_normal_case(void)
+{
+ const char *string = "192.168.2.0:3260@1";
+ const char *host_str = "192.168.2.0";
+ const char *port_str = "3260";
+ struct spdk_cpuset *cpumask_val;
+ struct spdk_iscsi_portal *p = NULL;
+ int rc;
+
+ cpumask_val = spdk_cpuset_alloc();
+ SPDK_CU_ASSERT_FATAL(cpumask_val != NULL);
+
+ spdk_cpuset_set_cpu(cpumask_val, 0, true);
+
+ rc = spdk_iscsi_parse_portal(string, &p, 0);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(p != NULL);
+ CU_ASSERT(strcmp(p->host, host_str) == 0);
+ CU_ASSERT(strcmp(p->port, port_str) == 0);
+ CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val));
+
+ spdk_iscsi_portal_destroy(p);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+
+ spdk_cpuset_free(cpumask_val);
+}
+
+static void
+parse_portal_ipv6_normal_case(void)
+{
+ const char *string = "[2001:ad6:1234::]:3260@1";
+ const char *host_str = "[2001:ad6:1234::]";
+ const char *port_str = "3260";
+ struct spdk_cpuset *cpumask_val;
+ struct spdk_iscsi_portal *p = NULL;
+ int rc;
+
+ cpumask_val = spdk_cpuset_alloc();
+ SPDK_CU_ASSERT_FATAL(cpumask_val != NULL);
+
+ spdk_cpuset_set_cpu(cpumask_val, 0, true);
+
+ rc = spdk_iscsi_parse_portal(string, &p, 0);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(p != NULL);
+ CU_ASSERT(strcmp(p->host, host_str) == 0);
+ CU_ASSERT(strcmp(p->port, port_str) == 0);
+ CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val));
+
+ spdk_iscsi_portal_destroy(p);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+
+ spdk_cpuset_free(cpumask_val);
+}
+
+static void
+parse_portal_ipv4_skip_cpumask_case(void)
+{
+ const char *string = "192.168.2.0:3260";
+ const char *host_str = "192.168.2.0";
+ const char *port_str = "3260";
+ struct spdk_cpuset *cpumask_val;
+ struct spdk_iscsi_portal *p = NULL;
+ int rc;
+
+ cpumask_val = spdk_app_get_core_mask();
+
+ rc = spdk_iscsi_parse_portal(string, &p, 0);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(p != NULL);
+ CU_ASSERT(strcmp(p->host, host_str) == 0);
+ CU_ASSERT(strcmp(p->port, port_str) == 0);
+ CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val));
+
+ spdk_iscsi_portal_destroy(p);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+parse_portal_ipv6_skip_cpumask_case(void)
+{
+ const char *string = "[2001:ad6:1234::]:3260";
+ const char *host_str = "[2001:ad6:1234::]";
+ const char *port_str = "3260";
+ struct spdk_cpuset *cpumask_val;
+ struct spdk_iscsi_portal *p = NULL;
+ int rc;
+
+ cpumask_val = spdk_app_get_core_mask();
+
+ rc = spdk_iscsi_parse_portal(string, &p, 0);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(p != NULL);
+ CU_ASSERT(strcmp(p->host, host_str) == 0);
+ CU_ASSERT(strcmp(p->port, port_str) == 0);
+ CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val));
+
+ spdk_iscsi_portal_destroy(p);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+parse_portal_ipv4_skip_port_and_cpumask_case(void)
+{
+ const char *string = "192.168.2.0";
+ const char *host_str = "192.168.2.0";
+ const char *port_str = "3260";
+ struct spdk_cpuset *cpumask_val;
+ struct spdk_iscsi_portal *p = NULL;
+ int rc;
+
+ cpumask_val = spdk_app_get_core_mask();
+
+ rc = spdk_iscsi_parse_portal(string, &p, 0);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(p != NULL);
+ CU_ASSERT(strcmp(p->host, host_str) == 0);
+ CU_ASSERT(strcmp(p->port, port_str) == 0);
+ CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val));
+
+ spdk_iscsi_portal_destroy(p);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+parse_portal_ipv6_skip_port_and_cpumask_case(void)
+{
+ const char *string = "[2001:ad6:1234::]";
+ const char *host_str = "[2001:ad6:1234::]";
+ const char *port_str = "3260";
+ struct spdk_cpuset *cpumask_val;
+ struct spdk_iscsi_portal *p = NULL;
+ int rc;
+
+ cpumask_val = spdk_app_get_core_mask();
+
+ rc = spdk_iscsi_parse_portal(string, &p, 0);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(p != NULL);
+ CU_ASSERT(strcmp(p->host, host_str) == 0);
+ CU_ASSERT(strcmp(p->port, port_str) == 0);
+ CU_ASSERT(spdk_cpuset_equal(p->cpumask, cpumask_val));
+
+ spdk_iscsi_portal_destroy(p);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+portal_grp_register_unregister_case(void)
+{
+ struct spdk_iscsi_portal *p;
+ struct spdk_iscsi_portal_grp *pg1, *pg2;
+ int rc;
+ const char *host = "192.168.2.0";
+ const char *port = "3260";
+ const char *cpumask = "1";
+
+ pg1 = spdk_iscsi_portal_grp_create(1);
+ CU_ASSERT(pg1 != NULL);
+
+ p = spdk_iscsi_portal_create(host, port, cpumask);
+ CU_ASSERT(p != NULL);
+
+ spdk_iscsi_portal_grp_add_portal(pg1, p);
+
+ rc = spdk_iscsi_portal_grp_register(pg1);
+ CU_ASSERT(rc == 0);
+
+ pg2 = spdk_iscsi_portal_grp_unregister(1);
+ CU_ASSERT(pg2 != NULL);
+ CU_ASSERT(pg1 == pg2);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.pg_head));
+
+ spdk_iscsi_portal_grp_destroy(pg1);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+portal_grp_register_twice_case(void)
+{
+ struct spdk_iscsi_portal *p;
+ struct spdk_iscsi_portal_grp *pg1, *pg2;
+ int rc;
+ const char *host = "192.168.2.0";
+ const char *port = "3260";
+ const char *cpumask = "1";
+
+ pg1 = spdk_iscsi_portal_grp_create(1);
+ CU_ASSERT(pg1 != NULL);
+
+ p = spdk_iscsi_portal_create(host, port, cpumask);
+ CU_ASSERT(p != NULL);
+
+ spdk_iscsi_portal_grp_add_portal(pg1, p);
+
+ rc = spdk_iscsi_portal_grp_register(pg1);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_portal_grp_register(pg1);
+ CU_ASSERT(rc != 0);
+
+ pg2 = spdk_iscsi_portal_grp_unregister(1);
+ CU_ASSERT(pg2 != NULL);
+ CU_ASSERT(pg1 == pg2);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.pg_head));
+
+ spdk_iscsi_portal_grp_destroy(pg1);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+}
+
+static void
+portal_grp_add_delete_case(void)
+{
+ struct spdk_iscsi_portal_grp *pg1, *pg2;
+ struct spdk_iscsi_portal *p;
+ int rc;
+
+ const char *host = "192.168.2.0";
+ const char *port = "3260";
+ const char *cpumask = "1";
+
+ /* internal of add_portal_group */
+ pg1 = spdk_iscsi_portal_grp_create(1);
+ CU_ASSERT(pg1 != NULL);
+
+ p = spdk_iscsi_portal_create(host, port, cpumask);
+ CU_ASSERT(p != NULL);
+
+ spdk_iscsi_portal_grp_add_portal(pg1, p);
+
+ rc = spdk_iscsi_portal_grp_open(pg1);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_iscsi_portal_grp_register(pg1);
+ CU_ASSERT(rc == 0);
+
+ /* internal of delete_portal_group */
+ pg2 = spdk_iscsi_portal_grp_unregister(1);
+ CU_ASSERT(pg2 != NULL);
+ CU_ASSERT(pg1 == pg2);
+
+ spdk_iscsi_portal_grp_release(pg2);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.portal_head));
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_iscsi.pg_head));
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("portal_grp_suite", test_setup, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "portal create ipv4 normal case",
+ portal_create_ipv4_normal_case) == NULL
+ || CU_add_test(suite, "portal create ipv6 normal case",
+ portal_create_ipv6_normal_case) == NULL
+ || CU_add_test(suite, "portal create ipv4 wildcard case",
+ portal_create_ipv4_wildcard_case) == NULL
+ || CU_add_test(suite, "portal create ipv6 wildcard case",
+ portal_create_ipv6_wildcard_case) == NULL
+ || CU_add_test(suite, "portal create cpumask NULL case",
+ portal_create_cpumask_null_case) == NULL
+ || CU_add_test(suite, "portal create cpumask no bit on case",
+ portal_create_cpumask_no_bit_on_case) == NULL
+ || CU_add_test(suite, "portal create twice case",
+ portal_create_twice_case) == NULL
+ || CU_add_test(suite, "parse portal ipv4 normal case",
+ parse_portal_ipv4_normal_case) == NULL
+ || CU_add_test(suite, "parse portal ipv6 normal case",
+ parse_portal_ipv6_normal_case) == NULL
+ || CU_add_test(suite, "parse portal ipv4 skip cpumask case",
+ parse_portal_ipv4_skip_cpumask_case) == NULL
+ || CU_add_test(suite, "parse portal ipv6 skip cpumask case",
+ parse_portal_ipv6_skip_cpumask_case) == NULL
+ || CU_add_test(suite, "parse portal ipv4 skip port and cpumask case",
+ parse_portal_ipv4_skip_port_and_cpumask_case) == NULL
+ || CU_add_test(suite, "parse portal ipv6 skip port and cpumask case",
+ parse_portal_ipv6_skip_port_and_cpumask_case) == NULL
+ || CU_add_test(suite, "portal group register/unregister case",
+ portal_grp_register_unregister_case) == NULL
+ || CU_add_test(suite, "portal group register twice case",
+ portal_grp_register_twice_case) == NULL
+ || CU_add_test(suite, "portal group add/delete case",
+ portal_grp_add_delete_case) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/iscsi/tgt_node.c/.gitignore b/src/spdk/test/unit/lib/iscsi/tgt_node.c/.gitignore
new file mode 100644
index 00000000..010d84b8
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/tgt_node.c/.gitignore
@@ -0,0 +1 @@
+tgt_node_ut
diff --git a/src/spdk/test/unit/lib/iscsi/tgt_node.c/Makefile b/src/spdk/test/unit/lib/iscsi/tgt_node.c/Makefile
new file mode 100644
index 00000000..8cdf3ef3
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/tgt_node.c/Makefile
@@ -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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+SPDK_LIB_LIST = conf
+TEST_FILE = tgt_node_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node.conf b/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node.conf
new file mode 100644
index 00000000..6bf5aa66
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node.conf
@@ -0,0 +1,95 @@
+[Global]
+
+# Test that parsing fails if there is no TargetName
+[Failure0]
+ TargetAlias "Data Disk1"
+ Mapping PortalGroup1 InitiatorGroup1
+ AuthMethod Auto
+ AuthGroup AuthGroup1
+ UseDigest Auto
+ QueueDepth 128
+ LUN0 Malloc0
+ LUN1 Malloc1
+
+# Test that parsing fails if there is no Mapping
+[Failure1]
+ TargetName target1
+ TargetAlias "Data Disk1"
+ AuthMethod Auto
+ AuthGroup AuthGroup1
+ UseDigest Auto
+ QueueDepth 128
+ LUN0 Malloc0
+ LUN1 Malloc1
+
+# Test that parsing fails if Mapping does not define Portal or InitiatorGroup
+[Failure2]
+ TargetName target1
+ TargetAlias "Data Disk1"
+ Mapping
+ AuthMethod Auto
+ AuthGroup AuthGroup1
+ UseDigest Auto
+ QueueDepth 128
+ LUN0 Malloc0
+ LUN1 Malloc1
+
+# Test that parsing fails if Mapping does not define InitiatorGroup
+[Failure3]
+ TargetName target1
+ TargetAlias "Data Disk1"
+ Mapping PortalGroup1
+ AuthMethod Auto
+ AuthGroup AuthGroup1
+ UseDigest Auto
+ QueueDepth 128
+ LUN0 Malloc0
+ LUN1 Malloc1
+
+# Test that parsing fails if Mapping switches PortalGroup/InitiatorGroup order
+[Failure4]
+ TargetName target1
+ TargetAlias "Data Disk1"
+ Mapping InitiatorGroup1 PortalGroup1
+ AuthMethod Auto
+ AuthGroup AuthGroup1
+ UseDigest Auto
+ QueueDepth 128
+ LUN0 Malloc0
+ LUN1 Malloc1
+
+# Test that parsing fails if Mapping uses invalid InitiatorGroup0
+[Failure5]
+ TargetName target1
+ TargetAlias "Data Disk1"
+ Mapping PortalGroup1 InitiatorGroup0
+ AuthMethod Auto
+ AuthGroup AuthGroup1
+ UseDigest Auto
+ QueueDepth 128
+ LUN0 Malloc0
+ LUN1 Malloc1
+
+# Test that parsing fails if Mapping uses invalid PortalGroup0
+[Failure6]
+ TargetName target1
+ TargetAlias "Data Disk1"
+ Mapping PortalGroup0 InitiatorGroup1
+ AuthMethod Auto
+ AuthGroup AuthGroup1
+ UseDigest Auto
+ QueueDepth 128
+ LUN0 Malloc0
+ LUN1 Malloc1
+
+# Test that parsing fails if AuthMethod is invalid
+[Failure7]
+ TargetName target1
+ TargetAlias "Data Disk1"
+ Mapping PortalGroup1 InitiatorGroup1
+ AuthMethod SomeGarbage
+ AuthGroup AuthGroup1
+ UseDigest Auto
+ QueueDepth 128
+ LUN0 Malloc0
+ LUN1 Malloc1
diff --git a/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node_ut.c b/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node_ut.c
new file mode 100644
index 00000000..eda02db6
--- /dev/null
+++ b/src/spdk/test/unit/lib/iscsi/tgt_node.c/tgt_node_ut.c
@@ -0,0 +1,886 @@
+/*-
+ * 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/scsi.h"
+
+#include "CUnit/Basic.h"
+#include "spdk_internal/mock.h"
+
+#include "../common.c"
+#include "iscsi/tgt_node.c"
+#include "scsi/scsi_internal.h"
+#include "unit/lib/json_mock.c"
+
+struct spdk_iscsi_globals g_spdk_iscsi;
+
+const char *config_file;
+
+DEFINE_STUB(spdk_scsi_dev_get_id,
+ int,
+ (const struct spdk_scsi_dev *dev),
+ 0);
+
+DEFINE_STUB(spdk_scsi_lun_get_bdev_name,
+ const char *,
+ (const struct spdk_scsi_lun *lun),
+ NULL);
+
+DEFINE_STUB(spdk_scsi_lun_get_id,
+ int,
+ (const struct spdk_scsi_lun *lun),
+ 0);
+
+bool
+spdk_sock_is_ipv6(struct spdk_sock *sock)
+{
+ return false;
+}
+
+bool
+spdk_sock_is_ipv4(struct spdk_sock *sock)
+{
+ return false;
+}
+
+struct spdk_iscsi_portal_grp *
+spdk_iscsi_portal_grp_find_by_tag(int tag)
+{
+ return NULL;
+}
+
+struct spdk_iscsi_init_grp *
+spdk_iscsi_init_grp_find_by_tag(int tag)
+{
+ return NULL;
+}
+
+struct spdk_scsi_lun *
+spdk_scsi_dev_get_lun(struct spdk_scsi_dev *dev, int lun_id)
+{
+ if (lun_id < 0 || lun_id >= SPDK_SCSI_DEV_MAX_LUN) {
+ return NULL;
+ }
+
+ return dev->lun[lun_id];
+}
+
+int
+spdk_scsi_dev_add_lun(struct spdk_scsi_dev *dev, const char *bdev_name, int lun_id,
+ void (*hotremove_cb)(const struct spdk_scsi_lun *, void *),
+ void *hotremove_ctx)
+{
+ if (bdev_name == NULL) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+static void
+add_lun_test_cases(void)
+{
+ struct spdk_iscsi_tgt_node tgtnode;
+ int lun_id = 0;
+ char *bdev_name = NULL;
+ struct spdk_scsi_dev scsi_dev;
+ int rc;
+
+ memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node));
+ memset(&scsi_dev, 0, sizeof(struct spdk_scsi_dev));
+
+ /* case 1 */
+ tgtnode.num_active_conns = 1;
+
+ rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id);
+ CU_ASSERT(rc != 0);
+
+ /* case 2 */
+ tgtnode.num_active_conns = 0;
+ lun_id = -2;
+
+ rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id);
+ CU_ASSERT(rc != 0);
+
+ /* case 3 */
+ lun_id = SPDK_SCSI_DEV_MAX_LUN;
+
+ rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id);
+ CU_ASSERT(rc != 0);
+
+ /* case 4 */
+ lun_id = -1;
+ tgtnode.dev = NULL;
+
+ rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id);
+ CU_ASSERT(rc != 0);
+
+ /* case 5 */
+ tgtnode.dev = &scsi_dev;
+
+ rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id);
+ CU_ASSERT(rc != 0);
+
+ /* case 6 */
+ bdev_name = "LUN0";
+
+ rc = spdk_iscsi_tgt_node_add_lun(&tgtnode, bdev_name, lun_id);
+ CU_ASSERT(rc == 0);
+}
+
+static void
+config_file_fail_cases(void)
+{
+ struct spdk_conf *config;
+ struct spdk_conf_section *sp;
+ char section_name[64];
+ int section_index;
+ int rc;
+
+ config = spdk_conf_allocate();
+
+ rc = spdk_conf_read(config, config_file);
+ CU_ASSERT(rc == 0);
+
+ section_index = 0;
+ while (true) {
+ snprintf(section_name, sizeof(section_name), "Failure%d", section_index);
+ sp = spdk_conf_find_section(config, section_name);
+ if (sp == NULL) {
+ break;
+ }
+ rc = spdk_iscsi_parse_tgt_node(sp);
+ CU_ASSERT(rc < 0);
+ section_index++;
+ }
+
+ spdk_conf_free(config);
+}
+
+static void
+allow_any_allowed(void)
+{
+ bool result;
+ char *netmask;
+ char *addr1, *addr2;
+
+ netmask = "ANY";
+ addr1 = "2001:ad6:1234:5678:9abc::";
+ addr2 = "192.168.2.1";
+
+ result = spdk_iscsi_netmask_allow_addr(netmask, addr1);
+ CU_ASSERT(result == true);
+
+ result = spdk_iscsi_netmask_allow_addr(netmask, addr2);
+ CU_ASSERT(result == true);
+}
+
+static void
+allow_ipv6_allowed(void)
+{
+ bool result;
+ char *netmask;
+ char *addr;
+
+ netmask = "[2001:ad6:1234::]/48";
+ addr = "2001:ad6:1234:5678:9abc::";
+
+ result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == true);
+
+ result = spdk_iscsi_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == true);
+
+ /* Netmask prefix bits == 128 (all bits must match) */
+ netmask = "[2001:ad6:1234:5678:9abc::1]/128";
+ addr = "2001:ad6:1234:5678:9abc::1";
+ result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == true);
+}
+
+static void
+allow_ipv6_denied(void)
+{
+ bool result;
+ char *netmask;
+ char *addr;
+
+ netmask = "[2001:ad6:1234::]/56";
+ addr = "2001:ad6:1234:5678:9abc::";
+
+ result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+
+ result = spdk_iscsi_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+
+ /* Netmask prefix bits == 128 (all bits must match) */
+ netmask = "[2001:ad6:1234:5678:9abc::1]/128";
+ addr = "2001:ad6:1234:5678:9abc::2";
+ result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+}
+
+static void
+allow_ipv6_invalid(void)
+{
+ bool result;
+ char *netmask;
+ char *addr;
+
+ /* Netmask prefix bits > 128 */
+ netmask = "[2001:ad6:1234::]/129";
+ addr = "2001:ad6:1234:5678:9abc::";
+ result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+
+ /* Netmask prefix bits == 0 */
+ netmask = "[2001:ad6:1234::]/0";
+ addr = "2001:ad6:1234:5678:9abc::";
+ result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+
+ /* Netmask prefix bits < 0 */
+ netmask = "[2001:ad6:1234::]/-1";
+ addr = "2001:ad6:1234:5678:9abc::";
+ result = spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+}
+
+static void
+allow_ipv4_allowed(void)
+{
+ bool result;
+ char *netmask;
+ char *addr;
+
+ netmask = "192.168.2.0/24";
+ addr = "192.168.2.1";
+
+ result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == true);
+
+ result = spdk_iscsi_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == true);
+
+ /* Netmask prefix == 32 (all bits must match) */
+ netmask = "192.168.2.1/32";
+ addr = "192.168.2.1";
+ result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == true);
+}
+
+static void
+allow_ipv4_denied(void)
+{
+ bool result;
+ char *netmask;
+ char *addr;
+
+ netmask = "192.168.2.0";
+ addr = "192.168.2.1";
+
+ result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+
+ result = spdk_iscsi_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+
+ /* Netmask prefix == 32 (all bits must match) */
+ netmask = "192.168.2.1/32";
+ addr = "192.168.2.2";
+ result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+}
+
+static void
+allow_ipv4_invalid(void)
+{
+ bool result;
+ char *netmask;
+ char *addr;
+
+ /* Netmask prefix bits > 32 */
+ netmask = "192.168.2.0/33";
+ addr = "192.168.2.1";
+ result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+
+ /* Netmask prefix bits == 0 */
+ netmask = "192.168.2.0/0";
+ addr = "192.168.2.1";
+ result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+
+ /* Netmask prefix bits < 0 */
+ netmask = "192.168.2.0/-1";
+ addr = "192.168.2.1";
+ result = spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr);
+ CU_ASSERT(result == false);
+}
+
+static void
+node_access_allowed(void)
+{
+ struct spdk_iscsi_tgt_node tgtnode;
+ struct spdk_iscsi_portal_grp pg;
+ struct spdk_iscsi_init_grp ig;
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_portal portal;
+ struct spdk_iscsi_initiator_name iname;
+ struct spdk_iscsi_initiator_netmask imask;
+ struct spdk_scsi_dev scsi_dev;
+ struct spdk_iscsi_pg_map *pg_map;
+ char *iqn, *addr;
+ bool result;
+
+ /* portal group initialization */
+ memset(&pg, 0, sizeof(struct spdk_iscsi_portal_grp));
+ pg.tag = 1;
+
+ /* initiator group initialization */
+ memset(&ig, 0, sizeof(struct spdk_iscsi_init_grp));
+ ig.tag = 1;
+
+ ig.ninitiators = 1;
+ iname.name = "iqn.2017-10.spdk.io:0001";
+ TAILQ_INIT(&ig.initiator_head);
+ TAILQ_INSERT_TAIL(&ig.initiator_head, &iname, tailq);
+
+ ig.nnetmasks = 1;
+ imask.mask = "192.168.2.0/24";
+ TAILQ_INIT(&ig.netmask_head);
+ TAILQ_INSERT_TAIL(&ig.netmask_head, &imask, tailq);
+
+ /* target initialization */
+ memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node));
+ tgtnode.name = "iqn.2017-10.spdk.io:0001";
+ TAILQ_INIT(&tgtnode.pg_map_head);
+
+ memset(&scsi_dev, 0, sizeof(struct spdk_scsi_dev));
+ snprintf(scsi_dev.name, sizeof(scsi_dev.name), "iqn.2017-10.spdk.io:0001");
+ tgtnode.dev = &scsi_dev;
+
+ pg_map = spdk_iscsi_tgt_node_add_pg_map(&tgtnode, &pg);
+ spdk_iscsi_pg_map_add_ig_map(pg_map, &ig);
+
+ /* portal initialization */
+ memset(&portal, 0, sizeof(struct spdk_iscsi_portal));
+ portal.group = &pg;
+ portal.host = "192.168.2.0";
+ portal.port = "3260";
+
+ /* input for UT */
+ memset(&conn, 0, sizeof(struct spdk_iscsi_conn));
+ conn.portal = &portal;
+
+ iqn = "iqn.2017-10.spdk.io:0001";
+ addr = "192.168.2.1";
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == true);
+
+ spdk_iscsi_pg_map_delete_ig_map(pg_map, &ig);
+ spdk_iscsi_tgt_node_delete_pg_map(&tgtnode, &pg);
+}
+
+static void
+node_access_denied_by_empty_netmask(void)
+{
+ struct spdk_iscsi_tgt_node tgtnode;
+ struct spdk_iscsi_portal_grp pg;
+ struct spdk_iscsi_init_grp ig;
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_portal portal;
+ struct spdk_iscsi_initiator_name iname;
+ struct spdk_scsi_dev scsi_dev;
+ struct spdk_iscsi_pg_map *pg_map;
+ char *iqn, *addr;
+ bool result;
+
+ /* portal group initialization */
+ memset(&pg, 0, sizeof(struct spdk_iscsi_portal_grp));
+ pg.tag = 1;
+
+ /* initiator group initialization */
+ memset(&ig, 0, sizeof(struct spdk_iscsi_init_grp));
+ ig.tag = 1;
+
+ ig.ninitiators = 1;
+ iname.name = "iqn.2017-10.spdk.io:0001";
+ TAILQ_INIT(&ig.initiator_head);
+ TAILQ_INSERT_TAIL(&ig.initiator_head, &iname, tailq);
+
+ ig.nnetmasks = 0;
+ TAILQ_INIT(&ig.netmask_head);
+
+ /* target initialization */
+ memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node));
+ tgtnode.name = "iqn.2017-10.spdk.io:0001";
+ TAILQ_INIT(&tgtnode.pg_map_head);
+
+ memset(&scsi_dev, 0, sizeof(struct spdk_scsi_dev));
+ snprintf(scsi_dev.name, sizeof(scsi_dev.name), "iqn.2017-10.spdk.io:0001");
+ tgtnode.dev = &scsi_dev;
+
+ pg_map = spdk_iscsi_tgt_node_add_pg_map(&tgtnode, &pg);
+ spdk_iscsi_pg_map_add_ig_map(pg_map, &ig);
+
+ /* portal initialization */
+ memset(&portal, 0, sizeof(struct spdk_iscsi_portal));
+ portal.group = &pg;
+ portal.host = "192.168.2.0";
+ portal.port = "3260";
+
+ /* input for UT */
+ memset(&conn, 0, sizeof(struct spdk_iscsi_conn));
+ conn.portal = &portal;
+
+ iqn = "iqn.2017-10.spdk.io:0001";
+ addr = "192.168.3.1";
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == false);
+
+ spdk_iscsi_pg_map_delete_ig_map(pg_map, &ig);
+ spdk_iscsi_tgt_node_delete_pg_map(&tgtnode, &pg);
+}
+
+#define IQN1 "iqn.2017-11.spdk.io:0001"
+#define NO_IQN1 "!iqn.2017-11.spdk.io:0001"
+#define IQN2 "iqn.2017-11.spdk.io:0002"
+#define IP1 "192.168.2.0"
+#define IP2 "192.168.2.1"
+
+static void
+node_access_multi_initiator_groups_cases(void)
+{
+ struct spdk_iscsi_tgt_node tgtnode;
+ struct spdk_iscsi_conn conn;
+ struct spdk_iscsi_portal_grp pg;
+ struct spdk_iscsi_portal portal;
+ struct spdk_iscsi_init_grp ig1, ig2;
+ struct spdk_iscsi_initiator_name iname1, iname2;
+ struct spdk_iscsi_initiator_netmask imask1, imask2;
+ struct spdk_scsi_dev scsi_dev;
+ struct spdk_iscsi_pg_map *pg_map;
+ char *iqn, *addr;
+ bool result;
+
+ /* target initialization */
+ memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node));
+ tgtnode.name = IQN1;
+ TAILQ_INIT(&tgtnode.pg_map_head);
+
+ memset(&scsi_dev, 0, sizeof(struct spdk_scsi_dev));
+ snprintf(scsi_dev.name, sizeof(scsi_dev.name), IQN1);
+ tgtnode.dev = &scsi_dev;
+
+ /* initiator group initialization */
+ memset(&ig1, 0, sizeof(struct spdk_iscsi_init_grp));
+ ig1.tag = 1;
+ TAILQ_INIT(&ig1.initiator_head);
+ TAILQ_INIT(&ig1.netmask_head);
+
+ ig1.ninitiators = 1;
+ iname1.name = NULL;
+ TAILQ_INSERT_TAIL(&ig1.initiator_head, &iname1, tailq);
+
+ ig1.nnetmasks = 1;
+ imask1.mask = NULL;
+ TAILQ_INSERT_TAIL(&ig1.netmask_head, &imask1, tailq);
+
+ memset(&ig2, 0, sizeof(struct spdk_iscsi_init_grp));
+ ig2.tag = 2;
+ TAILQ_INIT(&ig2.initiator_head);
+ TAILQ_INIT(&ig2.netmask_head);
+
+ ig2.ninitiators = 1;
+ iname2.name = NULL;
+ TAILQ_INSERT_TAIL(&ig2.initiator_head, &iname2, tailq);
+
+ ig2.nnetmasks = 1;
+ imask2.mask = NULL;
+ TAILQ_INSERT_TAIL(&ig2.netmask_head, &imask2, tailq);
+
+ /* portal group initialization */
+ memset(&pg, 0, sizeof(struct spdk_iscsi_portal_grp));
+ pg.tag = 1;
+
+ pg_map = spdk_iscsi_tgt_node_add_pg_map(&tgtnode, &pg);
+ spdk_iscsi_pg_map_add_ig_map(pg_map, &ig1);
+ spdk_iscsi_pg_map_add_ig_map(pg_map, &ig2);
+
+ /* portal initialization */
+ memset(&portal, 0, sizeof(struct spdk_iscsi_portal));
+ portal.group = &pg;
+ portal.host = IP1;
+ portal.port = "3260";
+
+ /* connection initialization */
+ memset(&conn, 0, sizeof(struct spdk_iscsi_conn));
+ conn.portal = &portal;
+
+ iqn = IQN1;
+ addr = IP1;
+
+ /*
+ * case 1:
+ * +-------------------------------------------+---------+
+ * | IG1 | IG2 | |
+ * +-------------------------------------------+ |
+ * | name | addr | name | addr | result |
+ * +-------------------------------------------+---------+
+ * +-------------------------------------------+---------+
+ * | denied | - | - | - | denied |
+ * +-------------------------------------------+---------+
+ */
+ iname1.name = NO_IQN1;
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == false);
+
+ /*
+ * case 2:
+ * +-------------------------------------------+---------+
+ * | IG1 | IG2 | |
+ * +-------------------------------------------+ |
+ * | name | addr | name | addr | result |
+ * +-------------------------------------------+---------+
+ * +-------------------------------------------+---------+
+ * | allowed | allowed | - | - | allowed |
+ * +-------------------------------------------+---------+
+ */
+ iname1.name = IQN1;
+ imask1.mask = IP1;
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == true);
+
+ /*
+ * case 3:
+ * +-------------------------------------------+---------+
+ * | IG1 | IG2 | |
+ * +-------------------------------------------+ |
+ * | name | addr | name | addr | result |
+ * +-------------------------------------------+---------+
+ * +-------------------------------------------+---------+
+ * | allowed | denied | denied | - | denied |
+ * +-------------------------------------------+---------+
+ */
+ iname1.name = IQN1;
+ imask1.mask = IP2;
+ iname2.name = NO_IQN1;
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == false);
+
+ /*
+ * case 4:
+ * +-------------------------------------------+---------+
+ * | IG1 | IG2 | |
+ * +-------------------------------------------+ |
+ * | name | addr | name | addr | result |
+ * +-------------------------------------------+---------+
+ * +-------------------------------------------+---------+
+ * | allowed | denied | allowed | allowed | allowed |
+ * +-------------------------------------------+---------+
+ */
+ iname1.name = IQN1;
+ imask1.mask = IP2;
+ iname2.name = IQN1;
+ imask2.mask = IP1;
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == true);
+
+ /*
+ * case 5:
+ * +---------------------------------------------+---------+
+ * | IG1 | IG2 | |
+ * +---------------------------------------------+ |
+ * | name | addr | name | addr | result |
+ * +---------------------------------------------+---------+
+ * +---------------------------------------------+---------+
+ * | allowed | denied | allowed | denied | denied |
+ * +---------------------------------------------+---------+
+ */
+ iname1.name = IQN1;
+ imask1.mask = IP2;
+ iname2.name = IQN1;
+ imask2.mask = IP2;
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == false);
+
+ /*
+ * case 6:
+ * +---------------------------------------------+---------+
+ * | IG1 | IG2 | |
+ * +---------------------------------------------+ |
+ * | name | addr | name | addr | result |
+ * +---------------------------------------------+---------+
+ * +---------------------------------------------+---------+
+ * | allowed | denied | not found | - | denied |
+ * +---------------------------------------------+---------+
+ */
+ iname1.name = IQN1;
+ imask1.mask = IP2;
+ iname2.name = IQN2;
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == false);
+
+ /*
+ * case 7:
+ * +---------------------------------------------+---------+
+ * | IG1 | IG2 | |
+ * +---------------------------------------------+ |
+ * | name | addr | name | addr | result |
+ * +---------------------------------------------+---------+
+ * +---------------------------------------------+---------+
+ * | not found | - | denied | - | denied |
+ * +---------------------------------------------+---------+
+ */
+ iname1.name = IQN2;
+ iname2.name = NO_IQN1;
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == false);
+
+ /*
+ * case 8:
+ * +---------------------------------------------+---------+
+ * | IG1 | IG2 | |
+ * +---------------------------------------------+ |
+ * | name | addr | name | addr | result |
+ * +---------------------------------------------+---------+
+ * +---------------------------------------------+---------+
+ * | not found | - | allowed | allowed | allowed |
+ * +---------------------------------------------+---------+
+ */
+ iname1.name = IQN2;
+ iname2.name = IQN1;
+ imask2.mask = IP1;
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == true);
+
+ /*
+ * case 9:
+ * +---------------------------------------------+---------+
+ * | IG1 | IG2 | |
+ * +---------------------------------------------+ |
+ * | name | addr | name | addr | result |
+ * +---------------------------------------------+---------+
+ * +---------------------------------------------+---------+
+ * | not found | - | allowed | denied | denied |
+ * +---------------------------------------------+---------+
+ */
+ iname1.name = IQN2;
+ iname2.name = IQN1;
+ imask2.mask = IP2;
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == false);
+
+ /*
+ * case 10:
+ * +---------------------------------------------+---------+
+ * | IG1 | IG2 | |
+ * +---------------------------------------------+ |
+ * | name | addr | name | addr | result |
+ * +---------------------------------------------+---------+
+ * +---------------------------------------------+---------+
+ * | not found | - | not found | - | denied |
+ * +---------------------------------------------+---------+
+ */
+ iname1.name = IQN2;
+ iname2.name = IQN2;
+
+ result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
+ CU_ASSERT(result == false);
+
+ spdk_iscsi_pg_map_delete_ig_map(pg_map, &ig1);
+ spdk_iscsi_pg_map_delete_ig_map(pg_map, &ig2);
+ spdk_iscsi_tgt_node_delete_pg_map(&tgtnode, &pg);
+}
+
+static void
+allow_iscsi_name_multi_maps_case(void)
+{
+ struct spdk_iscsi_tgt_node tgtnode;
+ struct spdk_iscsi_portal_grp pg1, pg2;
+ struct spdk_iscsi_init_grp ig;
+ struct spdk_iscsi_initiator_name iname;
+ struct spdk_iscsi_pg_map *pg_map1, *pg_map2;
+ struct spdk_scsi_dev scsi_dev;
+ char *iqn;
+ bool result;
+
+ /* target initialization */
+ memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node));
+ TAILQ_INIT(&tgtnode.pg_map_head);
+
+ memset(&scsi_dev, 0, sizeof(struct spdk_scsi_dev));
+ snprintf(scsi_dev.name, sizeof(scsi_dev.name), IQN1);
+ tgtnode.dev = &scsi_dev;
+
+ /* initiator group initialization */
+ memset(&ig, 0, sizeof(struct spdk_iscsi_init_grp));
+ TAILQ_INIT(&ig.initiator_head);
+
+ ig.ninitiators = 1;
+ iname.name = NULL;
+ TAILQ_INSERT_TAIL(&ig.initiator_head, &iname, tailq);
+
+ /* portal group initialization */
+ memset(&pg1, 0, sizeof(struct spdk_iscsi_portal_grp));
+ pg1.tag = 1;
+ memset(&pg2, 0, sizeof(struct spdk_iscsi_portal_grp));
+ pg2.tag = 1;
+
+ pg_map1 = spdk_iscsi_tgt_node_add_pg_map(&tgtnode, &pg1);
+ pg_map2 = spdk_iscsi_tgt_node_add_pg_map(&tgtnode, &pg2);
+ spdk_iscsi_pg_map_add_ig_map(pg_map1, &ig);
+ spdk_iscsi_pg_map_add_ig_map(pg_map2, &ig);
+
+ /* test for IG1 <-> PG1, PG2 case */
+ iqn = IQN1;
+
+ iname.name = IQN1;
+
+ result = spdk_iscsi_tgt_node_allow_iscsi_name(&tgtnode, iqn);
+ CU_ASSERT(result == true);
+
+ iname.name = IQN2;
+
+ result = spdk_iscsi_tgt_node_allow_iscsi_name(&tgtnode, iqn);
+ CU_ASSERT(result == false);
+
+ spdk_iscsi_pg_map_delete_ig_map(pg_map1, &ig);
+ spdk_iscsi_pg_map_delete_ig_map(pg_map2, &ig);
+ spdk_iscsi_tgt_node_delete_pg_map(&tgtnode, &pg1);
+ spdk_iscsi_tgt_node_delete_pg_map(&tgtnode, &pg2);
+}
+
+/*
+ * static bool
+ * spdk_iscsi_check_chap_params(bool disable_chap, bool require_chap,
+ * bool mutual_chap, int chap_group);
+ */
+static void
+chap_param_test_cases(void)
+{
+ /* Auto */
+ CU_ASSERT(spdk_iscsi_check_chap_params(false, false, false, 0) == true);
+
+ /* None */
+ CU_ASSERT(spdk_iscsi_check_chap_params(true, false, false, 0) == true);
+
+ /* CHAP */
+ CU_ASSERT(spdk_iscsi_check_chap_params(false, true, false, 0) == true);
+
+ /* CHAP Mutual */
+ CU_ASSERT(spdk_iscsi_check_chap_params(false, true, true, 0) == true);
+
+ /* Check mutual exclusiveness of disabled and required */
+ CU_ASSERT(spdk_iscsi_check_chap_params(true, true, false, 0) == false);
+
+ /* Mutual requires Required */
+ CU_ASSERT(spdk_iscsi_check_chap_params(false, false, true, 0) == false);
+
+ /* Remaining combinations */
+ CU_ASSERT(spdk_iscsi_check_chap_params(true, false, true, 0) == false);
+ CU_ASSERT(spdk_iscsi_check_chap_params(true, true, true, 0) == false);
+
+ /* Valid auth group ID */
+ CU_ASSERT(spdk_iscsi_check_chap_params(false, false, false, 1) == true);
+
+ /* Invalid auth group ID */
+ CU_ASSERT(spdk_iscsi_check_chap_params(false, false, false, -1) == false);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <config file>\n", argv[0]);
+ exit(1);
+ }
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ config_file = argv[1];
+
+ suite = CU_add_suite("iscsi_target_node_suite", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "add lun test cases", add_lun_test_cases) == NULL
+ || CU_add_test(suite, "config file fail cases", config_file_fail_cases) == NULL
+ || CU_add_test(suite, "allow any allowed case", allow_any_allowed) == NULL
+ || CU_add_test(suite, "allow ipv6 allowed case", allow_ipv6_allowed) == NULL
+ || CU_add_test(suite, "allow ipv6 denied case", allow_ipv6_denied) == NULL
+ || CU_add_test(suite, "allow ipv6 invalid case", allow_ipv6_invalid) == NULL
+ || CU_add_test(suite, "allow ipv4 allowed case", allow_ipv4_allowed) == NULL
+ || CU_add_test(suite, "allow ipv4 denied case", allow_ipv4_denied) == NULL
+ || CU_add_test(suite, "allow ipv4 invalid case", allow_ipv4_invalid) == NULL
+ || CU_add_test(suite, "node access allowed case", node_access_allowed) == NULL
+ || CU_add_test(suite, "node access denied case (empty netmask)",
+ node_access_denied_by_empty_netmask) == NULL
+ || CU_add_test(suite, "node access multiple initiator groups cases",
+ node_access_multi_initiator_groups_cases) == NULL
+ || CU_add_test(suite, "allow iscsi name case",
+ allow_iscsi_name_multi_maps_case) == NULL
+ || CU_add_test(suite, "chap param test cases", chap_param_test_cases) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/json/Makefile b/src/spdk/test/unit/lib/json/Makefile
new file mode 100644
index 00000000..db38f27d
--- /dev/null
+++ b/src/spdk/test/unit/lib/json/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 = json_parse.c json_util.c json_write.c
+
+.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/unit/lib/json/json_parse.c/.gitignore b/src/spdk/test/unit/lib/json/json_parse.c/.gitignore
new file mode 100644
index 00000000..2b4445fd
--- /dev/null
+++ b/src/spdk/test/unit/lib/json/json_parse.c/.gitignore
@@ -0,0 +1 @@
+json_parse_ut
diff --git a/src/spdk/test/unit/lib/json/json_parse.c/Makefile b/src/spdk/test/unit/lib/json/json_parse.c/Makefile
new file mode 100644
index 00000000..3d410024
--- /dev/null
+++ b/src/spdk/test/unit/lib/json/json_parse.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = json_parse_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/json/json_parse.c/json_parse_ut.c b/src/spdk/test/unit/lib/json/json_parse.c/json_parse_ut.c
new file mode 100644
index 00000000..dae80476
--- /dev/null
+++ b/src/spdk/test/unit/lib/json/json_parse.c/json_parse_ut.c
@@ -0,0 +1,940 @@
+/*-
+ * 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_cunit.h"
+
+#include "json/json_parse.c"
+
+static uint8_t g_buf[1000];
+static void *g_end;
+static struct spdk_json_val g_vals[100];
+static int g_cur_val;
+
+/* Fill buf with raw data */
+#define BUF_SETUP(in) \
+ memset(g_buf, 0, sizeof(g_buf)); \
+ if (sizeof(in) > 1) { \
+ memcpy(g_buf, in, sizeof(in) - 1); \
+ } \
+ g_end = NULL
+
+/*
+ * Do two checks - first pass NULL for values to ensure the count is correct,
+ * then pass g_vals to get the actual values.
+ */
+#define PARSE_PASS_FLAGS(in, num_vals, trailing, flags) \
+ BUF_SETUP(in); \
+ CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, NULL, 0, &g_end, flags) == num_vals); \
+ memset(g_vals, 0, sizeof(g_vals)); \
+ CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, g_vals, sizeof(g_vals), &g_end, flags | SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE) == num_vals); \
+ CU_ASSERT(g_end == g_buf + sizeof(in) - sizeof(trailing)); \
+ CU_ASSERT(memcmp(g_end, trailing, sizeof(trailing) - 1) == 0); \
+ g_cur_val = 0
+
+#define PARSE_PASS(in, num_vals, trailing) \
+ PARSE_PASS_FLAGS(in, num_vals, trailing, 0)
+
+#define PARSE_FAIL_FLAGS(in, retval, flags) \
+ BUF_SETUP(in); \
+ CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, NULL, 0, &g_end, flags) == retval)
+
+#define PARSE_FAIL(in, retval) \
+ PARSE_FAIL_FLAGS(in, retval, 0)
+
+#define VAL_STRING_MATCH(str, var_type) \
+ CU_ASSERT(g_vals[g_cur_val].type == var_type); \
+ CU_ASSERT(g_vals[g_cur_val].len == sizeof(str) - 1); \
+ if (g_vals[g_cur_val].len == sizeof(str) - 1 && sizeof(str) > 1) { \
+ CU_ASSERT(memcmp(g_vals[g_cur_val].start, str, g_vals[g_cur_val].len) == 0); \
+ } \
+ g_cur_val++
+
+#define VAL_STRING(str) VAL_STRING_MATCH(str, SPDK_JSON_VAL_STRING)
+#define VAL_NAME(str) VAL_STRING_MATCH(str, SPDK_JSON_VAL_NAME)
+#define VAL_NUMBER(num) VAL_STRING_MATCH(num, SPDK_JSON_VAL_NUMBER)
+
+#define VAL_LITERAL(str, val_type) \
+ CU_ASSERT(g_vals[g_cur_val].type == val_type); \
+ CU_ASSERT(g_vals[g_cur_val].len == strlen(str)); \
+ if (g_vals[g_cur_val].len == strlen(str)) { \
+ CU_ASSERT(memcmp(g_vals[g_cur_val].start, str, g_vals[g_cur_val].len) == 0); \
+ } \
+ g_cur_val++
+
+#define VAL_TRUE() VAL_LITERAL("true", SPDK_JSON_VAL_TRUE)
+#define VAL_FALSE() VAL_LITERAL("false", SPDK_JSON_VAL_FALSE)
+#define VAL_NULL() VAL_LITERAL("null", SPDK_JSON_VAL_NULL)
+
+#define VAL_ARRAY_BEGIN(count) \
+ CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_ARRAY_BEGIN); \
+ CU_ASSERT(g_vals[g_cur_val].len == count); \
+ g_cur_val++
+
+#define VAL_ARRAY_END() \
+ CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_ARRAY_END); \
+ g_cur_val++
+
+#define VAL_OBJECT_BEGIN(count) \
+ CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_OBJECT_BEGIN); \
+ CU_ASSERT(g_vals[g_cur_val].len == count); \
+ g_cur_val++
+
+#define VAL_OBJECT_END() \
+ CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_OBJECT_END); \
+ g_cur_val++
+
+/* Simplified macros for string-only testing */
+#define STR_PASS(in, out) \
+ PARSE_PASS("\"" in "\"", 1, ""); \
+ VAL_STRING(out)
+
+#define STR_FAIL(in, retval) \
+ PARSE_FAIL("\"" in "\"", retval)
+
+/* Simplified macros for number-only testing (no whitespace allowed) */
+#define NUM_PASS(in) \
+ PARSE_PASS(in, 1, ""); \
+ VAL_NUMBER(in)
+
+#define NUM_FAIL(in, retval) \
+ PARSE_FAIL(in, retval)
+
+static void
+test_parse_literal(void)
+{
+ PARSE_PASS("true", 1, "");
+ VAL_TRUE();
+
+ PARSE_PASS(" true ", 1, "");
+ VAL_TRUE();
+
+ PARSE_PASS("false", 1, "");
+ VAL_FALSE();
+
+ PARSE_PASS("null", 1, "");
+ VAL_NULL();
+
+ PARSE_PASS("trueaaa", 1, "aaa");
+ VAL_TRUE();
+
+ PARSE_PASS("truefalse", 1, "false");
+ VAL_TRUE();
+
+ PARSE_PASS("true false", 1, "false");
+ VAL_TRUE();
+
+ PARSE_PASS("true,false", 1, ",false");
+ VAL_TRUE();
+
+ PARSE_PASS("true,", 1, ",");
+ VAL_TRUE();
+
+ PARSE_FAIL("True", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("abcdef", SPDK_JSON_PARSE_INVALID);
+
+ PARSE_FAIL("t", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("tru", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("f", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("fals", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("n", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("nul", SPDK_JSON_PARSE_INCOMPLETE);
+
+ PARSE_FAIL("taaaaa", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("faaaaa", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("naaaaa", SPDK_JSON_PARSE_INVALID);
+}
+
+static void
+test_parse_string_simple(void)
+{
+ PARSE_PASS("\"\"", 1, "");
+ VAL_STRING("");
+
+ PARSE_PASS("\"hello world\"", 1, "");
+ VAL_STRING("hello world");
+
+ PARSE_PASS(" \"hello world\" ", 1, "");
+ VAL_STRING("hello world");
+
+ /* Unterminated string */
+ PARSE_FAIL("\"hello world", SPDK_JSON_PARSE_INCOMPLETE);
+
+ /* Trailing comma */
+ PARSE_PASS("\"hello world\",", 1, ",");
+ VAL_STRING("hello world");
+}
+
+static void
+test_parse_string_control_chars(void)
+{
+ /* U+0000 through U+001F must be escaped */
+ STR_FAIL("\x00", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x01", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x02", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x03", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x04", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x05", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x06", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x07", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x08", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x09", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x0A", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x0B", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x0C", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x0D", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x0E", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x0F", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x10", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x11", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x12", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x13", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x14", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x15", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x16", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x17", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x18", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x19", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x1A", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x1B", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x1C", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x1D", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x1E", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\x1F", SPDK_JSON_PARSE_INVALID);
+ STR_PASS(" ", " "); /* \x20 (first valid unescaped char) */
+
+ /* Test control chars in the middle of a string */
+ STR_FAIL("abc\ndef", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("abc\tdef", SPDK_JSON_PARSE_INVALID);
+}
+
+static void
+test_parse_string_utf8(void)
+{
+ /* Valid one-, two-, three-, and four-byte sequences */
+ STR_PASS("\x41", "A");
+ STR_PASS("\xC3\xB6", "\xC3\xB6");
+ STR_PASS("\xE2\x88\x9A", "\xE2\x88\x9A");
+ STR_PASS("\xF0\xA0\x9C\x8E", "\xF0\xA0\x9C\x8E");
+
+ /* Examples from RFC 3629 */
+ STR_PASS("\x41\xE2\x89\xA2\xCE\x91\x2E", "\x41\xE2\x89\xA2\xCE\x91\x2E");
+ STR_PASS("\xED\x95\x9C\xEA\xB5\xAD\xEC\x96\xB4", "\xED\x95\x9C\xEA\xB5\xAD\xEC\x96\xB4");
+ STR_PASS("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E", "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E");
+ STR_PASS("\xEF\xBB\xBF\xF0\xA3\x8E\xB4", "\xEF\xBB\xBF\xF0\xA3\x8E\xB4");
+
+ /* Edge cases */
+ STR_PASS("\x7F", "\x7F");
+ STR_FAIL("\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xC1", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xC2", SPDK_JSON_PARSE_INVALID);
+ STR_PASS("\xC2\x80", "\xC2\x80");
+ STR_PASS("\xC2\xBF", "\xC2\xBF");
+ STR_PASS("\xDF\x80", "\xDF\x80");
+ STR_PASS("\xDF\xBF", "\xDF\xBF");
+ STR_FAIL("\xDF", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xE0\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xE0\x1F", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xE0\x1F\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xE0", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xE0\xA0", SPDK_JSON_PARSE_INVALID);
+ STR_PASS("\xE0\xA0\x80", "\xE0\xA0\x80");
+ STR_PASS("\xE0\xA0\xBF", "\xE0\xA0\xBF");
+ STR_FAIL("\xE0\xA0\xC0", SPDK_JSON_PARSE_INVALID);
+ STR_PASS("\xE0\xBF\x80", "\xE0\xBF\x80");
+ STR_PASS("\xE0\xBF\xBF", "\xE0\xBF\xBF");
+ STR_FAIL("\xE0\xC0\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xE1", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xE1\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xE1\x7F\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xE1\x80\x7F", SPDK_JSON_PARSE_INVALID);
+ STR_PASS("\xE1\x80\x80", "\xE1\x80\x80");
+ STR_PASS("\xE1\x80\xBF", "\xE1\x80\xBF");
+ STR_PASS("\xE1\xBF\x80", "\xE1\xBF\x80");
+ STR_PASS("\xE1\xBF\xBF", "\xE1\xBF\xBF");
+ STR_FAIL("\xE1\xC0\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xE1\x80\xC0", SPDK_JSON_PARSE_INVALID);
+ STR_PASS("\xEF\x80\x80", "\xEF\x80\x80");
+ STR_PASS("\xEF\xBF\xBF", "\xEF\xBF\xBF");
+ STR_FAIL("\xF0", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF0\x90", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF0\x90\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF0\x80\x80\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF0\x8F\x80\x80", SPDK_JSON_PARSE_INVALID);
+ STR_PASS("\xF0\x90\x80\x80", "\xF0\x90\x80\x80");
+ STR_PASS("\xF0\x90\x80\xBF", "\xF0\x90\x80\xBF");
+ STR_PASS("\xF0\x90\xBF\x80", "\xF0\x90\xBF\x80");
+ STR_PASS("\xF0\xBF\x80\x80", "\xF0\xBF\x80\x80");
+ STR_FAIL("\xF0\xC0\x80\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF1", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF1\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF1\x80\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF1\x80\x80\x7F", SPDK_JSON_PARSE_INVALID);
+ STR_PASS("\xF1\x80\x80\x80", "\xF1\x80\x80\x80");
+ STR_PASS("\xF1\x80\x80\xBF", "\xF1\x80\x80\xBF");
+ STR_PASS("\xF1\x80\xBF\x80", "\xF1\x80\xBF\x80");
+ STR_PASS("\xF1\xBF\x80\x80", "\xF1\xBF\x80\x80");
+ STR_PASS("\xF3\x80\x80\x80", "\xF3\x80\x80\x80");
+ STR_FAIL("\xF3\xC0\x80\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF3\x80\xC0\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF3\x80\x80\xC0", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF4", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF4\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF4\x80\x80", SPDK_JSON_PARSE_INVALID);
+ STR_PASS("\xF4\x80\x80\x80", "\xF4\x80\x80\x80");
+ STR_PASS("\xF4\x8F\x80\x80", "\xF4\x8F\x80\x80");
+ STR_PASS("\xF4\x8F\xBF\xBF", "\xF4\x8F\xBF\xBF");
+ STR_FAIL("\xF4\x90\x80\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF5", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF5\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF5\x80\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF5\x80\x80\x80", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\xF5\x80\x80\x80\x80", SPDK_JSON_PARSE_INVALID);
+
+ /* Overlong encodings */
+ STR_FAIL("\xC0\x80", SPDK_JSON_PARSE_INVALID);
+
+ /* Surrogate pairs */
+ STR_FAIL("\xED\xA0\x80", SPDK_JSON_PARSE_INVALID); /* U+D800 First high surrogate */
+ STR_FAIL("\xED\xAF\xBF", SPDK_JSON_PARSE_INVALID); /* U+DBFF Last high surrogate */
+ STR_FAIL("\xED\xB0\x80", SPDK_JSON_PARSE_INVALID); /* U+DC00 First low surrogate */
+ STR_FAIL("\xED\xBF\xBF", SPDK_JSON_PARSE_INVALID); /* U+DFFF Last low surrogate */
+ STR_FAIL("\xED\xA1\x8C\xED\xBE\xB4",
+ SPDK_JSON_PARSE_INVALID); /* U+233B4 (invalid surrogate pair encoding) */
+}
+
+static void
+test_parse_string_escapes_twochar(void)
+{
+ STR_PASS("\\\"", "\"");
+ STR_PASS("\\\\", "\\");
+ STR_PASS("\\/", "/");
+ STR_PASS("\\b", "\b");
+ STR_PASS("\\f", "\f");
+ STR_PASS("\\n", "\n");
+ STR_PASS("\\r", "\r");
+ STR_PASS("\\t", "\t");
+
+ STR_PASS("abc\\tdef", "abc\tdef");
+ STR_PASS("abc\\\"def", "abc\"def");
+
+ /* Backslash at end of string (will be treated as escaped quote) */
+ STR_FAIL("\\", SPDK_JSON_PARSE_INCOMPLETE);
+ STR_FAIL("abc\\", SPDK_JSON_PARSE_INCOMPLETE);
+
+ /* Invalid C-like escapes */
+ STR_FAIL("\\a", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\v", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\'", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\?", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\0", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\x00", SPDK_JSON_PARSE_INVALID);
+
+ /* Other invalid escapes */
+ STR_FAIL("\\B", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\z", SPDK_JSON_PARSE_INVALID);
+}
+
+static void
+test_parse_string_escapes_unicode(void)
+{
+ STR_PASS("\\u0000", "\0");
+ STR_PASS("\\u0001", "\1");
+ STR_PASS("\\u0041", "A");
+ STR_PASS("\\uAAAA", "\xEA\xAA\xAA");
+ STR_PASS("\\uaaaa", "\xEA\xAA\xAA");
+ STR_PASS("\\uAaAa", "\xEA\xAA\xAA");
+
+ STR_FAIL("\\u", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\u0", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\u00", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\u000", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\u000g", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\U", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\U0000", SPDK_JSON_PARSE_INVALID);
+
+ PARSE_FAIL("\"\\u", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("\"\\u0", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("\"\\u00", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("\"\\u000", SPDK_JSON_PARSE_INCOMPLETE);
+
+ /* Surrogate pair */
+ STR_PASS("\\uD834\\uDD1E", "\xF0\x9D\x84\x9E");
+
+ /* Low surrogate without high */
+ STR_FAIL("\\uDC00", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\uDC00\\uDC00", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\uDC00abcdef", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\uDEAD", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("\"\\uD834", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("\"\\uD834\\", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("\"\\uD834\\u", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("\"\\uD834\\uD", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("\"\\uD834\\uDD1", SPDK_JSON_PARSE_INCOMPLETE);
+
+ /* High surrogate without low */
+ STR_FAIL("\\uD800", SPDK_JSON_PARSE_INVALID);
+ STR_FAIL("\\uD800abcdef", SPDK_JSON_PARSE_INVALID);
+
+ /* High surrogate followed by high surrogate */
+ STR_FAIL("\\uD800\\uD800", SPDK_JSON_PARSE_INVALID);
+}
+
+static void
+test_parse_number(void)
+{
+ NUM_PASS("0");
+ NUM_PASS("1");
+ NUM_PASS("100");
+ NUM_PASS("-1");
+ NUM_PASS("-0");
+ NUM_PASS("3.0");
+ NUM_PASS("3.00");
+ NUM_PASS("3.001");
+ NUM_PASS("3.14159");
+ NUM_PASS("3.141592653589793238462643383279");
+ NUM_PASS("1e400");
+ NUM_PASS("1E400");
+ NUM_PASS("0e10");
+ NUM_PASS("0e0");
+ NUM_PASS("-0e0");
+ NUM_PASS("-0e+0");
+ NUM_PASS("-0e-0");
+ NUM_PASS("1e+400");
+ NUM_PASS("1e-400");
+ NUM_PASS("6.022e23");
+ NUM_PASS("-1.234e+56");
+ NUM_PASS("1.23e+56");
+ NUM_PASS("-1.23e-56");
+ NUM_PASS("1.23e-56");
+ NUM_PASS("1e04");
+
+ /* Trailing garbage */
+ PARSE_PASS("0A", 1, "A");
+ VAL_NUMBER("0");
+
+ PARSE_PASS("0,", 1, ",");
+ VAL_NUMBER("0");
+
+ PARSE_PASS("0true", 1, "true");
+ VAL_NUMBER("0");
+
+ PARSE_PASS("00", 1, "0");
+ VAL_NUMBER("0");
+ PARSE_FAIL("[00", SPDK_JSON_PARSE_INVALID);
+
+ PARSE_PASS("007", 1, "07");
+ VAL_NUMBER("0");
+ PARSE_FAIL("[007]", SPDK_JSON_PARSE_INVALID);
+
+ PARSE_PASS("345.678.1", 1, ".1");
+ VAL_NUMBER("345.678");
+ PARSE_FAIL("[345.678.1]", SPDK_JSON_PARSE_INVALID);
+
+ PARSE_PASS("3.2e-4+5", 1, "+5");
+ VAL_NUMBER("3.2e-4");
+ PARSE_FAIL("[3.2e-4+5]", SPDK_JSON_PARSE_INVALID);
+
+ PARSE_PASS("3.4.5", 1, ".5");
+ VAL_NUMBER("3.4");
+ PARSE_FAIL("[3.4.5]", SPDK_JSON_PARSE_INVALID);
+
+ NUM_FAIL("345.", SPDK_JSON_PARSE_INCOMPLETE);
+ NUM_FAIL("+1", SPDK_JSON_PARSE_INVALID);
+ NUM_FAIL("--1", SPDK_JSON_PARSE_INVALID);
+ NUM_FAIL("3.", SPDK_JSON_PARSE_INCOMPLETE);
+ NUM_FAIL("3.+4", SPDK_JSON_PARSE_INVALID);
+ NUM_FAIL("3.2e+-4", SPDK_JSON_PARSE_INVALID);
+ NUM_FAIL("3.2e-+4", SPDK_JSON_PARSE_INVALID);
+ NUM_FAIL("3e+", SPDK_JSON_PARSE_INCOMPLETE);
+ NUM_FAIL("3e-", SPDK_JSON_PARSE_INCOMPLETE);
+ NUM_FAIL("3.e4", SPDK_JSON_PARSE_INVALID);
+ NUM_FAIL("3.2eX", SPDK_JSON_PARSE_INVALID);
+ NUM_FAIL("-", SPDK_JSON_PARSE_INCOMPLETE);
+ NUM_FAIL("NaN", SPDK_JSON_PARSE_INVALID);
+ NUM_FAIL(".123", SPDK_JSON_PARSE_INVALID);
+}
+
+static void
+test_parse_array(void)
+{
+ char buffer[SPDK_JSON_MAX_NESTING_DEPTH + 2] = {0};
+
+ PARSE_PASS("[]", 2, "");
+ VAL_ARRAY_BEGIN(0);
+ VAL_ARRAY_END();
+
+ PARSE_PASS("[true]", 3, "");
+ VAL_ARRAY_BEGIN(1);
+ VAL_TRUE();
+ VAL_ARRAY_END();
+
+ PARSE_PASS("[true, false]", 4, "");
+ VAL_ARRAY_BEGIN(2);
+ VAL_TRUE();
+ VAL_FALSE();
+ VAL_ARRAY_END();
+
+ PARSE_PASS("[\"hello\"]", 3, "");
+ VAL_ARRAY_BEGIN(1);
+ VAL_STRING("hello");
+ VAL_ARRAY_END();
+
+ PARSE_PASS("[[]]", 4, "");
+ VAL_ARRAY_BEGIN(2);
+ VAL_ARRAY_BEGIN(0);
+ VAL_ARRAY_END();
+ VAL_ARRAY_END();
+
+ PARSE_PASS("[\"hello\", \"world\"]", 4, "");
+ VAL_ARRAY_BEGIN(2);
+ VAL_STRING("hello");
+ VAL_STRING("world");
+ VAL_ARRAY_END();
+
+ PARSE_PASS("[],", 2, ",");
+ VAL_ARRAY_BEGIN(0);
+ VAL_ARRAY_END();
+
+ PARSE_FAIL("]", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("[", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("[true", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("[\"hello", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("[\"hello\"", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("[true,]", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("[,]", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("[,true]", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("[true}", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("[true,,true]", SPDK_JSON_PARSE_INVALID);
+
+ /* Nested arrays exactly up to the allowed nesting depth */
+ memset(buffer, '[', SPDK_JSON_MAX_NESTING_DEPTH);
+ buffer[SPDK_JSON_MAX_NESTING_DEPTH] = ' ';
+ PARSE_FAIL(buffer, SPDK_JSON_PARSE_INCOMPLETE);
+
+ /* Nested arrays exceeding the maximum allowed nesting depth for this implementation */
+ buffer[SPDK_JSON_MAX_NESTING_DEPTH] = '[';
+ PARSE_FAIL(buffer, SPDK_JSON_PARSE_MAX_DEPTH_EXCEEDED);
+}
+
+static void
+test_parse_object(void)
+{
+ PARSE_PASS("{}", 2, "");
+ VAL_OBJECT_BEGIN(0);
+ VAL_OBJECT_END();
+
+ PARSE_PASS("{\"a\": true}", 4, "");
+ VAL_OBJECT_BEGIN(2);
+ VAL_NAME("a");
+ VAL_TRUE();
+ VAL_OBJECT_END();
+
+ PARSE_PASS("{\"abc\": \"def\"}", 4, "");
+ VAL_OBJECT_BEGIN(2);
+ VAL_NAME("abc");
+ VAL_STRING("def");
+ VAL_OBJECT_END();
+
+ PARSE_PASS("{\"a\": true, \"b\": false}", 6, "");
+ VAL_OBJECT_BEGIN(4);
+ VAL_NAME("a");
+ VAL_TRUE();
+ VAL_NAME("b");
+ VAL_FALSE();
+ VAL_OBJECT_END();
+
+ PARSE_PASS("{\"a\": { \"b\": true } }", 7, "");
+ VAL_OBJECT_BEGIN(5);
+ VAL_NAME("a");
+ VAL_OBJECT_BEGIN(2);
+ VAL_NAME("b");
+ VAL_TRUE();
+ VAL_OBJECT_END();
+ VAL_OBJECT_END();
+
+ PARSE_PASS("{\"{test\": 0}", 4, "");
+ VAL_OBJECT_BEGIN(2);
+ VAL_NAME("{test");
+ VAL_NUMBER("0");
+ VAL_OBJECT_END();
+
+ PARSE_PASS("{\"test}\": 1}", 4, "");
+ VAL_OBJECT_BEGIN(2);
+ VAL_NAME("test}");
+ VAL_NUMBER("1");
+ VAL_OBJECT_END();
+
+ PARSE_PASS("{\"\\\"\": 2}", 4, "");
+ VAL_OBJECT_BEGIN(2);
+ VAL_NAME("\"");
+ VAL_NUMBER("2");
+ VAL_OBJECT_END();
+
+ PARSE_PASS("{\"a\":true},", 4, ",");
+ VAL_OBJECT_BEGIN(2);
+ VAL_NAME("a");
+ VAL_TRUE();
+ VAL_OBJECT_END();
+
+ /* Object end without object begin (trailing garbage) */
+ PARSE_PASS("true}", 1, "}");
+ VAL_TRUE();
+
+ PARSE_PASS("0}", 1, "}");
+ VAL_NUMBER("0");
+
+ PARSE_PASS("\"a\"}", 1, "}");
+ VAL_STRING("a");
+
+ PARSE_FAIL("}", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("{", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("{\"a", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("{\"a\"", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("{\"a\":", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("{\"a\":true", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("{\"a\":true,", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("{\"a\":true]", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("{\"a\":true,}", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("{\"a\":true,\"}", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("{\"a\":true,\"b}", SPDK_JSON_PARSE_INCOMPLETE);
+ PARSE_FAIL("{\"a\":true,\"b\"}", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("{\"a\":true,\"b\":}", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("{\"a\":true,\"b\",}", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("{\"a\",}", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("{,\"a\": true}", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("{a:true}", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("{'a':true}", SPDK_JSON_PARSE_INVALID);
+}
+
+static void
+test_parse_nesting(void)
+{
+ PARSE_PASS("[[[[[[[[]]]]]]]]", 16, "");
+
+ PARSE_PASS("{\"a\": [0, 1, 2]}", 8, "");
+ VAL_OBJECT_BEGIN(6);
+ VAL_NAME("a");
+ VAL_ARRAY_BEGIN(3);
+ VAL_NUMBER("0");
+ VAL_NUMBER("1");
+ VAL_NUMBER("2");
+ VAL_ARRAY_END();
+ VAL_OBJECT_END();
+
+ PARSE_PASS("{\"a\": [0, 1, 2], \"b\": 3 }", 10, "");
+ VAL_OBJECT_BEGIN(8);
+ VAL_NAME("a");
+ VAL_ARRAY_BEGIN(3);
+ VAL_NUMBER("0");
+ VAL_NUMBER("1");
+ VAL_NUMBER("2");
+ VAL_ARRAY_END();
+ VAL_NAME("b");
+ VAL_NUMBER("3");
+ VAL_OBJECT_END();
+
+ PARSE_PASS("[0, 1, {\"a\": 3}, 4, 5]", 10, "");
+ VAL_ARRAY_BEGIN(8);
+ VAL_NUMBER("0");
+ VAL_NUMBER("1");
+ VAL_OBJECT_BEGIN(2);
+ VAL_NAME("a");
+ VAL_NUMBER("3");
+ VAL_OBJECT_END();
+ VAL_NUMBER("4");
+ VAL_NUMBER("5");
+ VAL_ARRAY_END();
+
+ PARSE_PASS("\t[ { \"a\": {\"b\": [ {\"c\": 1}, 2 ],\n\"d\": 3}, \"e\" : 4}, 5 ] ", 20, "");
+ VAL_ARRAY_BEGIN(18);
+ VAL_OBJECT_BEGIN(15);
+ VAL_NAME("a");
+ VAL_OBJECT_BEGIN(10);
+ VAL_NAME("b");
+ VAL_ARRAY_BEGIN(5);
+ VAL_OBJECT_BEGIN(2);
+ VAL_NAME("c");
+ VAL_NUMBER("1");
+ VAL_OBJECT_END();
+ VAL_NUMBER("2");
+ VAL_ARRAY_END();
+ VAL_NAME("d");
+ VAL_NUMBER("3");
+ VAL_OBJECT_END();
+ VAL_NAME("e");
+ VAL_NUMBER("4");
+ VAL_OBJECT_END();
+ VAL_NUMBER("5");
+ VAL_ARRAY_END();
+
+ /* Examples from RFC 7159 */
+ PARSE_PASS(
+ "{\n"
+ " \"Image\": {\n"
+ " \"Width\": 800,\n"
+ " \"Height\": 600,\n"
+ " \"Title\": \"View from 15th Floor\",\n"
+ " \"Thumbnail\": {\n"
+ " \"Url\": \"http://www.example.com/image/481989943\",\n"
+ " \"Height\": 125,\n"
+ " \"Width\": 100\n"
+ " },\n"
+ " \"Animated\" : false,\n"
+ " \"IDs\": [116, 943, 234, 38793]\n"
+ " }\n"
+ "}\n",
+ 29, "");
+
+ VAL_OBJECT_BEGIN(27);
+ VAL_NAME("Image");
+ VAL_OBJECT_BEGIN(24);
+ VAL_NAME("Width");
+ VAL_NUMBER("800");
+ VAL_NAME("Height");
+ VAL_NUMBER("600");
+ VAL_NAME("Title");
+ VAL_STRING("View from 15th Floor");
+ VAL_NAME("Thumbnail");
+ VAL_OBJECT_BEGIN(6);
+ VAL_NAME("Url");
+ VAL_STRING("http://www.example.com/image/481989943");
+ VAL_NAME("Height");
+ VAL_NUMBER("125");
+ VAL_NAME("Width");
+ VAL_NUMBER("100");
+ VAL_OBJECT_END();
+ VAL_NAME("Animated");
+ VAL_FALSE();
+ VAL_NAME("IDs");
+ VAL_ARRAY_BEGIN(4);
+ VAL_NUMBER("116");
+ VAL_NUMBER("943");
+ VAL_NUMBER("234");
+ VAL_NUMBER("38793");
+ VAL_ARRAY_END();
+ VAL_OBJECT_END();
+ VAL_OBJECT_END();
+
+ PARSE_PASS(
+ "[\n"
+ " {\n"
+ " \"precision\": \"zip\",\n"
+ " \"Latitude\": 37.7668,\n"
+ " \"Longitude\": -122.3959,\n"
+ " \"Address\": \"\",\n"
+ " \"City\": \"SAN FRANCISCO\",\n"
+ " \"State\": \"CA\",\n"
+ " \"Zip\": \"94107\",\n"
+ " \"Country\": \"US\"\n"
+ " },\n"
+ " {\n"
+ " \"precision\": \"zip\",\n"
+ " \"Latitude\": 37.371991,\n"
+ " \"Longitude\": -122.026020,\n"
+ " \"Address\": \"\",\n"
+ " \"City\": \"SUNNYVALE\",\n"
+ " \"State\": \"CA\",\n"
+ " \"Zip\": \"94085\",\n"
+ " \"Country\": \"US\"\n"
+ " }\n"
+ "]",
+ 38, "");
+
+ VAL_ARRAY_BEGIN(36);
+ VAL_OBJECT_BEGIN(16);
+ VAL_NAME("precision");
+ VAL_STRING("zip");
+ VAL_NAME("Latitude");
+ VAL_NUMBER("37.7668");
+ VAL_NAME("Longitude");
+ VAL_NUMBER("-122.3959");
+ VAL_NAME("Address");
+ VAL_STRING("");
+ VAL_NAME("City");
+ VAL_STRING("SAN FRANCISCO");
+ VAL_NAME("State");
+ VAL_STRING("CA");
+ VAL_NAME("Zip");
+ VAL_STRING("94107");
+ VAL_NAME("Country");
+ VAL_STRING("US");
+ VAL_OBJECT_END();
+ VAL_OBJECT_BEGIN(16);
+ VAL_NAME("precision");
+ VAL_STRING("zip");
+ VAL_NAME("Latitude");
+ VAL_NUMBER("37.371991");
+ VAL_NAME("Longitude");
+ VAL_NUMBER("-122.026020");
+ VAL_NAME("Address");
+ VAL_STRING("");
+ VAL_NAME("City");
+ VAL_STRING("SUNNYVALE");
+ VAL_NAME("State");
+ VAL_STRING("CA");
+ VAL_NAME("Zip");
+ VAL_STRING("94085");
+ VAL_NAME("Country");
+ VAL_STRING("US");
+ VAL_OBJECT_END();
+ VAL_ARRAY_END();
+
+ /* Trailing garbage */
+ PARSE_PASS("{\"a\": [0, 1, 2]}]", 8, "]");
+ VAL_OBJECT_BEGIN(6);
+ VAL_NAME("a");
+ VAL_ARRAY_BEGIN(3);
+ VAL_NUMBER("0");
+ VAL_NUMBER("1");
+ VAL_NUMBER("2");
+ VAL_ARRAY_END();
+ VAL_OBJECT_END();
+
+ PARSE_PASS("{\"a\": [0, 1, 2]}}", 8, "}");
+ PARSE_PASS("{\"a\": [0, 1, 2]}]", 8, "]");
+ VAL_OBJECT_BEGIN(6);
+ VAL_NAME("a");
+ VAL_ARRAY_BEGIN(3);
+ VAL_NUMBER("0");
+ VAL_NUMBER("1");
+ VAL_NUMBER("2");
+ VAL_ARRAY_END();
+ VAL_OBJECT_END();
+
+ PARSE_FAIL("{\"a\": [0, 1, 2}]", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("{\"a\": [0, 1, 2]", SPDK_JSON_PARSE_INCOMPLETE);
+}
+
+
+static void
+test_parse_comment(void)
+{
+ /* Comments are not allowed by the JSON RFC */
+ PARSE_PASS("[0]", 3, "");
+ PARSE_FAIL("/* test */[0]", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("[/* test */0]", SPDK_JSON_PARSE_INVALID);
+ PARSE_FAIL("[0/* test */]", SPDK_JSON_PARSE_INVALID);
+
+ /*
+ * This is allowed since the parser stops once it reads a complete JSON object.
+ * The next parse call would fail (see tests above) when parsing the comment.
+ */
+ PARSE_PASS("[0]/* test */", 3, "/* test */");
+
+ /*
+ * Test with non-standard comments enabled.
+ */
+ PARSE_PASS_FLAGS("/* test */[0]", 3, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+ VAL_ARRAY_BEGIN(1);
+ VAL_NUMBER("0");
+ VAL_ARRAY_END();
+
+ PARSE_PASS_FLAGS("[/* test */0]", 3, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+ VAL_ARRAY_BEGIN(1);
+ VAL_NUMBER("0");
+ VAL_ARRAY_END();
+
+ PARSE_PASS_FLAGS("[0/* test */]", 3, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+ VAL_ARRAY_BEGIN(1);
+ VAL_NUMBER("0");
+ VAL_ARRAY_END();
+
+ PARSE_FAIL_FLAGS("/* test */", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+ PARSE_FAIL_FLAGS("[/* test */", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+ PARSE_FAIL_FLAGS("[0/* test */", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+
+ /*
+ * Single-line comments
+ */
+ PARSE_PASS_FLAGS("// test\n0", 1, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+ VAL_NUMBER("0");
+
+ PARSE_PASS_FLAGS("// test\r\n0", 1, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+ VAL_NUMBER("0");
+
+ PARSE_PASS_FLAGS("// [0] test\n0", 1, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+ VAL_NUMBER("0");
+
+ PARSE_FAIL_FLAGS("//", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+ PARSE_FAIL_FLAGS("// test", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+ PARSE_FAIL_FLAGS("//\n", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+
+ /* Invalid character following slash */
+ PARSE_FAIL_FLAGS("[0/x", SPDK_JSON_PARSE_INVALID, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+
+ /* Single slash at end of buffer */
+ PARSE_FAIL_FLAGS("[0/", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("json", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "parse_literal", test_parse_literal) == NULL ||
+ CU_add_test(suite, "parse_string_simple", test_parse_string_simple) == NULL ||
+ CU_add_test(suite, "parse_string_control_chars", test_parse_string_control_chars) == NULL ||
+ CU_add_test(suite, "parse_string_utf8", test_parse_string_utf8) == NULL ||
+ CU_add_test(suite, "parse_string_escapes_twochar", test_parse_string_escapes_twochar) == NULL ||
+ CU_add_test(suite, "parse_string_escapes_unicode", test_parse_string_escapes_unicode) == NULL ||
+ CU_add_test(suite, "parse_number", test_parse_number) == NULL ||
+ CU_add_test(suite, "parse_array", test_parse_array) == NULL ||
+ CU_add_test(suite, "parse_object", test_parse_object) == NULL ||
+ CU_add_test(suite, "parse_nesting", test_parse_nesting) == NULL ||
+ CU_add_test(suite, "parse_comment", test_parse_comment) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/json/json_util.c/.gitignore b/src/spdk/test/unit/lib/json/json_util.c/.gitignore
new file mode 100644
index 00000000..02f6d50c
--- /dev/null
+++ b/src/spdk/test/unit/lib/json/json_util.c/.gitignore
@@ -0,0 +1 @@
+json_util_ut
diff --git a/src/spdk/test/unit/lib/json/json_util.c/Makefile b/src/spdk/test/unit/lib/json/json_util.c/Makefile
new file mode 100644
index 00000000..c9a28208
--- /dev/null
+++ b/src/spdk/test/unit/lib/json/json_util.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = json_util_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/json/json_util.c/json_util_ut.c b/src/spdk/test/unit/lib/json/json_util.c/json_util_ut.c
new file mode 100644
index 00000000..203b744e
--- /dev/null
+++ b/src/spdk/test/unit/lib/json/json_util.c/json_util_ut.c
@@ -0,0 +1,963 @@
+/*-
+ * 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_cunit.h"
+
+#include "json/json_util.c"
+
+/* For spdk_json_parse() */
+#include "json/json_parse.c"
+
+#define NUM_SETUP(x) \
+ snprintf(buf, sizeof(buf), "%s", x); \
+ v.type = SPDK_JSON_VAL_NUMBER; \
+ v.start = buf; \
+ v.len = sizeof(x) - 1
+
+#define NUM_UINT16_PASS(s, i) \
+ NUM_SETUP(s); \
+ CU_ASSERT(spdk_json_number_to_uint16(&v, &u16) == 0); \
+ CU_ASSERT(u16 == i)
+
+#define NUM_UINT16_FAIL(s) \
+ NUM_SETUP(s); \
+ CU_ASSERT(spdk_json_number_to_uint16(&v, &u16) != 0)
+
+#define NUM_INT32_PASS(s, i) \
+ NUM_SETUP(s); \
+ CU_ASSERT(spdk_json_number_to_int32(&v, &i32) == 0); \
+ CU_ASSERT(i32 == i)
+
+#define NUM_INT32_FAIL(s) \
+ NUM_SETUP(s); \
+ CU_ASSERT(spdk_json_number_to_int32(&v, &i32) != 0)
+
+#define NUM_UINT64_PASS(s, i) \
+ NUM_SETUP(s); \
+ CU_ASSERT(spdk_json_number_to_uint64(&v, &u64) == 0); \
+ CU_ASSERT(u64 == i)
+
+#define NUM_UINT64_FAIL(s) \
+ NUM_SETUP(s); \
+ CU_ASSERT(spdk_json_number_to_uint64(&v, &u64) != 0)
+
+static void
+test_strequal(void)
+{
+ struct spdk_json_val v;
+
+ v.type = SPDK_JSON_VAL_STRING;
+ v.start = "test";
+ v.len = sizeof("test") - 1;
+ CU_ASSERT(spdk_json_strequal(&v, "test") == true);
+ CU_ASSERT(spdk_json_strequal(&v, "TEST") == false);
+ CU_ASSERT(spdk_json_strequal(&v, "hello") == false);
+ CU_ASSERT(spdk_json_strequal(&v, "t") == false);
+
+ v.type = SPDK_JSON_VAL_NAME;
+ CU_ASSERT(spdk_json_strequal(&v, "test") == true);
+
+ v.type = SPDK_JSON_VAL_NUMBER;
+ CU_ASSERT(spdk_json_strequal(&v, "test") == false);
+
+ v.type = SPDK_JSON_VAL_STRING;
+ v.start = "test\0hello";
+ v.len = sizeof("test\0hello") - 1;
+ CU_ASSERT(spdk_json_strequal(&v, "test") == false);
+}
+
+static void
+test_num_to_uint16(void)
+{
+ struct spdk_json_val v;
+ char buf[100];
+ uint16_t u16 = 0;
+
+ NUM_SETUP("1234");
+ CU_ASSERT(spdk_json_number_to_uint16(&v, &u16) == 0);
+ CU_ASSERT(u16 == 1234);
+
+ NUM_UINT16_PASS("0", 0);
+ NUM_UINT16_PASS("1234", 1234);
+ NUM_UINT16_PASS("1234.00000", 1234);
+ NUM_UINT16_PASS("1.2e1", 12);
+ NUM_UINT16_PASS("12340e-1", 1234);
+
+ NUM_UINT16_FAIL("1.2");
+ NUM_UINT16_FAIL("-1234");
+ NUM_UINT16_FAIL("1.2E0");
+ NUM_UINT16_FAIL("1.234e1");
+ NUM_UINT16_FAIL("12341e-1");
+}
+
+static void
+test_num_to_int32(void)
+{
+ struct spdk_json_val v;
+ char buf[100];
+ int32_t i32 = 0;
+
+ NUM_SETUP("1234");
+ CU_ASSERT(spdk_json_number_to_int32(&v, &i32) == 0);
+ CU_ASSERT(i32 == 1234);
+
+
+ NUM_INT32_PASS("0", 0);
+ NUM_INT32_PASS("1234", 1234);
+ NUM_INT32_PASS("-1234", -1234);
+ NUM_INT32_PASS("1234.00000", 1234);
+ NUM_INT32_PASS("1.2e1", 12);
+ NUM_INT32_PASS("12340e-1", 1234);
+ NUM_INT32_PASS("-0", 0);
+
+ NUM_INT32_FAIL("1.2");
+ NUM_INT32_FAIL("1.2E0");
+ NUM_INT32_FAIL("1.234e1");
+ NUM_INT32_FAIL("12341e-1");
+}
+
+static void
+test_num_to_uint64(void)
+{
+ struct spdk_json_val v;
+ char buf[100];
+ uint64_t u64 = 0;
+
+ NUM_SETUP("1234");
+ CU_ASSERT(spdk_json_number_to_uint64(&v, &u64) == 0);
+ CU_ASSERT(u64 == 1234);
+
+
+ NUM_UINT64_PASS("0", 0);
+ NUM_UINT64_PASS("1234", 1234);
+ NUM_UINT64_PASS("1234.00000", 1234);
+ NUM_UINT64_PASS("1.2e1", 12);
+ NUM_UINT64_PASS("12340e-1", 1234);
+ NUM_UINT64_PASS("123456780e-1", 12345678);
+
+ NUM_UINT64_FAIL("1.2");
+ NUM_UINT64_FAIL("-1234");
+ NUM_UINT64_FAIL("1.2E0");
+ NUM_UINT64_FAIL("1.234e1");
+ NUM_UINT64_FAIL("12341e-1");
+ NUM_UINT64_FAIL("123456781e-1");
+}
+
+static void
+test_decode_object(void)
+{
+ struct my_object {
+ char *my_name;
+ uint32_t my_int;
+ bool my_bool;
+ };
+ struct spdk_json_val object[] = {
+ {"", 6, SPDK_JSON_VAL_OBJECT_BEGIN},
+ {"first", 5, SPDK_JSON_VAL_NAME},
+ {"HELLO", 5, SPDK_JSON_VAL_STRING},
+ {"second", 6, SPDK_JSON_VAL_NAME},
+ {"234", 3, SPDK_JSON_VAL_NUMBER},
+ {"third", 5, SPDK_JSON_VAL_NAME},
+ {"", 1, SPDK_JSON_VAL_TRUE},
+ {"", 0, SPDK_JSON_VAL_OBJECT_END},
+ };
+
+ struct spdk_json_object_decoder decoders[] = {
+ {"first", offsetof(struct my_object, my_name), spdk_json_decode_string, false},
+ {"second", offsetof(struct my_object, my_int), spdk_json_decode_uint32, false},
+ {"third", offsetof(struct my_object, my_bool), spdk_json_decode_bool, false},
+ {"fourth", offsetof(struct my_object, my_bool), spdk_json_decode_bool, true},
+ };
+ struct my_object output = {
+ .my_name = NULL,
+ .my_int = 0,
+ .my_bool = false,
+ };
+ uint32_t answer = 234;
+ char *answer_str = "HELLO";
+ bool answer_bool = true;
+
+ /* Passing Test: object containing simple types */
+ CU_ASSERT(spdk_json_decode_object(object, decoders, 4, &output) == 0);
+ SPDK_CU_ASSERT_FATAL(output.my_name != NULL);
+ CU_ASSERT(memcmp(output.my_name, answer_str, 6) == 0);
+ CU_ASSERT(output.my_int == answer);
+ CU_ASSERT(output.my_bool == answer_bool);
+
+ /* Failing Test: member with no matching decoder */
+ /* i.e. I remove the matching decoder from the boolean argument */
+ CU_ASSERT(spdk_json_decode_object(object, decoders, 2, &output) != 0);
+
+ /* Failing Test: non-optional decoder with no corresponding member */
+
+ decoders[3].optional = false;
+ CU_ASSERT(spdk_json_decode_object(object, decoders, 4, &output) != 0);
+
+ /* return to base state */
+ decoders[3].optional = true;
+
+ /* Failing Test: duplicated names for json values */
+ object[3].start = "first";
+ object[3].len = 5;
+ CU_ASSERT(spdk_json_decode_object(object, decoders, 3, &output) != 0);
+
+ /* return to base state */
+ object[3].start = "second";
+ object[3].len = 6;
+
+ /* Failing Test: invalid value for decoder */
+ object[2].start = "HELO";
+ CU_ASSERT(spdk_json_decode_object(object, decoders, 3, &output) != 0);
+
+ /* return to base state */
+ object[2].start = "HELLO";
+
+ /* Failing Test: not an object */
+ object[0].type = SPDK_JSON_VAL_ARRAY_BEGIN;
+ CU_ASSERT(spdk_json_decode_object(object, decoders, 3, &output) != 0);
+
+ free(output.my_name);
+}
+
+static void
+test_decode_array(void)
+{
+ struct spdk_json_val values[4];
+ uint32_t my_int[2] = {0, 0};
+ char *my_string[2] = {NULL, NULL};
+ size_t out_size;
+
+ /* passing integer test */
+ values[0].type = SPDK_JSON_VAL_ARRAY_BEGIN;
+ values[0].len = 2;
+ values[1].type = SPDK_JSON_VAL_NUMBER;
+ values[1].len = 4;
+ values[1].start = "1234";
+ values[2].type = SPDK_JSON_VAL_NUMBER;
+ values[2].len = 4;
+ values[2].start = "5678";
+ values[3].type = SPDK_JSON_VAL_ARRAY_END;
+ CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_uint32, my_int, 2, &out_size,
+ sizeof(uint32_t)) == 0);
+ CU_ASSERT(my_int[0] == 1234);
+ CU_ASSERT(my_int[1] == 5678);
+ CU_ASSERT(out_size == 2);
+
+ /* array length exceeds max */
+ values[0].len = 3;
+ CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_uint32, my_int, 2, &out_size,
+ sizeof(uint32_t)) != 0);
+
+ /* mixed types */
+ values[0].len = 2;
+ values[2].type = SPDK_JSON_VAL_STRING;
+ values[2].len = 5;
+ values[2].start = "HELLO";
+ CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_uint32, my_int, 2, &out_size,
+ sizeof(uint32_t)) != 0);
+
+ /* no array start */
+ values[0].type = SPDK_JSON_VAL_NUMBER;
+ values[2].type = SPDK_JSON_VAL_NUMBER;
+ values[2].len = 4;
+ values[2].start = "5678";
+ CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_uint32, my_int, 2, &out_size,
+ sizeof(uint32_t)) != 0);
+
+ /* mismatched array type and parser */
+ values[0].type = SPDK_JSON_VAL_ARRAY_BEGIN;
+ values[1].type = SPDK_JSON_VAL_STRING;
+ values[1].len = 5;
+ values[1].start = "HELLO";
+ values[2].type = SPDK_JSON_VAL_STRING;
+ values[2].len = 5;
+ values[2].start = "WORLD";
+ CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_uint32, my_int, 2, &out_size,
+ sizeof(uint32_t)) != 0);
+
+ /* passing String example */
+ CU_ASSERT(spdk_json_decode_array(values, spdk_json_decode_string, my_string, 2, &out_size,
+ sizeof(char *)) == 0);
+ SPDK_CU_ASSERT_FATAL(my_string[0] != NULL);
+ SPDK_CU_ASSERT_FATAL(my_string[1] != NULL);
+ CU_ASSERT(memcmp(my_string[0], "HELLO", 6) == 0);
+ CU_ASSERT(memcmp(my_string[1], "WORLD", 6) == 0);
+ CU_ASSERT(out_size == 2);
+
+ free(my_string[0]);
+ free(my_string[1]);
+}
+
+static void
+test_decode_bool(void)
+{
+ struct spdk_json_val v;
+ bool b;
+
+ /* valid bool (true) */
+ v.type = SPDK_JSON_VAL_TRUE;
+ b = false;
+ CU_ASSERT(spdk_json_decode_bool(&v, &b) == 0);
+ CU_ASSERT(b == true);
+
+ /* valid bool (false) */
+ v.type = SPDK_JSON_VAL_FALSE;
+ b = true;
+ CU_ASSERT(spdk_json_decode_bool(&v, &b) == 0);
+ CU_ASSERT(b == false);
+
+ /* incorrect type */
+ v.type = SPDK_JSON_VAL_NULL;
+ CU_ASSERT(spdk_json_decode_bool(&v, &b) != 0);
+}
+
+static void
+test_decode_int32(void)
+{
+ struct spdk_json_val v;
+ int32_t i;
+
+ /* correct type and valid value */
+ v.type = SPDK_JSON_VAL_NUMBER;
+ v.start = "33";
+ v.len = 2;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0);
+ CU_ASSERT(i == 33)
+
+ /* correct type and invalid value (float) */
+ v.start = "32.45";
+ v.len = 5;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0);
+
+ /* incorrect type */
+ v.type = SPDK_JSON_VAL_STRING;
+ v.start = "String";
+ v.len = 6;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0);
+
+ /* incorrect type */
+ v.type = SPDK_JSON_VAL_TRUE;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0);
+
+ /* edge case (integer max) */
+ v.type = SPDK_JSON_VAL_NUMBER;
+ v.start = "2147483647";
+ v.len = 10;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0);
+ CU_ASSERT(i == 2147483647);
+
+ /* invalid value (overflow) */
+ v.start = "2147483648";
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0);
+
+ /* edge case (integer min) */
+ v.type = SPDK_JSON_VAL_NUMBER;
+ v.start = "-2147483648";
+ v.len = 11;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0);
+ CU_ASSERT(i == -2147483648);
+
+ /* invalid value (overflow) */
+ v.start = "-2147483649";
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0);
+
+ /* valid exponent */
+ v.start = "4e3";
+ v.len = 3;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0);
+ CU_ASSERT(i == 4000);
+
+ /* invalid negative exponent */
+ v.start = "-400e-4";
+ v.len = 7;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0);
+
+ /* invalid negative exponent */
+ v.start = "400e-4";
+ v.len = 6;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0);
+
+ /* valid negative exponent */
+ v.start = "-400e-2";
+ v.len = 7;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0);
+ CU_ASSERT(i == -4)
+
+ /* invalid exponent (overflow) */
+ v.start = "-2e32";
+ v.len = 5;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0);
+
+ /* valid exponent with decimal */
+ v.start = "2.13e2";
+ v.len = 6;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) == 0);
+ CU_ASSERT(i == 213)
+
+ /* invalid exponent with decimal */
+ v.start = "2.134e2";
+ v.len = 7;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_int32(&v, &i) != 0);
+}
+
+static void
+test_decode_uint16(void)
+{
+ struct spdk_json_val v;
+ uint32_t i;
+
+ /* incorrect type */
+ v.type = SPDK_JSON_VAL_STRING;
+ v.start = "Strin";
+ v.len = 5;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0);
+
+ /* invalid value (float) */
+ v.type = SPDK_JSON_VAL_NUMBER;
+ v.start = "123.4";
+ v.len = 5;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0);
+
+ /* edge case (0) */
+ v.start = "0";
+ v.len = 1;
+ i = 456;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) == 0);
+ CU_ASSERT(i == 0);
+
+ /* invalid value (negative) */
+ v.start = "-1";
+ v.len = 2;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0);
+
+ /* edge case (maximum) */
+ v.start = "65535";
+ v.len = 5;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) == 0);
+ CU_ASSERT(i == 65535);
+
+ /* invalid value (overflow) */
+ v.start = "65536";
+ v.len = 5;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0);
+
+ /* valid exponent */
+ v.start = "66E2";
+ v.len = 4;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) == 0);
+ CU_ASSERT(i == 6600);
+
+ /* invalid exponent (overflow) */
+ v.start = "66E3";
+ v.len = 4;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0);
+
+ /* invalid exponent (decimal) */
+ v.start = "65.535E2";
+ v.len = 7;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0);
+
+ /* valid exponent with decimal */
+ v.start = "65.53E2";
+ v.len = 7;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) == 0);
+ CU_ASSERT(i == 6553);
+
+ /* invalid negative exponent */
+ v.start = "40e-2";
+ v.len = 5;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0);
+
+ /* invalid negative exponent */
+ v.start = "-40e-1";
+ v.len = 6;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) != 0);
+
+ /* valid negative exponent */
+ v.start = "40e-1";
+ v.len = 5;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint16(&v, &i) == 0);
+ CU_ASSERT(i == 4);
+}
+
+static void
+test_decode_uint32(void)
+{
+ struct spdk_json_val v;
+ uint32_t i;
+
+ /* incorrect type */
+ v.type = SPDK_JSON_VAL_STRING;
+ v.start = "String";
+ v.len = 6;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0);
+
+ /* invalid value (float) */
+ v.type = SPDK_JSON_VAL_NUMBER;
+ v.start = "123.45";
+ v.len = 6;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0);
+
+ /* edge case (0) */
+ v.start = "0";
+ v.len = 1;
+ i = 456;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0);
+ CU_ASSERT(i == 0);
+
+ /* invalid value (negative) */
+ v.start = "-1";
+ v.len = 2;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0);
+
+ /* edge case (maximum) */
+ v.start = "4294967295";
+ v.len = 10;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0);
+ CU_ASSERT(i == 4294967295);
+
+ /* invalid value (overflow) */
+ v.start = "4294967296";
+ v.len = 10;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0);
+
+ /* valid exponent */
+ v.start = "42E2";
+ v.len = 4;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0);
+ CU_ASSERT(i == 4200);
+
+ /* invalid exponent (overflow) */
+ v.start = "42e32";
+ v.len = 5;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0);
+
+ /* invalid exponent (decimal) */
+ v.start = "42.323E2";
+ v.len = 8;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0);
+
+ /* valid exponent with decimal */
+ v.start = "42.32E2";
+ v.len = 7;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0);
+ CU_ASSERT(i == 4232);
+
+ /* invalid negative exponent */
+ v.start = "400e-4";
+ v.len = 6;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0);
+
+ /* invalid negative exponent */
+ v.start = "-400e-2";
+ v.len = 7;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) != 0);
+
+ /* valid negative exponent */
+ v.start = "400e-2";
+ v.len = 6;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0);
+ CU_ASSERT(i == 4);
+
+ /* valid negative exponent */
+ v.start = "10e-1";
+ v.len = 5;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint32(&v, &i) == 0);
+ CU_ASSERT(i == 1)
+}
+
+static void
+test_decode_uint64(void)
+{
+ struct spdk_json_val v;
+ uint64_t i;
+
+ /* incorrect type */
+ v.type = SPDK_JSON_VAL_STRING;
+ v.start = "String";
+ v.len = 6;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0);
+
+ /* invalid value (float) */
+ v.type = SPDK_JSON_VAL_NUMBER;
+ v.start = "123.45";
+ v.len = 6;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0);
+
+ /* edge case (0) */
+ v.start = "0";
+ v.len = 1;
+ i = 456;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) == 0);
+ CU_ASSERT(i == 0);
+
+ /* invalid value (negative) */
+ v.start = "-1";
+ v.len = 2;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0);
+
+ /* edge case (maximum) */
+ v.start = "18446744073709551615";
+ v.len = 20;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) == 0);
+ CU_ASSERT(i == 18446744073709551615U);
+
+ /* invalid value (overflow) */
+ v.start = "18446744073709551616";
+ v.len = 20;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0);
+
+ /* valid exponent */
+ v.start = "42E2";
+ v.len = 4;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) == 0);
+ CU_ASSERT(i == 4200);
+
+ /* invalid exponent (overflow) */
+ v.start = "42e64";
+ v.len = 5;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0);
+
+ /* invalid exponent (decimal) */
+ v.start = "42.323E2";
+ v.len = 8;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0);
+
+ /* valid exponent with decimal */
+ v.start = "42.32E2";
+ v.len = 7;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) == 0);
+ CU_ASSERT(i == 4232);
+
+ /* invalid negative exponent */
+ v.start = "400e-4";
+ v.len = 6;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0);
+
+ /* invalid negative exponent */
+ v.start = "-400e-2";
+ v.len = 7;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) != 0);
+
+ /* valid negative exponent */
+ v.start = "400e-2";
+ v.len = 6;
+ i = 0;
+ CU_ASSERT(spdk_json_decode_uint64(&v, &i) == 0);
+ CU_ASSERT(i == 4)
+}
+
+static void
+test_decode_string(void)
+{
+ struct spdk_json_val v;
+ char *value = NULL;
+
+ /* Passing Test: Standard */
+ v.type = SPDK_JSON_VAL_STRING;
+ v.start = "HELLO";
+ v.len = 5;
+ CU_ASSERT(spdk_json_decode_string(&v, &value) == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(memcmp(value, v.start, 6) == 0);
+
+ /* Edge Test: Empty String */
+ v.start = "";
+ v.len = 0;
+ CU_ASSERT(spdk_json_decode_string(&v, &value) == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(memcmp(value, v.start, 1) == 0);
+
+ /*
+ * Failing Test: Null Terminator In String
+ * It is valid for a json string to contain \u0000 and the parser will accept it.
+ * However, a null terminated C string cannot contain '\0' and should be rejected
+ * if that character is found before the end of the string.
+ */
+ v.start = "HELO";
+ v.len = 5;
+ CU_ASSERT(spdk_json_decode_string(&v, &value) != 0);
+
+ /* Failing Test: Wrong Type */
+ v.start = "45673";
+ v.type = SPDK_JSON_VAL_NUMBER;
+ CU_ASSERT(spdk_json_decode_string(&v, &value) != 0);
+
+ /* Passing Test: Special Characters */
+ v.type = SPDK_JSON_VAL_STRING;
+ v.start = "HE\bLL\tO\\WORLD";
+ v.len = 13;
+ CU_ASSERT(spdk_json_decode_string(&v, &value) == 0);
+ SPDK_CU_ASSERT_FATAL(value != NULL);
+ CU_ASSERT(memcmp(value, v.start, 14) == 0);
+
+ free(value);
+}
+
+char ut_json_text[] =
+ "{"
+ " \"string\": \"Some string data\","
+ " \"object\": { "
+ " \"another_string\": \"Yet anoter string data\","
+ " \"array name with space\": [1, [], {} ]"
+ " },"
+ " \"array\": [ \"Text\", 2, {} ]"
+ "}"
+ ;
+
+static void
+test_find(void)
+{
+ struct spdk_json_val *values, *key, *val, *key2, *val2;
+ ssize_t values_cnt;
+ ssize_t rc;
+
+ values_cnt = spdk_json_parse(ut_json_text, strlen(ut_json_text), NULL, 0, NULL, 0);
+ SPDK_CU_ASSERT_FATAL(values_cnt > 0);
+
+ values = calloc(values_cnt, sizeof(struct spdk_json_val));
+ SPDK_CU_ASSERT_FATAL(values != NULL);
+
+ rc = spdk_json_parse(ut_json_text, strlen(ut_json_text), values, values_cnt, NULL, 0);
+ SPDK_CU_ASSERT_FATAL(values_cnt == rc);
+
+ key = val = NULL;
+ rc = spdk_json_find(values, "string", &key, &val, SPDK_JSON_VAL_STRING);
+ CU_ASSERT(rc == 0);
+
+ CU_ASSERT(key != NULL && spdk_json_strequal(key, "string") == true);
+ CU_ASSERT(val != NULL && spdk_json_strequal(val, "Some string data") == true)
+
+ key = val = NULL;
+ rc = spdk_json_find(values, "object", &key, &val, SPDK_JSON_VAL_OBJECT_BEGIN);
+ CU_ASSERT(rc == 0);
+
+ CU_ASSERT(key != NULL && spdk_json_strequal(key, "object") == true);
+
+ /* Find key in "object" by passing SPDK_JSON_VAL_ANY to match any type */
+ key2 = val2 = NULL;
+ rc = spdk_json_find(val, "array name with space", &key2, &val2, SPDK_JSON_VAL_ANY);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(key2 != NULL && spdk_json_strequal(key2, "array name with space") == true);
+ CU_ASSERT(val2 != NULL && val2->type == SPDK_JSON_VAL_ARRAY_BEGIN);
+
+ /* Find the "array" key in "object" by passing SPDK_JSON_VAL_ARRAY_BEGIN to match only array */
+ key2 = val2 = NULL;
+ rc = spdk_json_find(val, "array name with space", &key2, &val2, SPDK_JSON_VAL_ARRAY_BEGIN);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(key2 != NULL && spdk_json_strequal(key2, "array name with space") == true);
+ CU_ASSERT(val2 != NULL && val2->type == SPDK_JSON_VAL_ARRAY_BEGIN);
+
+ /* Negative test - key doesn't exist */
+ key2 = val2 = NULL;
+ rc = spdk_json_find(val, "this_key_does_not_exist", &key2, &val2, SPDK_JSON_VAL_ANY);
+ CU_ASSERT(rc == -ENOENT);
+
+ /* Negative test - key type doesn't match */
+ key2 = val2 = NULL;
+ rc = spdk_json_find(val, "another_string", &key2, &val2, SPDK_JSON_VAL_ARRAY_BEGIN);
+ CU_ASSERT(rc == -EDOM);
+
+ free(values);
+}
+
+static void
+test_iterating(void)
+{
+ struct spdk_json_val *values;
+ struct spdk_json_val *string_key;
+ struct spdk_json_val *object_key, *object_val;
+ struct spdk_json_val *array_key, *array_val;
+ struct spdk_json_val *another_string_key;
+ struct spdk_json_val *array_name_with_space_key, *array_name_with_space_val;
+ struct spdk_json_val *it;
+ ssize_t values_cnt;
+ ssize_t rc;
+
+ values_cnt = spdk_json_parse(ut_json_text, strlen(ut_json_text), NULL, 0, NULL, 0);
+ SPDK_CU_ASSERT_FATAL(values_cnt > 0);
+
+ values = calloc(values_cnt, sizeof(struct spdk_json_val));
+ SPDK_CU_ASSERT_FATAL(values != NULL);
+
+ rc = spdk_json_parse(ut_json_text, strlen(ut_json_text), values, values_cnt, NULL, 0);
+ SPDK_CU_ASSERT_FATAL(values_cnt == rc);
+
+ /* Iterate over object keys. JSON spec doesn't guarantee order of keys in object but
+ * SPDK implementation implicitly does.
+ */
+ string_key = spdk_json_object_first(values);
+ CU_ASSERT(spdk_json_strequal(string_key, "string") == true);
+
+ object_key = spdk_json_next(string_key);
+ object_val = spdk_json_value(object_key);
+ CU_ASSERT(spdk_json_strequal(object_key, "object") == true);
+
+ array_key = spdk_json_next(object_key);
+ array_val = spdk_json_value(array_key);
+ CU_ASSERT(spdk_json_strequal(array_key, "array") == true);
+
+ /* NULL '}' */
+ CU_ASSERT(spdk_json_next(array_key) == NULL);
+
+ /* Iterate over subobjects */
+ another_string_key = spdk_json_object_first(object_val);
+ CU_ASSERT(spdk_json_strequal(another_string_key, "another_string") == true);
+
+ array_name_with_space_key = spdk_json_next(another_string_key);
+ array_name_with_space_val = spdk_json_value(array_name_with_space_key);
+ CU_ASSERT(spdk_json_strequal(array_name_with_space_key, "array name with space") == true);
+
+ CU_ASSERT(spdk_json_next(array_name_with_space_key) == NULL);
+
+ /* Iterate over array in subobject */
+ it = spdk_json_array_first(array_name_with_space_val);
+ SPDK_CU_ASSERT_FATAL(it != NULL);
+ CU_ASSERT(it->type == SPDK_JSON_VAL_NUMBER);
+
+ it = spdk_json_next(it);
+ SPDK_CU_ASSERT_FATAL(it != NULL);
+ CU_ASSERT(it->type == SPDK_JSON_VAL_ARRAY_BEGIN);
+
+ it = spdk_json_next(it);
+ SPDK_CU_ASSERT_FATAL(it != NULL);
+ CU_ASSERT(it->type == SPDK_JSON_VAL_OBJECT_BEGIN);
+
+ it = spdk_json_next(it);
+ CU_ASSERT(it == NULL);
+
+ /* Iterate over array in root object */
+ it = spdk_json_array_first(array_val);
+ SPDK_CU_ASSERT_FATAL(it != NULL);
+ CU_ASSERT(it->type == SPDK_JSON_VAL_STRING);
+
+ it = spdk_json_next(it);
+ SPDK_CU_ASSERT_FATAL(it != NULL);
+ CU_ASSERT(it->type == SPDK_JSON_VAL_NUMBER);
+
+ it = spdk_json_next(it);
+ SPDK_CU_ASSERT_FATAL(it != NULL);
+ CU_ASSERT(it->type == SPDK_JSON_VAL_OBJECT_BEGIN);
+
+ /* Array end */
+ it = spdk_json_next(it);
+ CU_ASSERT(it == NULL);
+
+ free(values);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("json", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "strequal", test_strequal) == NULL ||
+ CU_add_test(suite, "num_to_uint16", test_num_to_uint16) == NULL ||
+ CU_add_test(suite, "num_to_int32", test_num_to_int32) == NULL ||
+ CU_add_test(suite, "num_to_uint64", test_num_to_uint64) == NULL ||
+ CU_add_test(suite, "decode_object", test_decode_object) == NULL ||
+ CU_add_test(suite, "decode_array", test_decode_array) == NULL ||
+ CU_add_test(suite, "decode_bool", test_decode_bool) == NULL ||
+ CU_add_test(suite, "decode_uint16", test_decode_uint16) == NULL ||
+ CU_add_test(suite, "decode_int32", test_decode_int32) == NULL ||
+ CU_add_test(suite, "decode_uint32", test_decode_uint32) == NULL ||
+ CU_add_test(suite, "decode_uint64", test_decode_uint64) == NULL ||
+ CU_add_test(suite, "decode_string", test_decode_string) == NULL ||
+ CU_add_test(suite, "find_object", test_find) == NULL ||
+ CU_add_test(suite, "iterating", test_iterating) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/json/json_write.c/.gitignore b/src/spdk/test/unit/lib/json/json_write.c/.gitignore
new file mode 100644
index 00000000..dd576b23
--- /dev/null
+++ b/src/spdk/test/unit/lib/json/json_write.c/.gitignore
@@ -0,0 +1 @@
+json_write_ut
diff --git a/src/spdk/test/unit/lib/json/json_write.c/Makefile b/src/spdk/test/unit/lib/json/json_write.c/Makefile
new file mode 100644
index 00000000..9fe1fa91
--- /dev/null
+++ b/src/spdk/test/unit/lib/json/json_write.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = json_write_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/json/json_write.c/json_write_ut.c b/src/spdk/test/unit/lib/json/json_write.c/json_write_ut.c
new file mode 100644
index 00000000..70c62fe1
--- /dev/null
+++ b/src/spdk/test/unit/lib/json/json_write.c/json_write_ut.c
@@ -0,0 +1,745 @@
+/*-
+ * 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_cunit.h"
+
+#include "json/json_write.c"
+#include "json/json_parse.c"
+
+#include "spdk/util.h"
+
+static uint8_t g_buf[1000];
+static uint8_t *g_write_pos;
+
+static int
+write_cb(void *cb_ctx, const void *data, size_t size)
+{
+ size_t buf_free = g_buf + sizeof(g_buf) - g_write_pos;
+
+ if (size > buf_free) {
+ return -1;
+ }
+
+ memcpy(g_write_pos, data, size);
+ g_write_pos += size;
+
+ return 0;
+}
+
+#define BEGIN() \
+ memset(g_buf, 0, sizeof(g_buf)); \
+ g_write_pos = g_buf; \
+ w = spdk_json_write_begin(write_cb, NULL, 0); \
+ SPDK_CU_ASSERT_FATAL(w != NULL)
+
+#define END(json) \
+ CU_ASSERT(spdk_json_write_end(w) == 0); \
+ CU_ASSERT(g_write_pos - g_buf == sizeof(json) - 1); \
+ CU_ASSERT(memcmp(json, g_buf, sizeof(json) - 1) == 0)
+
+#define END_NOCMP() \
+ CU_ASSERT(spdk_json_write_end(w) == 0)
+
+#define END_FAIL() \
+ CU_ASSERT(spdk_json_write_end(w) < 0)
+
+#define VAL_STRING(str) \
+ CU_ASSERT(spdk_json_write_string_raw(w, str, sizeof(str) - 1) == 0)
+
+#define VAL_STRING_FAIL(str) \
+ CU_ASSERT(spdk_json_write_string_raw(w, str, sizeof(str) - 1) < 0)
+
+#define STR_PASS(in, out) \
+ BEGIN(); VAL_STRING(in); END("\"" out "\"")
+
+#define STR_FAIL(in) \
+ BEGIN(); VAL_STRING_FAIL(in); END_FAIL()
+
+#define VAL_STRING_UTF16LE(str) \
+ CU_ASSERT(spdk_json_write_string_utf16le_raw(w, (const uint16_t *)str, sizeof(str) / sizeof(uint16_t) - 1) == 0)
+
+#define VAL_STRING_UTF16LE_FAIL(str) \
+ CU_ASSERT(spdk_json_write_string_utf16le_raw(w, (const uint16_t *)str, sizeof(str) / sizeof(uint16_t) - 1) < 0)
+
+#define STR_UTF16LE_PASS(in, out) \
+ BEGIN(); VAL_STRING_UTF16LE(in); END("\"" out "\"")
+
+#define STR_UTF16LE_FAIL(in) \
+ BEGIN(); VAL_STRING_UTF16LE_FAIL(in); END_FAIL()
+
+#define VAL_NAME(name) \
+ CU_ASSERT(spdk_json_write_name_raw(w, name, sizeof(name) - 1) == 0)
+
+#define VAL_NULL() CU_ASSERT(spdk_json_write_null(w) == 0)
+#define VAL_TRUE() CU_ASSERT(spdk_json_write_bool(w, true) == 0)
+#define VAL_FALSE() CU_ASSERT(spdk_json_write_bool(w, false) == 0)
+
+#define VAL_INT32(i) CU_ASSERT(spdk_json_write_int32(w, i) == 0);
+#define VAL_UINT32(u) CU_ASSERT(spdk_json_write_uint32(w, u) == 0);
+
+#define VAL_INT64(i) CU_ASSERT(spdk_json_write_int64(w, i) == 0);
+#define VAL_UINT64(u) CU_ASSERT(spdk_json_write_uint64(w, u) == 0);
+
+#define VAL_ARRAY_BEGIN() CU_ASSERT(spdk_json_write_array_begin(w) == 0)
+#define VAL_ARRAY_END() CU_ASSERT(spdk_json_write_array_end(w) == 0)
+
+#define VAL_OBJECT_BEGIN() CU_ASSERT(spdk_json_write_object_begin(w) == 0)
+#define VAL_OBJECT_END() CU_ASSERT(spdk_json_write_object_end(w) == 0)
+
+#define VAL(v) CU_ASSERT(spdk_json_write_val(w, v) == 0)
+
+static void
+test_write_literal(void)
+{
+ struct spdk_json_write_ctx *w;
+
+ BEGIN();
+ VAL_NULL();
+ END("null");
+
+ BEGIN();
+ VAL_TRUE();
+ END("true");
+
+ BEGIN();
+ VAL_FALSE();
+ END("false");
+}
+
+static void
+test_write_string_simple(void)
+{
+ struct spdk_json_write_ctx *w;
+
+ STR_PASS("hello world", "hello world");
+ STR_PASS(" ", " ");
+ STR_PASS("~", "~");
+}
+
+static void
+test_write_string_escapes(void)
+{
+ struct spdk_json_write_ctx *w;
+
+ /* Two-character escapes */
+ STR_PASS("\b", "\\b");
+ STR_PASS("\f", "\\f");
+ STR_PASS("\n", "\\n");
+ STR_PASS("\r", "\\r");
+ STR_PASS("\t", "\\t");
+ STR_PASS("\"", "\\\"");
+ STR_PASS("\\", "\\\\");
+
+ /* JSON defines an escape for forward slash, but it is optional */
+ STR_PASS("/", "/");
+
+ STR_PASS("hello\nworld", "hello\\nworld");
+
+ STR_PASS("\x00", "\\u0000");
+ STR_PASS("\x01", "\\u0001");
+ STR_PASS("\x02", "\\u0002");
+
+ STR_PASS("\xC3\xB6", "\\u00F6");
+ STR_PASS("\xE2\x88\x9A", "\\u221A");
+ STR_PASS("\xEA\xAA\xAA", "\\uAAAA");
+
+ /* Surrogate pairs */
+ STR_PASS("\xF0\x9D\x84\x9E", "\\uD834\\uDD1E");
+ STR_PASS("\xF0\xA0\x9C\x8E", "\\uD841\\uDF0E");
+
+ /* Examples from RFC 3629 */
+ STR_PASS("\x41\xE2\x89\xA2\xCE\x91\x2E", "A\\u2262\\u0391.");
+ STR_PASS("\xED\x95\x9C\xEA\xB5\xAD\xEC\x96\xB4", "\\uD55C\\uAD6D\\uC5B4");
+ STR_PASS("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E", "\\u65E5\\u672C\\u8A9E");
+ STR_PASS("\xEF\xBB\xBF\xF0\xA3\x8E\xB4", "\\uFEFF\\uD84C\\uDFB4");
+
+ /* UTF-8 edge cases */
+ STR_PASS("\x7F", "\\u007F");
+ STR_FAIL("\x80");
+ STR_FAIL("\xC1");
+ STR_FAIL("\xC2");
+ STR_PASS("\xC2\x80", "\\u0080");
+ STR_PASS("\xC2\xBF", "\\u00BF");
+ STR_PASS("\xDF\x80", "\\u07C0");
+ STR_PASS("\xDF\xBF", "\\u07FF");
+ STR_FAIL("\xDF");
+ STR_FAIL("\xE0\x80");
+ STR_FAIL("\xE0\x1F");
+ STR_FAIL("\xE0\x1F\x80");
+ STR_FAIL("\xE0");
+ STR_FAIL("\xE0\xA0");
+ STR_PASS("\xE0\xA0\x80", "\\u0800");
+ STR_PASS("\xE0\xA0\xBF", "\\u083F");
+ STR_FAIL("\xE0\xA0\xC0");
+ STR_PASS("\xE0\xBF\x80", "\\u0FC0");
+ STR_PASS("\xE0\xBF\xBF", "\\u0FFF");
+ STR_FAIL("\xE0\xC0\x80");
+ STR_FAIL("\xE1");
+ STR_FAIL("\xE1\x80");
+ STR_FAIL("\xE1\x7F\x80");
+ STR_FAIL("\xE1\x80\x7F");
+ STR_PASS("\xE1\x80\x80", "\\u1000");
+ STR_PASS("\xE1\x80\xBF", "\\u103F");
+ STR_PASS("\xE1\xBF\x80", "\\u1FC0");
+ STR_PASS("\xE1\xBF\xBF", "\\u1FFF");
+ STR_FAIL("\xE1\xC0\x80");
+ STR_FAIL("\xE1\x80\xC0");
+ STR_PASS("\xEF\x80\x80", "\\uF000");
+ STR_PASS("\xEF\xBF\xBF", "\\uFFFF");
+ STR_FAIL("\xF0");
+ STR_FAIL("\xF0\x90");
+ STR_FAIL("\xF0\x90\x80");
+ STR_FAIL("\xF0\x80\x80\x80");
+ STR_FAIL("\xF0\x8F\x80\x80");
+ STR_PASS("\xF0\x90\x80\x80", "\\uD800\\uDC00");
+ STR_PASS("\xF0\x90\x80\xBF", "\\uD800\\uDC3F");
+ STR_PASS("\xF0\x90\xBF\x80", "\\uD803\\uDFC0");
+ STR_PASS("\xF0\xBF\x80\x80", "\\uD8BC\\uDC00");
+ STR_FAIL("\xF0\xC0\x80\x80");
+ STR_FAIL("\xF1");
+ STR_FAIL("\xF1\x80");
+ STR_FAIL("\xF1\x80\x80");
+ STR_FAIL("\xF1\x80\x80\x7F");
+ STR_PASS("\xF1\x80\x80\x80", "\\uD8C0\\uDC00");
+ STR_PASS("\xF1\x80\x80\xBF", "\\uD8C0\\uDC3F");
+ STR_PASS("\xF1\x80\xBF\x80", "\\uD8C3\\uDFC0");
+ STR_PASS("\xF1\xBF\x80\x80", "\\uD9BC\\uDC00");
+ STR_PASS("\xF3\x80\x80\x80", "\\uDAC0\\uDC00");
+ STR_FAIL("\xF3\xC0\x80\x80");
+ STR_FAIL("\xF3\x80\xC0\x80");
+ STR_FAIL("\xF3\x80\x80\xC0");
+ STR_FAIL("\xF4");
+ STR_FAIL("\xF4\x80");
+ STR_FAIL("\xF4\x80\x80");
+ STR_PASS("\xF4\x80\x80\x80", "\\uDBC0\\uDC00");
+ STR_PASS("\xF4\x8F\x80\x80", "\\uDBFC\\uDC00");
+ STR_PASS("\xF4\x8F\xBF\xBF", "\\uDBFF\\uDFFF");
+ STR_FAIL("\xF4\x90\x80\x80");
+ STR_FAIL("\xF5");
+ STR_FAIL("\xF5\x80");
+ STR_FAIL("\xF5\x80\x80");
+ STR_FAIL("\xF5\x80\x80\x80");
+ STR_FAIL("\xF5\x80\x80\x80\x80");
+
+ /* Overlong encodings */
+ STR_FAIL("\xC0\x80");
+
+ /* Surrogate pairs */
+ STR_FAIL("\xED\xA0\x80"); /* U+D800 First high surrogate */
+ STR_FAIL("\xED\xAF\xBF"); /* U+DBFF Last high surrogate */
+ STR_FAIL("\xED\xB0\x80"); /* U+DC00 First low surrogate */
+ STR_FAIL("\xED\xBF\xBF"); /* U+DFFF Last low surrogate */
+ STR_FAIL("\xED\xA1\x8C\xED\xBE\xB4"); /* U+233B4 (invalid surrogate pair encoding) */
+}
+
+static void
+test_write_string_utf16le(void)
+{
+ struct spdk_json_write_ctx *w;
+
+ /* All characters in BMP */
+ STR_UTF16LE_PASS(((uint8_t[]) {
+ 'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0, 0x15, 0xFE, 0, 0
+ }), "Hello\\uFE15");
+
+ /* Surrogate pair */
+ STR_UTF16LE_PASS(((uint8_t[]) {
+ 'H', 0, 'i', 0, 0x34, 0xD8, 0x1E, 0xDD, '!', 0, 0, 0
+ }), "Hi\\uD834\\uDD1E!");
+
+ /* Valid high surrogate, but no low surrogate */
+ STR_UTF16LE_FAIL(((uint8_t[]) {
+ 0x00, 0xD8, 0, 0 /* U+D800 */
+ }));
+
+ /* Invalid leading low surrogate */
+ STR_UTF16LE_FAIL(((uint8_t[]) {
+ 0x00, 0xDC, 0x00, 0xDC, 0, 0 /* U+DC00 U+DC00 */
+ }));
+
+ /* Valid high surrogate followed by another high surrogate (invalid) */
+ STR_UTF16LE_FAIL(((uint8_t[]) {
+ 0x00, 0xD8, 0x00, 0xD8, 0, 0 /* U+D800 U+D800 */
+ }));
+}
+
+static void
+test_write_number_int32(void)
+{
+ struct spdk_json_write_ctx *w;
+
+ BEGIN();
+ VAL_INT32(0);
+ END("0");
+
+ BEGIN();
+ VAL_INT32(1);
+ END("1");
+
+ BEGIN();
+ VAL_INT32(123);
+ END("123");
+
+ BEGIN();
+ VAL_INT32(-123);
+ END("-123");
+
+ BEGIN();
+ VAL_INT32(2147483647);
+ END("2147483647");
+
+ BEGIN();
+ VAL_INT32(-2147483648);
+ END("-2147483648");
+}
+
+static void
+test_write_number_uint32(void)
+{
+ struct spdk_json_write_ctx *w;
+
+ BEGIN();
+ VAL_UINT32(0);
+ END("0");
+
+ BEGIN();
+ VAL_UINT32(1);
+ END("1");
+
+ BEGIN();
+ VAL_UINT32(123);
+ END("123");
+
+ BEGIN();
+ VAL_UINT32(2147483647);
+ END("2147483647");
+
+ BEGIN();
+ VAL_UINT32(4294967295);
+ END("4294967295");
+}
+
+static void
+test_write_number_int64(void)
+{
+ struct spdk_json_write_ctx *w;
+
+ BEGIN();
+ VAL_INT64(0);
+ END("0");
+
+ BEGIN();
+ VAL_INT64(1);
+ END("1");
+
+ BEGIN();
+ VAL_INT64(123);
+ END("123");
+
+ BEGIN();
+ VAL_INT64(-123);
+ END("-123");
+
+ BEGIN();
+ VAL_INT64(INT64_MAX);
+ END("9223372036854775807");
+
+ BEGIN();
+ VAL_INT64(INT64_MIN);
+ END("-9223372036854775808");
+}
+
+static void
+test_write_number_uint64(void)
+{
+ struct spdk_json_write_ctx *w;
+
+ BEGIN();
+ VAL_UINT64(0);
+ END("0");
+
+ BEGIN();
+ VAL_UINT64(1);
+ END("1");
+
+ BEGIN();
+ VAL_UINT64(123);
+ END("123");
+
+ BEGIN();
+ VAL_UINT64(INT64_MAX);
+ END("9223372036854775807");
+
+ BEGIN();
+ VAL_UINT64(UINT64_MAX);
+ END("18446744073709551615");
+}
+
+static void
+test_write_array(void)
+{
+ struct spdk_json_write_ctx *w;
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_ARRAY_END();
+ END("[]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_INT32(0);
+ VAL_ARRAY_END();
+ END("[0]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_INT32(0);
+ VAL_INT32(1);
+ VAL_ARRAY_END();
+ END("[0,1]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_INT32(0);
+ VAL_INT32(1);
+ VAL_INT32(2);
+ VAL_ARRAY_END();
+ END("[0,1,2]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_STRING("a");
+ VAL_ARRAY_END();
+ END("[\"a\"]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_STRING("a");
+ VAL_STRING("b");
+ VAL_ARRAY_END();
+ END("[\"a\",\"b\"]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_STRING("a");
+ VAL_STRING("b");
+ VAL_STRING("c");
+ VAL_ARRAY_END();
+ END("[\"a\",\"b\",\"c\"]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_TRUE();
+ VAL_ARRAY_END();
+ END("[true]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_TRUE();
+ VAL_FALSE();
+ VAL_ARRAY_END();
+ END("[true,false]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_TRUE();
+ VAL_FALSE();
+ VAL_TRUE();
+ VAL_ARRAY_END();
+ END("[true,false,true]");
+}
+
+static void
+test_write_object(void)
+{
+ struct spdk_json_write_ctx *w;
+
+ BEGIN();
+ VAL_OBJECT_BEGIN();
+ VAL_OBJECT_END();
+ END("{}");
+
+ BEGIN();
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("a");
+ VAL_INT32(0);
+ VAL_OBJECT_END();
+ END("{\"a\":0}");
+
+ BEGIN();
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("a");
+ VAL_INT32(0);
+ VAL_NAME("b");
+ VAL_INT32(1);
+ VAL_OBJECT_END();
+ END("{\"a\":0,\"b\":1}");
+
+ BEGIN();
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("a");
+ VAL_INT32(0);
+ VAL_NAME("b");
+ VAL_INT32(1);
+ VAL_NAME("c");
+ VAL_INT32(2);
+ VAL_OBJECT_END();
+ END("{\"a\":0,\"b\":1,\"c\":2}");
+}
+
+static void
+test_write_nesting(void)
+{
+ struct spdk_json_write_ctx *w;
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_ARRAY_END();
+ VAL_ARRAY_END();
+ END("[[]]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_ARRAY_END();
+ VAL_ARRAY_END();
+ VAL_ARRAY_END();
+ END("[[[]]]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_INT32(0);
+ VAL_ARRAY_BEGIN();
+ VAL_ARRAY_END();
+ VAL_ARRAY_END();
+ END("[0,[]]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_ARRAY_END();
+ VAL_INT32(0);
+ VAL_ARRAY_END();
+ END("[[],0]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_INT32(0);
+ VAL_ARRAY_BEGIN();
+ VAL_INT32(1);
+ VAL_ARRAY_END();
+ VAL_INT32(2);
+ VAL_ARRAY_END();
+ END("[0,[1],2]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_INT32(0);
+ VAL_INT32(1);
+ VAL_ARRAY_BEGIN();
+ VAL_INT32(2);
+ VAL_INT32(3);
+ VAL_ARRAY_END();
+ VAL_INT32(4);
+ VAL_INT32(5);
+ VAL_ARRAY_END();
+ END("[0,1,[2,3],4,5]");
+
+ BEGIN();
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("a");
+ VAL_OBJECT_BEGIN();
+ VAL_OBJECT_END();
+ VAL_OBJECT_END();
+ END("{\"a\":{}}");
+
+ BEGIN();
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("a");
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("b");
+ VAL_INT32(0);
+ VAL_OBJECT_END();
+ VAL_OBJECT_END();
+ END("{\"a\":{\"b\":0}}");
+
+ BEGIN();
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("a");
+ VAL_ARRAY_BEGIN();
+ VAL_INT32(0);
+ VAL_ARRAY_END();
+ VAL_OBJECT_END();
+ END("{\"a\":[0]}");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("a");
+ VAL_INT32(0);
+ VAL_OBJECT_END();
+ VAL_ARRAY_END();
+ END("[{\"a\":0}]");
+
+ BEGIN();
+ VAL_ARRAY_BEGIN();
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("a");
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("b");
+ VAL_ARRAY_BEGIN();
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("c");
+ VAL_INT32(1);
+ VAL_OBJECT_END();
+ VAL_INT32(2);
+ VAL_ARRAY_END();
+ VAL_NAME("d");
+ VAL_INT32(3);
+ VAL_OBJECT_END();
+ VAL_NAME("e");
+ VAL_INT32(4);
+ VAL_OBJECT_END();
+ VAL_INT32(5);
+ VAL_ARRAY_END();
+ END("[{\"a\":{\"b\":[{\"c\":1},2],\"d\":3},\"e\":4},5]");
+
+ /* Examples from RFC 7159 */
+ BEGIN();
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("Image");
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("Width");
+ VAL_INT32(800);
+ VAL_NAME("Height");
+ VAL_INT32(600);
+ VAL_NAME("Title");
+ VAL_STRING("View from 15th Floor");
+ VAL_NAME("Thumbnail");
+ VAL_OBJECT_BEGIN();
+ VAL_NAME("Url");
+ VAL_STRING("http://www.example.com/image/481989943");
+ VAL_NAME("Height");
+ VAL_INT32(125);
+ VAL_NAME("Width");
+ VAL_INT32(100);
+ VAL_OBJECT_END();
+ VAL_NAME("Animated");
+ VAL_FALSE();
+ VAL_NAME("IDs");
+ VAL_ARRAY_BEGIN();
+ VAL_INT32(116);
+ VAL_INT32(943);
+ VAL_INT32(234);
+ VAL_INT32(38793);
+ VAL_ARRAY_END();
+ VAL_OBJECT_END();
+ VAL_OBJECT_END();
+ END(
+ "{\"Image\":"
+ "{"
+ "\"Width\":800,"
+ "\"Height\":600,"
+ "\"Title\":\"View from 15th Floor\","
+ "\"Thumbnail\":{"
+ "\"Url\":\"http://www.example.com/image/481989943\","
+ "\"Height\":125,"
+ "\"Width\":100"
+ "},"
+ "\"Animated\":false,"
+ "\"IDs\":[116,943,234,38793]"
+ "}"
+ "}");
+}
+
+/* Round-trip parse and write test */
+static void
+test_write_val(void)
+{
+ struct spdk_json_write_ctx *w;
+ struct spdk_json_val values[100];
+ char src[] = "{\"a\":[1,2,3],\"b\":{\"c\":\"d\"},\"e\":true,\"f\":false,\"g\":null}";
+
+ CU_ASSERT(spdk_json_parse(src, strlen(src), values, SPDK_COUNTOF(values), NULL,
+ SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE) == 19);
+
+ BEGIN();
+ VAL(values);
+ END("{\"a\":[1,2,3],\"b\":{\"c\":\"d\"},\"e\":true,\"f\":false,\"g\":null}");
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("json", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "write_literal", test_write_literal) == NULL ||
+ CU_add_test(suite, "write_string_simple", test_write_string_simple) == NULL ||
+ CU_add_test(suite, "write_string_escapes", test_write_string_escapes) == NULL ||
+ CU_add_test(suite, "write_string_utf16le", test_write_string_utf16le) == NULL ||
+ CU_add_test(suite, "write_number_int32", test_write_number_int32) == NULL ||
+ CU_add_test(suite, "write_number_uint32", test_write_number_uint32) == NULL ||
+ CU_add_test(suite, "write_number_int64", test_write_number_int64) == NULL ||
+ CU_add_test(suite, "write_number_uint64", test_write_number_uint64) == NULL ||
+ CU_add_test(suite, "write_array", test_write_array) == NULL ||
+ CU_add_test(suite, "write_object", test_write_object) == NULL ||
+ CU_add_test(suite, "write_nesting", test_write_nesting) == NULL ||
+ CU_add_test(suite, "write_val", test_write_val) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/json_mock.c b/src/spdk/test/unit/lib/json_mock.c
new file mode 100644
index 00000000..b9cee171
--- /dev/null
+++ b/src/spdk/test/unit/lib/json_mock.c
@@ -0,0 +1,81 @@
+/*-
+ * 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/json.h"
+#include "spdk_internal/mock.h"
+
+DEFINE_STUB(spdk_json_write_begin, struct spdk_json_write_ctx *, (spdk_json_write_cb write_cb,
+ void *cb_ctx, uint32_t flags), NULL);
+
+DEFINE_STUB(spdk_json_write_end, int, (struct spdk_json_write_ctx *w), 0);
+DEFINE_STUB(spdk_json_write_null, int, (struct spdk_json_write_ctx *w), 0);
+DEFINE_STUB(spdk_json_write_bool, int, (struct spdk_json_write_ctx *w, bool val), 0);
+DEFINE_STUB(spdk_json_write_int32, int, (struct spdk_json_write_ctx *w, int32_t val), 0);
+DEFINE_STUB(spdk_json_write_uint32, int, (struct spdk_json_write_ctx *w, uint32_t val), 0);
+DEFINE_STUB(spdk_json_write_int64, int, (struct spdk_json_write_ctx *w, int64_t val), 0);
+DEFINE_STUB(spdk_json_write_uint64, int, (struct spdk_json_write_ctx *w, uint64_t val), 0);
+DEFINE_STUB(spdk_json_write_string, int, (struct spdk_json_write_ctx *w, const char *val), 0);
+DEFINE_STUB(spdk_json_write_string_raw, int, (struct spdk_json_write_ctx *w, const char *val,
+ size_t len), 0);
+
+DEFINE_STUB(spdk_json_write_array_begin, int, (struct spdk_json_write_ctx *w), 0);
+DEFINE_STUB(spdk_json_write_array_end, int, (struct spdk_json_write_ctx *w), 0);
+DEFINE_STUB(spdk_json_write_object_begin, int, (struct spdk_json_write_ctx *w), 0);
+DEFINE_STUB(spdk_json_write_object_end, int, (struct spdk_json_write_ctx *w), 0);
+DEFINE_STUB(spdk_json_write_name, int, (struct spdk_json_write_ctx *w, const char *name), 0);
+DEFINE_STUB(spdk_json_write_name_raw, int, (struct spdk_json_write_ctx *w, const char *name,
+ size_t len), 0);
+
+/* Utility functions */
+DEFINE_STUB(spdk_json_write_named_null, int, (struct spdk_json_write_ctx *w, const char *name), 0);
+DEFINE_STUB(spdk_json_write_named_bool, int, (struct spdk_json_write_ctx *w, const char *name,
+ bool val), 0);
+DEFINE_STUB(spdk_json_write_named_int32, int, (struct spdk_json_write_ctx *w, const char *name,
+ int32_t val), 0);
+DEFINE_STUB(spdk_json_write_named_uint32, int, (struct spdk_json_write_ctx *w, const char *name,
+ uint32_t val), 0);
+DEFINE_STUB(spdk_json_write_named_uint64, int, (struct spdk_json_write_ctx *w, const char *name,
+ uint64_t val), 0);
+DEFINE_STUB(spdk_json_write_named_int64, int, (struct spdk_json_write_ctx *w, const char *name,
+ int64_t val), 0);
+DEFINE_STUB(spdk_json_write_named_string, int, (struct spdk_json_write_ctx *w, const char *name,
+ const char *val), 0);
+DEFINE_STUB(spdk_json_write_named_string_fmt, int, (struct spdk_json_write_ctx *w, const char *name,
+ const char *fmt, ...), 0);
+DEFINE_STUB(spdk_json_write_named_string_fmt_v, int, (struct spdk_json_write_ctx *w,
+ const char *name, const char *fmt, va_list args), 0);
+
+DEFINE_STUB(spdk_json_write_named_array_begin, int, (struct spdk_json_write_ctx *w,
+ const char *name), 0);
+DEFINE_STUB(spdk_json_write_named_object_begin, int, (struct spdk_json_write_ctx *w,
+ const char *name), 0);
diff --git a/src/spdk/test/unit/lib/jsonrpc/Makefile b/src/spdk/test/unit/lib/jsonrpc/Makefile
new file mode 100644
index 00000000..0fc0a2e9
--- /dev/null
+++ b/src/spdk/test/unit/lib/jsonrpc/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 = jsonrpc_server.c
+
+.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/unit/lib/jsonrpc/jsonrpc_server.c/.gitignore b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/.gitignore
new file mode 100644
index 00000000..8852a96d
--- /dev/null
+++ b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/.gitignore
@@ -0,0 +1 @@
+jsonrpc_server_ut
diff --git a/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/Makefile b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/Makefile
new file mode 100644
index 00000000..6c02115f
--- /dev/null
+++ b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/Makefile
@@ -0,0 +1,39 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = jsonrpc_server_ut.c
+SPDK_LIB_LIST = json
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/jsonrpc_server_ut.c b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/jsonrpc_server_ut.c
new file mode 100644
index 00000000..3c62e41f
--- /dev/null
+++ b/src/spdk/test/unit/lib/jsonrpc/jsonrpc_server.c/jsonrpc_server_ut.c
@@ -0,0 +1,423 @@
+/*-
+ * 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_cunit.h"
+
+#include "jsonrpc/jsonrpc_server.c"
+
+#define MAX_PARAMS 100
+#define MAX_REQS 100
+
+struct req {
+ int error;
+ bool got_method;
+ bool got_id;
+ bool got_params;
+ struct spdk_jsonrpc_request *request;
+ struct spdk_json_val method;
+ struct spdk_json_val id;
+ struct spdk_json_val params[MAX_PARAMS];
+};
+
+static uint8_t g_buf[1000];
+static struct req g_reqs[MAX_REQS];
+static struct req *g_cur_req;
+static struct spdk_json_val *g_params;
+static size_t g_num_reqs;
+
+#define PARSE_PASS(in, trailing) \
+ memcpy(g_buf, in, sizeof(in) - 1); \
+ g_num_reqs = 0; \
+ g_cur_req = NULL; \
+ CU_ASSERT(spdk_jsonrpc_parse_request(conn, g_buf, sizeof(in) - 1) == sizeof(in) - sizeof(trailing)); \
+ if (g_cur_req && g_cur_req->request) { \
+ free(g_cur_req->request->send_buf); \
+ g_cur_req->request->send_buf = NULL; \
+ }
+
+#define PARSE_FAIL(in) \
+ memcpy(g_buf, in, sizeof(in) - 1); \
+ g_num_reqs = 0; \
+ g_cur_req = 0; \
+ CU_ASSERT(spdk_jsonrpc_parse_request(conn, g_buf, sizeof(in) - 1) < 0); \
+ if (g_cur_req && g_cur_req->request) { \
+ free(g_cur_req->request->send_buf); \
+ g_cur_req->request->send_buf = NULL; \
+ }
+
+#define REQ_BEGIN(expected_error) \
+ if (g_cur_req == NULL) { \
+ g_cur_req = g_reqs; \
+ } else { \
+ g_cur_req++; \
+ } \
+ CU_ASSERT(g_cur_req - g_reqs <= (ptrdiff_t)g_num_reqs); \
+ CU_ASSERT(g_cur_req->error == expected_error)
+
+#define REQ_BEGIN_VALID() REQ_BEGIN(0)
+#define REQ_BEGIN_INVALID(expected_error) REQ_BEGIN(expected_error)
+
+#define REQ_METHOD(name) \
+ CU_ASSERT(g_cur_req->got_method); \
+ CU_ASSERT(spdk_json_strequal(&g_cur_req->method, name) == true)
+
+#define REQ_METHOD_MISSING() \
+ CU_ASSERT(g_cur_req->got_method == false)
+
+#define REQ_ID_NUM(num) \
+ CU_ASSERT(g_cur_req->got_id); \
+ CU_ASSERT(g_cur_req->id.type == SPDK_JSON_VAL_NUMBER); \
+ CU_ASSERT(memcmp(g_cur_req->id.start, num, sizeof(num) - 1) == 0)
+
+#define REQ_ID_STRING(str) \
+ CU_ASSERT(g_cur_req->got_id); \
+ CU_ASSERT(g_cur_req->id.type == SPDK_JSON_VAL_STRING); \
+ CU_ASSERT(memcmp(g_cur_req->id.start, str, sizeof(str) - 1) == 0)
+
+#define REQ_ID_NULL() \
+ CU_ASSERT(g_cur_req->got_id); \
+ CU_ASSERT(g_cur_req->id.type == SPDK_JSON_VAL_NULL)
+
+#define REQ_ID_MISSING() \
+ CU_ASSERT(g_cur_req->got_id == false)
+
+#define REQ_PARAMS_MISSING() \
+ CU_ASSERT(g_cur_req->got_params == false)
+
+#define REQ_PARAMS_BEGIN() \
+ CU_ASSERT(g_cur_req->got_params); \
+ g_params = g_cur_req->params
+
+#define PARAM_ARRAY_BEGIN() \
+ CU_ASSERT(g_params->type == SPDK_JSON_VAL_ARRAY_BEGIN); \
+ g_params++
+
+#define PARAM_ARRAY_END() \
+ CU_ASSERT(g_params->type == SPDK_JSON_VAL_ARRAY_END); \
+ g_params++
+
+#define PARAM_OBJECT_BEGIN() \
+ CU_ASSERT(g_params->type == SPDK_JSON_VAL_OBJECT_BEGIN); \
+ g_params++
+
+#define PARAM_OBJECT_END() \
+ CU_ASSERT(g_params->type == SPDK_JSON_VAL_OBJECT_END); \
+ g_params++
+
+#define PARAM_NUM(num) \
+ CU_ASSERT(g_params->type == SPDK_JSON_VAL_NUMBER); \
+ CU_ASSERT(g_params->len == sizeof(num) - 1); \
+ CU_ASSERT(memcmp(g_params->start, num, g_params->len) == 0); \
+ g_params++
+
+#define PARAM_NAME(str) \
+ CU_ASSERT(g_params->type == SPDK_JSON_VAL_NAME); \
+ CU_ASSERT(g_params->len == sizeof(str) - 1); \
+ CU_ASSERT(memcmp(g_params->start, str, g_params->len) == 0); \
+ g_params++
+
+#define PARAM_STRING(str) \
+ CU_ASSERT(g_params->type == SPDK_JSON_VAL_STRING); \
+ CU_ASSERT(g_params->len == sizeof(str) - 1); \
+ CU_ASSERT(memcmp(g_params->start, str, g_params->len) == 0); \
+ g_params++
+
+#define FREE_REQUEST() \
+ if (g_reqs->request) { \
+ free(g_reqs->request->send_buf); \
+ } \
+ free(g_reqs->request); \
+ g_reqs->request = NULL
+
+static void
+ut_handle(struct spdk_jsonrpc_request *request, int error, const struct spdk_json_val *method,
+ const struct spdk_json_val *params)
+{
+ const struct spdk_json_val *id = &request->id;
+ struct req *r;
+
+ SPDK_CU_ASSERT_FATAL(g_num_reqs != MAX_REQS);
+ r = &g_reqs[g_num_reqs++];
+
+ r->request = request;
+ r->error = error;
+
+ if (method) {
+ r->got_method = true;
+ r->method = *method;
+ } else {
+ r->got_method = false;
+ }
+
+ if (params) {
+ r->got_params = true;
+ SPDK_CU_ASSERT_FATAL(spdk_json_val_len(params) < MAX_PARAMS);
+ memcpy(r->params, params, spdk_json_val_len(params) * sizeof(struct spdk_json_val));
+ } else {
+ r->got_params = false;
+ }
+
+ if (id && id->type != SPDK_JSON_VAL_INVALID) {
+ r->got_id = true;
+ r->id = *id;
+ } else {
+ r->got_id = false;
+ }
+}
+
+void
+spdk_jsonrpc_server_handle_error(struct spdk_jsonrpc_request *request, int error)
+{
+ /*
+ * Map missing id to Null - this mirrors the behavior in the real
+ * spdk_jsonrpc_server_handle_error() function.
+ */
+ if (request->id.type == SPDK_JSON_VAL_INVALID) {
+ request->id.type = SPDK_JSON_VAL_NULL;
+ }
+
+ ut_handle(request, error, NULL, NULL);
+}
+
+void
+spdk_jsonrpc_server_handle_request(struct spdk_jsonrpc_request *request,
+ const struct spdk_json_val *method, const struct spdk_json_val *params)
+{
+ ut_handle(request, 0, method, params);
+}
+
+void
+spdk_jsonrpc_server_send_response(struct spdk_jsonrpc_request *request)
+{
+ /* TODO */
+}
+
+static void
+test_parse_request(void)
+{
+ struct spdk_jsonrpc_server *server;
+ struct spdk_jsonrpc_server_conn *conn;
+
+ server = calloc(1, sizeof(*server));
+ SPDK_CU_ASSERT_FATAL(server != NULL);
+
+ conn = calloc(1, sizeof(*conn));
+ SPDK_CU_ASSERT_FATAL(conn != NULL);
+
+ conn->server = server;
+
+ /* rpc call with positional parameters */
+ PARSE_PASS("{\"jsonrpc\":\"2.0\",\"method\":\"subtract\",\"params\":[42,23],\"id\":1}", "");
+ REQ_BEGIN_VALID();
+ REQ_METHOD("subtract");
+ REQ_ID_NUM("1");
+ REQ_PARAMS_BEGIN();
+ PARAM_ARRAY_BEGIN();
+ PARAM_NUM("42");
+ PARAM_NUM("23");
+ PARAM_ARRAY_END();
+ FREE_REQUEST();
+
+ /* rpc call with named parameters */
+ PARSE_PASS("{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": {\"subtrahend\": 23, \"minuend\": 42}, \"id\": 3}",
+ "");
+ REQ_BEGIN_VALID();
+ REQ_METHOD("subtract");
+ REQ_ID_NUM("3");
+ REQ_PARAMS_BEGIN();
+ PARAM_OBJECT_BEGIN();
+ PARAM_NAME("subtrahend");
+ PARAM_NUM("23");
+ PARAM_NAME("minuend");
+ PARAM_NUM("42");
+ PARAM_OBJECT_END();
+ FREE_REQUEST();
+
+ /* notification */
+ PARSE_PASS("{\"jsonrpc\": \"2.0\", \"method\": \"update\", \"params\": [1,2,3,4,5]}", "");
+ REQ_BEGIN_VALID();
+ REQ_METHOD("update");
+ REQ_ID_MISSING();
+ REQ_PARAMS_BEGIN();
+ PARAM_ARRAY_BEGIN();
+ PARAM_NUM("1");
+ PARAM_NUM("2");
+ PARAM_NUM("3");
+ PARAM_NUM("4");
+ PARAM_NUM("5");
+ PARAM_ARRAY_END();
+ FREE_REQUEST();
+
+ /* invalid JSON */
+ PARSE_FAIL("{\"jsonrpc\": \"2.0\", \"method\": \"foobar, \"params\": \"bar\", \"baz]");
+ REQ_BEGIN_INVALID(SPDK_JSONRPC_ERROR_PARSE_ERROR);
+ REQ_METHOD_MISSING();
+ REQ_ID_NULL();
+ REQ_PARAMS_MISSING();
+ FREE_REQUEST();
+
+ /* invalid request (method must be a string; params must be array or object) */
+ PARSE_PASS("{\"jsonrpc\": \"2.0\", \"method\": 1, \"params\": \"bar\"}", "");
+ REQ_BEGIN_INVALID(SPDK_JSONRPC_ERROR_INVALID_REQUEST);
+ REQ_METHOD_MISSING();
+ REQ_ID_NULL();
+ REQ_PARAMS_MISSING();
+ FREE_REQUEST();
+
+ /* batch, invalid JSON */
+ PARSE_FAIL(
+ "["
+ "{\"jsonrpc\": \"2.0\", \"method\": \"sum\", \"params\": [1,2,4], \"id\": \"1\"},"
+ "{\"jsonrpc\": \"2.0\", \"method\""
+ "]");
+ REQ_BEGIN_INVALID(SPDK_JSONRPC_ERROR_PARSE_ERROR);
+ REQ_METHOD_MISSING();
+ REQ_ID_NULL();
+ REQ_PARAMS_MISSING();
+ FREE_REQUEST();
+
+ /* empty array */
+ PARSE_PASS("[]", "");
+ REQ_BEGIN_INVALID(SPDK_JSONRPC_ERROR_INVALID_REQUEST);
+ REQ_METHOD_MISSING();
+ REQ_ID_NULL();
+ REQ_PARAMS_MISSING();
+ FREE_REQUEST();
+
+ /* batch - not supported */
+ PARSE_PASS(
+ "["
+ "{\"jsonrpc\": \"2.0\", \"method\": \"sum\", \"params\": [1,2,4], \"id\": \"1\"},"
+ "{\"jsonrpc\": \"2.0\", \"method\": \"notify_hello\", \"params\": [7]},"
+ "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42,23], \"id\": \"2\"},"
+ "{\"foo\": \"boo\"},"
+ "{\"jsonrpc\": \"2.0\", \"method\": \"foo.get\", \"params\": {\"name\": \"myself\"}, \"id\": \"5\"},"
+ "{\"jsonrpc\": \"2.0\", \"method\": \"get_data\", \"id\": \"9\"}"
+ "]", "");
+
+ REQ_BEGIN_INVALID(SPDK_JSONRPC_ERROR_INVALID_REQUEST);
+ REQ_METHOD_MISSING();
+ REQ_ID_NULL();
+ REQ_PARAMS_MISSING();
+ FREE_REQUEST();
+
+ free(conn);
+ free(server);
+}
+
+static void
+test_parse_request_streaming(void)
+{
+ struct spdk_jsonrpc_server *server;
+ struct spdk_jsonrpc_server_conn *conn;
+ size_t len, i;
+
+ server = calloc(1, sizeof(*server));
+ SPDK_CU_ASSERT_FATAL(server != NULL);
+
+ conn = calloc(1, sizeof(*conn));
+ SPDK_CU_ASSERT_FATAL(conn != NULL);
+
+ conn->server = server;
+
+ /*
+ * Two valid requests end to end in the same buffer.
+ * Parse should return the first one and point to the beginning of the second one.
+ */
+ PARSE_PASS(
+ "{\"jsonrpc\":\"2.0\",\"method\":\"a\",\"params\":[1],\"id\":1}"
+ "{\"jsonrpc\":\"2.0\",\"method\":\"b\",\"params\":[2],\"id\":2}",
+ "{\"jsonrpc\":\"2.0\",\"method\":\"b\",\"params\":[2],\"id\":2}");
+ REQ_BEGIN_VALID();
+ REQ_METHOD("a");
+ REQ_ID_NUM("1");
+ REQ_PARAMS_BEGIN();
+ PARAM_ARRAY_BEGIN();
+ PARAM_NUM("1");
+ PARAM_ARRAY_END();
+ FREE_REQUEST();
+
+ /* Partial (but not invalid) requests - parse should not consume anything. */
+ snprintf(g_buf, sizeof(g_buf), "%s",
+ "{\"jsonrpc\":\"2.0\",\"method\":\"b\",\"params\":[2],\"id\":2}");
+ len = strlen(g_buf);
+
+ /* Try every partial length up to the full request length */
+ for (i = 0; i < len; i++) {
+ int rc = spdk_jsonrpc_parse_request(conn, g_buf, i);
+ /* Partial request - no data consumed */
+ CU_ASSERT(rc == 0);
+ FREE_REQUEST();
+ }
+
+ /* Verify that full request can be parsed successfully */
+ CU_ASSERT(spdk_jsonrpc_parse_request(conn, g_buf, len) == (ssize_t)len);
+ FREE_REQUEST();
+
+ free(conn);
+ free(server);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("jsonrpc", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "parse_request", test_parse_request) == NULL ||
+ CU_add_test(suite, "parse_request_streaming", test_parse_request_streaming) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/log/Makefile b/src/spdk/test/unit/lib/log/Makefile
new file mode 100644
index 00000000..79411a45
--- /dev/null
+++ b/src/spdk/test/unit/lib/log/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 = log.c
+
+.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/unit/lib/log/log.c/.gitignore b/src/spdk/test/unit/lib/log/log.c/.gitignore
new file mode 100644
index 00000000..60261c07
--- /dev/null
+++ b/src/spdk/test/unit/lib/log/log.c/.gitignore
@@ -0,0 +1 @@
+log_ut
diff --git a/src/spdk/test/unit/lib/log/log.c/Makefile b/src/spdk/test/unit/lib/log/log.c/Makefile
new file mode 100644
index 00000000..deedd9fb
--- /dev/null
+++ b/src/spdk/test/unit/lib/log/log.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = log_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/log/log.c/log_ut.c b/src/spdk/test/unit/lib/log/log.c/log_ut.c
new file mode 100644
index 00000000..17650f71
--- /dev/null
+++ b/src/spdk/test/unit/lib/log/log.c/log_ut.c
@@ -0,0 +1,113 @@
+/*-
+ * 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_cunit.h"
+#include "spdk/log.h"
+
+#include "log/log.c"
+#include "log/log_flags.c"
+
+static void
+log_test(void)
+{
+ spdk_log_set_level(SPDK_LOG_ERROR);
+ CU_ASSERT_EQUAL(spdk_log_get_level(), SPDK_LOG_ERROR);
+ spdk_log_set_level(SPDK_LOG_WARN);
+ CU_ASSERT_EQUAL(spdk_log_get_level(), SPDK_LOG_WARN);
+ spdk_log_set_level(SPDK_LOG_NOTICE);
+ CU_ASSERT_EQUAL(spdk_log_get_level(), SPDK_LOG_NOTICE);
+ spdk_log_set_level(SPDK_LOG_INFO);
+ CU_ASSERT_EQUAL(spdk_log_get_level(), SPDK_LOG_INFO);
+ spdk_log_set_level(SPDK_LOG_DEBUG);
+ CU_ASSERT_EQUAL(spdk_log_get_level(), SPDK_LOG_DEBUG);
+
+ spdk_log_set_print_level(SPDK_LOG_ERROR);
+ CU_ASSERT_EQUAL(spdk_log_get_print_level(), SPDK_LOG_ERROR);
+ spdk_log_set_print_level(SPDK_LOG_WARN);
+ CU_ASSERT_EQUAL(spdk_log_get_print_level(), SPDK_LOG_WARN);
+ spdk_log_set_print_level(SPDK_LOG_NOTICE);
+ CU_ASSERT_EQUAL(spdk_log_get_print_level(), SPDK_LOG_NOTICE);
+ spdk_log_set_print_level(SPDK_LOG_INFO);
+ CU_ASSERT_EQUAL(spdk_log_get_print_level(), SPDK_LOG_INFO);
+ spdk_log_set_print_level(SPDK_LOG_DEBUG);
+ CU_ASSERT_EQUAL(spdk_log_get_print_level(), SPDK_LOG_DEBUG);
+
+#ifdef DEBUG
+ CU_ASSERT(spdk_log_get_trace_flag("log") == false);
+
+ spdk_log_set_trace_flag("log");
+ CU_ASSERT(spdk_log_get_trace_flag("log") == true);
+
+ spdk_log_clear_trace_flag("log");
+ CU_ASSERT(spdk_log_get_trace_flag("log") == false);
+#endif
+
+ spdk_log_open();
+ spdk_log_set_trace_flag("log");
+ SPDK_WARNLOG("log warning unit test\n");
+ SPDK_DEBUGLOG(SPDK_LOG_LOG, "log trace test\n");
+ SPDK_TRACEDUMP(SPDK_LOG_LOG, "log trace dump test:", "trace dump", 10);
+ spdk_trace_dump(stderr, "spdk dump test:", "spdk dump", 9);
+
+ spdk_log_close();
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("log", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "log_ut", log_test) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/lvol/Makefile b/src/spdk/test/unit/lib/lvol/Makefile
new file mode 100644
index 00000000..c9276de4
--- /dev/null
+++ b/src/spdk/test/unit/lib/lvol/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 = lvol.c
+
+.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/unit/lib/lvol/lvol.c/.gitignore b/src/spdk/test/unit/lib/lvol/lvol.c/.gitignore
new file mode 100644
index 00000000..57e92bfe
--- /dev/null
+++ b/src/spdk/test/unit/lib/lvol/lvol.c/.gitignore
@@ -0,0 +1 @@
+lvol_ut
diff --git a/src/spdk/test/unit/lib/lvol/lvol.c/Makefile b/src/spdk/test/unit/lib/lvol/lvol.c/Makefile
new file mode 100644
index 00000000..917f4ef6
--- /dev/null
+++ b/src/spdk/test/unit/lib/lvol/lvol.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = lvol_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/lvol/lvol.c/lvol_ut.c b/src/spdk/test/unit/lib/lvol/lvol.c/lvol_ut.c
new file mode 100644
index 00000000..0aebbe1a
--- /dev/null
+++ b/src/spdk/test/unit/lib/lvol/lvol.c/lvol_ut.c
@@ -0,0 +1,2127 @@
+/*-
+ * 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_cunit.h"
+#include "spdk/blob.h"
+#include "spdk/thread.h"
+#include "spdk/util.h"
+
+#include "common/lib/test_env.c"
+
+#include "lvol/lvol.c"
+
+#define DEV_BUFFER_SIZE (64 * 1024 * 1024)
+#define DEV_BUFFER_BLOCKLEN (4096)
+#define DEV_BUFFER_BLOCKCNT (DEV_BUFFER_SIZE / DEV_BUFFER_BLOCKLEN)
+#define BS_CLUSTER_SIZE (1024 * 1024)
+#define BS_FREE_CLUSTERS (DEV_BUFFER_SIZE / BS_CLUSTER_SIZE)
+#define BS_PAGE_SIZE (4096)
+
+#define SPDK_BLOB_OPTS_CLUSTER_SZ (1024 * 1024)
+#define SPDK_BLOB_OPTS_NUM_MD_PAGES UINT32_MAX
+#define SPDK_BLOB_OPTS_MAX_MD_OPS 32
+#define SPDK_BLOB_OPTS_MAX_CHANNEL_OPS 512
+
+#define SPDK_BLOB_THIN_PROV (1ULL << 0)
+
+const char *uuid = "828d9766-ae50-11e7-bd8d-001e67edf350";
+
+struct spdk_blob {
+ spdk_blob_id id;
+ uint32_t ref;
+ struct spdk_blob_store *bs;
+ int close_status;
+ int open_status;
+ int load_status;
+ TAILQ_ENTRY(spdk_blob) link;
+ char uuid[SPDK_UUID_STRING_LEN];
+ char name[SPDK_LVS_NAME_MAX];
+ bool thin_provisioned;
+};
+
+int g_lvolerrno;
+int g_lvserrno;
+int g_close_super_status;
+int g_resize_rc;
+int g_inflate_rc;
+bool g_lvs_rename_blob_open_error = false;
+struct spdk_lvol_store *g_lvol_store;
+struct spdk_lvol *g_lvol;
+spdk_blob_id g_blobid = 1;
+struct spdk_io_channel *g_io_channel;
+
+struct spdk_blob_store {
+ struct spdk_bs_opts bs_opts;
+ spdk_blob_id super_blobid;
+ TAILQ_HEAD(, spdk_blob) blobs;
+ int get_super_status;
+};
+
+struct lvol_ut_bs_dev {
+ struct spdk_bs_dev bs_dev;
+ int init_status;
+ int load_status;
+ struct spdk_blob_store *bs;
+};
+
+void spdk_bs_inflate_blob(struct spdk_blob_store *bs, struct spdk_io_channel *channel,
+ spdk_blob_id blobid, spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ cb_fn(cb_arg, g_inflate_rc);
+}
+
+void spdk_bs_blob_decouple_parent(struct spdk_blob_store *bs, struct spdk_io_channel *channel,
+ spdk_blob_id blobid, spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ cb_fn(cb_arg, g_inflate_rc);
+}
+
+void
+spdk_bs_iter_next(struct spdk_blob_store *bs, struct spdk_blob *b,
+ spdk_blob_op_with_handle_complete cb_fn, void *cb_arg)
+{
+ struct spdk_blob *next;
+ int _errno = 0;
+
+ next = TAILQ_NEXT(b, link);
+ if (next == NULL) {
+ _errno = -ENOENT;
+ } else if (next->load_status != 0) {
+ _errno = next->load_status;
+ }
+
+ cb_fn(cb_arg, next, _errno);
+}
+
+void
+spdk_bs_iter_first(struct spdk_blob_store *bs,
+ spdk_blob_op_with_handle_complete cb_fn, void *cb_arg)
+{
+ struct spdk_blob *first;
+ int _errno = 0;
+
+ first = TAILQ_FIRST(&bs->blobs);
+ if (first == NULL) {
+ _errno = -ENOENT;
+ } else if (first->load_status != 0) {
+ _errno = first->load_status;
+ }
+
+ cb_fn(cb_arg, first, _errno);
+}
+
+uint64_t spdk_blob_get_num_clusters(struct spdk_blob *blob)
+{
+ return 0;
+}
+
+void
+spdk_bs_get_super(struct spdk_blob_store *bs,
+ spdk_blob_op_with_id_complete cb_fn, void *cb_arg)
+{
+ if (bs->get_super_status != 0) {
+ cb_fn(cb_arg, 0, bs->get_super_status);
+ } else {
+ cb_fn(cb_arg, bs->super_blobid, 0);
+ }
+}
+
+void
+spdk_bs_set_super(struct spdk_blob_store *bs, spdk_blob_id blobid,
+ spdk_bs_op_complete cb_fn, void *cb_arg)
+{
+ bs->super_blobid = blobid;
+ cb_fn(cb_arg, 0);
+}
+
+void
+spdk_bs_load(struct spdk_bs_dev *dev, struct spdk_bs_opts *opts,
+ spdk_bs_op_with_handle_complete cb_fn, void *cb_arg)
+{
+ struct lvol_ut_bs_dev *ut_dev = SPDK_CONTAINEROF(dev, struct lvol_ut_bs_dev, bs_dev);
+ struct spdk_blob_store *bs = NULL;
+
+ if (ut_dev->load_status == 0) {
+ bs = ut_dev->bs;
+ }
+
+ cb_fn(cb_arg, bs, ut_dev->load_status);
+}
+
+struct spdk_io_channel *spdk_bs_alloc_io_channel(struct spdk_blob_store *bs)
+{
+ if (g_io_channel == NULL) {
+ g_io_channel = calloc(1, sizeof(struct spdk_io_channel));
+ SPDK_CU_ASSERT_FATAL(g_io_channel != NULL);
+ }
+ g_io_channel->ref++;
+ return g_io_channel;
+}
+
+void spdk_bs_free_io_channel(struct spdk_io_channel *channel)
+{
+ g_io_channel->ref--;
+ if (g_io_channel->ref == 0) {
+ free(g_io_channel);
+ g_io_channel = NULL;
+ }
+ return;
+}
+
+int
+spdk_blob_set_xattr(struct spdk_blob *blob, const char *name, const void *value,
+ uint16_t value_len)
+{
+ if (!strcmp(name, "uuid")) {
+ CU_ASSERT(value_len == SPDK_UUID_STRING_LEN);
+ memcpy(blob->uuid, value, SPDK_UUID_STRING_LEN);
+ } else if (!strcmp(name, "name")) {
+ CU_ASSERT(value_len <= SPDK_LVS_NAME_MAX);
+ memcpy(blob->name, value, value_len);
+ }
+
+ return 0;
+}
+
+int
+spdk_blob_get_xattr_value(struct spdk_blob *blob, const char *name,
+ const void **value, size_t *value_len)
+{
+ if (!strcmp(name, "uuid") && strnlen(blob->uuid, SPDK_UUID_STRING_LEN) != 0) {
+ CU_ASSERT(strnlen(blob->uuid, SPDK_UUID_STRING_LEN) == (SPDK_UUID_STRING_LEN - 1));
+ *value = blob->uuid;
+ *value_len = SPDK_UUID_STRING_LEN;
+ return 0;
+ } else if (!strcmp(name, "name") && strnlen(blob->name, SPDK_LVS_NAME_MAX) != 0) {
+ *value = blob->name;
+ *value_len = strnlen(blob->name, SPDK_LVS_NAME_MAX) + 1;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+int
+spdk_blob_get_clones(struct spdk_blob_store *bs, spdk_blob_id blobid, spdk_blob_id *ids,
+ size_t *count)
+{
+ return 0;
+}
+
+uint64_t
+spdk_bs_get_page_size(struct spdk_blob_store *bs)
+{
+ return BS_PAGE_SIZE;
+}
+
+int
+spdk_bdev_notify_blockcnt_change(struct spdk_bdev *bdev, uint64_t size)
+{
+ bdev->blockcnt = size;
+ return 0;
+}
+
+static void
+init_dev(struct lvol_ut_bs_dev *dev)
+{
+ memset(dev, 0, sizeof(*dev));
+ dev->bs_dev.blockcnt = DEV_BUFFER_BLOCKCNT;
+ dev->bs_dev.blocklen = DEV_BUFFER_BLOCKLEN;
+}
+
+static void
+free_dev(struct lvol_ut_bs_dev *dev)
+{
+ struct spdk_blob_store *bs = dev->bs;
+ struct spdk_blob *blob, *tmp;
+
+ if (bs == NULL) {
+ return;
+ }
+
+ TAILQ_FOREACH_SAFE(blob, &bs->blobs, link, tmp) {
+ TAILQ_REMOVE(&bs->blobs, blob, link);
+ free(blob);
+ }
+
+ free(bs);
+ dev->bs = NULL;
+}
+
+void
+spdk_bs_init(struct spdk_bs_dev *dev, struct spdk_bs_opts *o,
+ spdk_bs_op_with_handle_complete cb_fn, void *cb_arg)
+{
+ struct lvol_ut_bs_dev *ut_dev = SPDK_CONTAINEROF(dev, struct lvol_ut_bs_dev, bs_dev);
+ struct spdk_blob_store *bs;
+
+ bs = calloc(1, sizeof(*bs));
+ SPDK_CU_ASSERT_FATAL(bs != NULL);
+
+ TAILQ_INIT(&bs->blobs);
+
+ ut_dev->bs = bs;
+
+ memcpy(&bs->bs_opts, o, sizeof(struct spdk_bs_opts));
+
+ cb_fn(cb_arg, bs, 0);
+}
+
+void
+spdk_bs_unload(struct spdk_blob_store *bs, spdk_bs_op_complete cb_fn, void *cb_arg)
+{
+ cb_fn(cb_arg, 0);
+}
+
+void
+spdk_bs_destroy(struct spdk_blob_store *bs, spdk_bs_op_complete cb_fn,
+ void *cb_arg)
+{
+ free(bs);
+
+ cb_fn(cb_arg, 0);
+}
+
+void
+spdk_bs_delete_blob(struct spdk_blob_store *bs, spdk_blob_id blobid,
+ spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ struct spdk_blob *blob;
+
+ TAILQ_FOREACH(blob, &bs->blobs, link) {
+ if (blob->id == blobid) {
+ TAILQ_REMOVE(&bs->blobs, blob, link);
+ free(blob);
+ break;
+ }
+ }
+
+ cb_fn(cb_arg, 0);
+}
+
+spdk_blob_id
+spdk_blob_get_id(struct spdk_blob *blob)
+{
+ return blob->id;
+}
+
+void
+spdk_bs_opts_init(struct spdk_bs_opts *opts)
+{
+ opts->cluster_sz = SPDK_BLOB_OPTS_CLUSTER_SZ;
+ opts->num_md_pages = SPDK_BLOB_OPTS_NUM_MD_PAGES;
+ opts->max_md_ops = SPDK_BLOB_OPTS_MAX_MD_OPS;
+ opts->max_channel_ops = SPDK_BLOB_OPTS_MAX_CHANNEL_OPS;
+ memset(&opts->bstype, 0, sizeof(opts->bstype));
+}
+
+uint64_t
+spdk_bs_get_cluster_size(struct spdk_blob_store *bs)
+{
+ return BS_CLUSTER_SIZE;
+}
+
+void spdk_blob_close(struct spdk_blob *b, spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ b->ref--;
+
+ cb_fn(cb_arg, b->close_status);
+}
+
+void
+spdk_blob_resize(struct spdk_blob *blob, uint64_t sz, spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ if (g_resize_rc != 0) {
+ return cb_fn(cb_arg, g_resize_rc);
+ } else if (sz > DEV_BUFFER_SIZE / BS_CLUSTER_SIZE) {
+ return cb_fn(cb_arg, -ENOMEM);
+ }
+ cb_fn(cb_arg, 0);
+}
+
+void
+spdk_blob_sync_md(struct spdk_blob *blob, spdk_blob_op_complete cb_fn, void *cb_arg)
+{
+ cb_fn(cb_arg, 0);
+}
+
+void
+spdk_bs_open_blob(struct spdk_blob_store *bs, spdk_blob_id blobid,
+ spdk_blob_op_with_handle_complete cb_fn, void *cb_arg)
+{
+ struct spdk_blob *blob;
+
+ if (!g_lvs_rename_blob_open_error) {
+ TAILQ_FOREACH(blob, &bs->blobs, link) {
+ if (blob->id == blobid) {
+ blob->ref++;
+ cb_fn(cb_arg, blob, blob->open_status);
+ return;
+ }
+ }
+ }
+
+ cb_fn(cb_arg, NULL, -ENOENT);
+}
+
+uint64_t
+spdk_bs_free_cluster_count(struct spdk_blob_store *bs)
+{
+ return BS_FREE_CLUSTERS;
+}
+
+void
+spdk_blob_opts_init(struct spdk_blob_opts *opts)
+{
+ opts->num_clusters = 0;
+ opts->thin_provision = false;
+ opts->xattrs.count = 0;
+ opts->xattrs.names = NULL;
+ opts->xattrs.ctx = NULL;
+ opts->xattrs.get_value = NULL;
+}
+
+void
+spdk_bs_create_blob(struct spdk_blob_store *bs,
+ spdk_blob_op_with_id_complete cb_fn, void *cb_arg)
+{
+ spdk_bs_create_blob_ext(bs, NULL, cb_fn, cb_arg);
+}
+
+void
+spdk_bs_create_blob_ext(struct spdk_blob_store *bs, const struct spdk_blob_opts *opts,
+ spdk_blob_op_with_id_complete cb_fn, void *cb_arg)
+{
+ struct spdk_blob *b;
+
+ if (opts && opts->num_clusters > DEV_BUFFER_SIZE / BS_CLUSTER_SIZE) {
+ cb_fn(cb_arg, 0, -1);
+ return;
+ }
+
+ b = calloc(1, sizeof(*b));
+ SPDK_CU_ASSERT_FATAL(b != NULL);
+
+ b->id = g_blobid++;
+ if (opts != NULL && opts->thin_provision) {
+ b->thin_provisioned = true;
+ }
+ b->bs = bs;
+
+ TAILQ_INSERT_TAIL(&bs->blobs, b, link);
+ cb_fn(cb_arg, b->id, 0);
+}
+
+void
+spdk_bs_create_snapshot(struct spdk_blob_store *bs, spdk_blob_id blobid,
+ const struct spdk_blob_xattr_opts *snapshot_xattrs,
+ spdk_blob_op_with_id_complete cb_fn, void *cb_arg)
+{
+ spdk_bs_create_blob_ext(bs, NULL, cb_fn, cb_arg);
+}
+
+void
+spdk_bs_create_clone(struct spdk_blob_store *bs, spdk_blob_id blobid,
+ const struct spdk_blob_xattr_opts *clone_xattrs,
+ spdk_blob_op_with_id_complete cb_fn, void *cb_arg)
+{
+ spdk_bs_create_blob_ext(bs, NULL, cb_fn, cb_arg);
+}
+
+static void
+_lvol_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx)
+{
+ fn(ctx);
+}
+
+static void
+lvol_store_op_with_handle_complete(void *cb_arg, struct spdk_lvol_store *lvol_store, int lvserrno)
+{
+ g_lvol_store = lvol_store;
+ g_lvserrno = lvserrno;
+}
+
+static void
+lvol_op_complete(void *cb_arg, int lvolerrno)
+{
+ g_lvolerrno = lvolerrno;
+}
+
+static void
+lvol_op_with_handle_complete(void *cb_arg, struct spdk_lvol *lvol, int lvserrno)
+{
+ g_lvol = lvol;
+ g_lvserrno = lvserrno;
+}
+
+static void
+lvol_store_op_complete(void *cb_arg, int lvserrno)
+{
+ g_lvserrno = lvserrno;
+}
+
+static void
+close_cb(void *cb_arg, int lvolerrno)
+{
+ g_lvserrno = lvolerrno;
+}
+
+static void
+destroy_cb(void *cb_arg, int lvolerrno)
+{
+ g_lvserrno = lvolerrno;
+}
+
+static void
+lvs_init_unload_success(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(!TAILQ_EMPTY(&g_lvol_stores));
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ /* Lvol store has an open lvol, this unload should fail. */
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == -EBUSY);
+ CU_ASSERT(g_lvserrno == -EBUSY);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(!TAILQ_EMPTY(&g_lvol_stores));
+
+ /* Lvol has to be closed (or destroyed) before unloading lvol store. */
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvs_init_destroy_success(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ /* Lvol store contains one lvol, this destroy should fail. */
+ g_lvserrno = -1;
+ rc = spdk_lvs_destroy(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == -EBUSY);
+ CU_ASSERT(g_lvserrno == -EBUSY);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ spdk_lvol_destroy(g_lvol, destroy_cb, NULL);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_destroy(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ spdk_free_thread();
+}
+
+static void
+lvs_init_opts_success(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ g_lvserrno = -1;
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+ opts.cluster_sz = 8192;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(dev.bs->bs_opts.cluster_sz == opts.cluster_sz);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvs_unload_lvs_is_null_fail(void)
+{
+ int rc = 0;
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(NULL, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == -ENODEV);
+ CU_ASSERT(g_lvserrno == -1);
+
+ spdk_free_thread();
+}
+
+static void
+lvs_names(void)
+{
+ struct lvol_ut_bs_dev dev_x, dev_y, dev_x2;
+ struct spdk_lvs_opts opts_none, opts_x, opts_y, opts_full;
+ struct spdk_lvol_store *lvs_x, *lvs_y, *lvs_x2;
+ int rc = 0;
+
+ init_dev(&dev_x);
+ init_dev(&dev_y);
+ init_dev(&dev_x2);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts_none);
+ spdk_lvs_opts_init(&opts_x);
+ opts_x.name[0] = 'x';
+ spdk_lvs_opts_init(&opts_y);
+ opts_y.name[0] = 'y';
+ spdk_lvs_opts_init(&opts_full);
+ memset(opts_full.name, 'a', sizeof(opts_full.name));
+
+ /* Test that opts with no name fails spdk_lvs_init(). */
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+ rc = spdk_lvs_init(&dev_x.bs_dev, &opts_none, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc != 0);
+ CU_ASSERT(g_lvol_store == NULL);
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+
+ /* Test that opts with no null terminator for name fails spdk_lvs_init(). */
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+ rc = spdk_lvs_init(&dev_x.bs_dev, &opts_full, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc != 0);
+ CU_ASSERT(g_lvol_store == NULL);
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+
+ /* Test that we can create an lvolstore with name 'x'. */
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+ g_lvol_store = NULL;
+ rc = spdk_lvs_init(&dev_x.bs_dev, &opts_x, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(!TAILQ_EMPTY(&g_lvol_stores));
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ lvs_x = g_lvol_store;
+
+ /* Test that we can create an lvolstore with name 'y'. */
+ g_lvol_store = NULL;
+ rc = spdk_lvs_init(&dev_y.bs_dev, &opts_y, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ lvs_y = g_lvol_store;
+
+ /* Test that we cannot create another lvolstore with name 'x'. */
+ rc = spdk_lvs_init(&dev_x2.bs_dev, &opts_x, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == -EEXIST);
+
+ /* Now destroy lvolstore 'x' and then confirm we can create a new lvolstore with name 'x'. */
+ g_lvserrno = -1;
+ rc = spdk_lvs_destroy(lvs_x, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+ rc = spdk_lvs_init(&dev_x.bs_dev, &opts_x, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ lvs_x = g_lvol_store;
+
+ /*
+ * Unload lvolstore 'x'. Then we should be able to create another lvolstore with name 'x'.
+ */
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(lvs_x, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+ rc = spdk_lvs_init(&dev_x2.bs_dev, &opts_x, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ lvs_x2 = g_lvol_store;
+
+ /* Confirm that we cannot load the first lvolstore 'x'. */
+ g_lvserrno = 0;
+ spdk_lvs_load(&dev_x.bs_dev, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno != 0);
+
+ /* Destroy the second lvolstore 'x'. Then we should be able to load the first lvolstore 'x'. */
+ g_lvserrno = -1;
+ rc = spdk_lvs_destroy(lvs_x2, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvserrno = -1;
+ spdk_lvs_load(&dev_x.bs_dev, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ lvs_x = g_lvol_store;
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_destroy(lvs_x, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_destroy(lvs_y, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_create_destroy_success(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ spdk_lvol_destroy(g_lvol, destroy_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_create_fail(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvol_store = NULL;
+ g_lvserrno = 0;
+ rc = spdk_lvs_init(NULL, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc != 0);
+ CU_ASSERT(g_lvol_store == NULL);
+
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ g_lvol = NULL;
+ rc = spdk_lvol_create(NULL, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc != 0);
+ CU_ASSERT(g_lvol == NULL);
+
+ g_lvol = NULL;
+ rc = spdk_lvol_create(g_lvol_store, "lvol", DEV_BUFFER_SIZE + 1, false,
+ lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno != 0);
+ CU_ASSERT(g_lvol == NULL);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_destroy_fail(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ spdk_lvol_destroy(g_lvol, destroy_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_close_fail(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_close_success(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_resize(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_resize_rc = 0;
+ g_lvserrno = -1;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ /* Resize to same size */
+ spdk_lvol_resize(g_lvol, 10, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ /* Resize to smaller size */
+ spdk_lvol_resize(g_lvol, 5, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ /* Resize to bigger size */
+ spdk_lvol_resize(g_lvol, 15, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ /* Resize to size = 0 */
+ spdk_lvol_resize(g_lvol, 0, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ /* Resize to bigger size than available */
+ g_lvserrno = 0;
+ spdk_lvol_resize(g_lvol, 0xFFFFFFFF, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno != 0);
+
+ /* Fail resize */
+ g_resize_rc = -1;
+ g_lvserrno = 0;
+ spdk_lvol_resize(g_lvol, 10, lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno != 0);
+ g_resize_rc = 0;
+
+ g_resize_rc = 0;
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ spdk_lvol_destroy(g_lvol, destroy_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+null_cb(void *ctx, struct spdk_blob_store *bs, int bserrno)
+{
+ SPDK_CU_ASSERT_FATAL(bs != NULL);
+}
+
+static void
+lvs_load(void)
+{
+ int rc = -1;
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_with_handle_req *req;
+ struct spdk_bs_opts bs_opts = {};
+ struct spdk_blob *super_blob;
+
+ req = calloc(1, sizeof(*req));
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+
+ init_dev(&dev);
+ spdk_bs_opts_init(&bs_opts);
+ snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "LVOLSTORE");
+ spdk_bs_init(&dev.bs_dev, &bs_opts, null_cb, NULL);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ /* Fail on bs load */
+ dev.load_status = -1;
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno != 0);
+ CU_ASSERT(g_lvol_store == NULL);
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+
+ /* Fail on getting super blob */
+ dev.load_status = 0;
+ dev.bs->get_super_status = -1;
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno == -ENODEV);
+ CU_ASSERT(g_lvol_store == NULL);
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+
+ /* Fail on opening super blob */
+ g_lvserrno = 0;
+ super_blob = calloc(1, sizeof(*super_blob));
+ super_blob->id = 0x100;
+ super_blob->open_status = -1;
+ TAILQ_INSERT_TAIL(&dev.bs->blobs, super_blob, link);
+ dev.bs->super_blobid = 0x100;
+ dev.bs->get_super_status = 0;
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno == -ENODEV);
+ CU_ASSERT(g_lvol_store == NULL);
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+
+ /* Fail on getting uuid */
+ g_lvserrno = 0;
+ super_blob->open_status = 0;
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno == -EINVAL);
+ CU_ASSERT(g_lvol_store == NULL);
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+
+ /* Fail on getting name */
+ g_lvserrno = 0;
+ spdk_blob_set_xattr(super_blob, "uuid", uuid, SPDK_UUID_STRING_LEN);
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno == -EINVAL);
+ CU_ASSERT(g_lvol_store == NULL);
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+
+ /* Fail on closing super blob */
+ g_lvserrno = 0;
+ spdk_blob_set_xattr(super_blob, "name", "lvs", strnlen("lvs", SPDK_LVS_NAME_MAX) + 1);
+ super_blob->close_status = -1;
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno == -ENODEV);
+ CU_ASSERT(g_lvol_store == NULL);
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+
+ /* Load successfully */
+ g_lvserrno = 0;
+ super_blob->close_status = 0;
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store != NULL);
+ CU_ASSERT(!TAILQ_EMPTY(&g_lvol_stores));
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(TAILQ_EMPTY(&g_lvol_stores));
+
+ free(req);
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvols_load(void)
+{
+ int rc = -1;
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_with_handle_req *req;
+ struct spdk_bs_opts bs_opts;
+ struct spdk_blob *super_blob, *blob1, *blob2, *blob3;
+
+ req = calloc(1, sizeof(*req));
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+
+ init_dev(&dev);
+ spdk_bs_opts_init(&bs_opts);
+ snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "LVOLSTORE");
+ spdk_bs_init(&dev.bs_dev, &bs_opts, null_cb, NULL);
+ super_blob = calloc(1, sizeof(*super_blob));
+ SPDK_CU_ASSERT_FATAL(super_blob != NULL);
+ super_blob->id = 0x100;
+ spdk_blob_set_xattr(super_blob, "uuid", uuid, SPDK_UUID_STRING_LEN);
+ spdk_blob_set_xattr(super_blob, "name", "lvs", strnlen("lvs", SPDK_LVS_NAME_MAX) + 1);
+ TAILQ_INSERT_TAIL(&dev.bs->blobs, super_blob, link);
+ dev.bs->super_blobid = 0x100;
+
+ /*
+ * Create 3 blobs, write different char values to the last char in the UUID
+ * to make sure they are unique.
+ */
+ blob1 = calloc(1, sizeof(*blob1));
+ SPDK_CU_ASSERT_FATAL(blob1 != NULL);
+ blob1->id = 0x1;
+ spdk_blob_set_xattr(blob1, "uuid", uuid, SPDK_UUID_STRING_LEN);
+ spdk_blob_set_xattr(blob1, "name", "lvol1", strnlen("lvol1", SPDK_LVOL_NAME_MAX) + 1);
+ blob1->uuid[SPDK_UUID_STRING_LEN - 2] = '1';
+
+ blob2 = calloc(1, sizeof(*blob2));
+ SPDK_CU_ASSERT_FATAL(blob2 != NULL);
+ blob2->id = 0x2;
+ spdk_blob_set_xattr(blob2, "uuid", uuid, SPDK_UUID_STRING_LEN);
+ spdk_blob_set_xattr(blob2, "name", "lvol2", strnlen("lvol2", SPDK_LVOL_NAME_MAX) + 1);
+ blob2->uuid[SPDK_UUID_STRING_LEN - 2] = '2';
+
+ blob3 = calloc(1, sizeof(*blob3));
+ SPDK_CU_ASSERT_FATAL(blob3 != NULL);
+ blob3->id = 0x2;
+ spdk_blob_set_xattr(blob3, "uuid", uuid, SPDK_UUID_STRING_LEN);
+ spdk_blob_set_xattr(blob3, "name", "lvol3", strnlen("lvol3", SPDK_LVOL_NAME_MAX) + 1);
+ blob3->uuid[SPDK_UUID_STRING_LEN - 2] = '3';
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ /* Load lvs with 0 blobs */
+ g_lvserrno = 0;
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT(g_lvol_store != NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+
+ TAILQ_INSERT_TAIL(&dev.bs->blobs, blob1, link);
+ TAILQ_INSERT_TAIL(&dev.bs->blobs, blob2, link);
+ TAILQ_INSERT_TAIL(&dev.bs->blobs, blob3, link);
+
+ /* Load lvs again with 3 blobs, but fail on 1st one */
+ g_lvol_store = NULL;
+ g_lvserrno = 0;
+ blob1->load_status = -1;
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno != 0);
+ CU_ASSERT(g_lvol_store == NULL);
+
+ /* Load lvs again with 3 blobs, but fail on 3rd one */
+ g_lvol_store = NULL;
+ g_lvserrno = 0;
+ blob1->load_status = 0;
+ blob2->load_status = 0;
+ blob3->load_status = -1;
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno != 0);
+ CU_ASSERT(g_lvol_store == NULL);
+
+ /* Load lvs again with 3 blobs, with success */
+ g_lvol_store = NULL;
+ g_lvserrno = 0;
+ blob1->load_status = 0;
+ blob2->load_status = 0;
+ blob3->load_status = 0;
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ CU_ASSERT(!TAILQ_EMPTY(&g_lvol_store->lvols));
+
+ g_lvserrno = -1;
+ /* rc = */ spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ /*
+ * Disable these two asserts for now. lvolstore should allow unload as long
+ * as the lvols were not opened - but this is coming a future patch.
+ */
+ /* CU_ASSERT(rc == 0); */
+ /* CU_ASSERT(g_lvserrno == 0); */
+
+ free(req);
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_open(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_with_handle_req *req;
+ struct spdk_bs_opts bs_opts;
+ struct spdk_blob *super_blob, *blob1, *blob2, *blob3;
+ struct spdk_lvol *lvol, *tmp;
+
+ req = calloc(1, sizeof(*req));
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+
+ init_dev(&dev);
+ spdk_bs_opts_init(&bs_opts);
+ snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "LVOLSTORE");
+ spdk_bs_init(&dev.bs_dev, &bs_opts, null_cb, NULL);
+ super_blob = calloc(1, sizeof(*super_blob));
+ SPDK_CU_ASSERT_FATAL(super_blob != NULL);
+ super_blob->id = 0x100;
+ spdk_blob_set_xattr(super_blob, "uuid", uuid, SPDK_UUID_STRING_LEN);
+ spdk_blob_set_xattr(super_blob, "name", "lvs", strnlen("lvs", SPDK_LVS_NAME_MAX) + 1);
+ TAILQ_INSERT_TAIL(&dev.bs->blobs, super_blob, link);
+ dev.bs->super_blobid = 0x100;
+
+ /*
+ * Create 3 blobs, write different char values to the last char in the UUID
+ * to make sure they are unique.
+ */
+ blob1 = calloc(1, sizeof(*blob1));
+ SPDK_CU_ASSERT_FATAL(blob1 != NULL);
+ blob1->id = 0x1;
+ spdk_blob_set_xattr(blob1, "uuid", uuid, SPDK_UUID_STRING_LEN);
+ spdk_blob_set_xattr(blob1, "name", "lvol1", strnlen("lvol1", SPDK_LVOL_NAME_MAX) + 1);
+ blob1->uuid[SPDK_UUID_STRING_LEN - 2] = '1';
+
+ blob2 = calloc(1, sizeof(*blob2));
+ SPDK_CU_ASSERT_FATAL(blob2 != NULL);
+ blob2->id = 0x2;
+ spdk_blob_set_xattr(blob2, "uuid", uuid, SPDK_UUID_STRING_LEN);
+ spdk_blob_set_xattr(blob2, "name", "lvol2", strnlen("lvol2", SPDK_LVOL_NAME_MAX) + 1);
+ blob2->uuid[SPDK_UUID_STRING_LEN - 2] = '2';
+
+ blob3 = calloc(1, sizeof(*blob3));
+ SPDK_CU_ASSERT_FATAL(blob3 != NULL);
+ blob3->id = 0x2;
+ spdk_blob_set_xattr(blob3, "uuid", uuid, SPDK_UUID_STRING_LEN);
+ spdk_blob_set_xattr(blob3, "name", "lvol3", strnlen("lvol3", SPDK_LVOL_NAME_MAX) + 1);
+ blob3->uuid[SPDK_UUID_STRING_LEN - 2] = '3';
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ TAILQ_INSERT_TAIL(&dev.bs->blobs, blob1, link);
+ TAILQ_INSERT_TAIL(&dev.bs->blobs, blob2, link);
+ TAILQ_INSERT_TAIL(&dev.bs->blobs, blob3, link);
+
+ /* Load lvs with 3 blobs */
+ g_lvol_store = NULL;
+ g_lvserrno = 0;
+ spdk_lvs_load(&dev.bs_dev, lvol_store_op_with_handle_complete, req);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_lvol_stores));
+
+ blob1->open_status = -1;
+ blob2->open_status = -1;
+ blob3->open_status = -1;
+
+ /* Fail opening all lvols */
+ TAILQ_FOREACH_SAFE(lvol, &g_lvol_store->lvols, link, tmp) {
+ spdk_lvol_open(lvol, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno != 0);
+ }
+
+ blob1->open_status = 0;
+ blob2->open_status = 0;
+ blob3->open_status = 0;
+
+ /* Open all lvols */
+ TAILQ_FOREACH_SAFE(lvol, &g_lvol_store->lvols, link, tmp) {
+ spdk_lvol_open(lvol, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ }
+
+ /* Close all lvols */
+ TAILQ_FOREACH_SAFE(lvol, &g_lvol_store->lvols, link, tmp) {
+ spdk_lvol_close(lvol, lvol_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ }
+
+ g_lvserrno = -1;
+ spdk_lvs_destroy(g_lvol_store, lvol_store_op_complete, NULL);
+
+ free(req);
+ free(blob1);
+ free(blob2);
+ free(blob3);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_snapshot(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvol *lvol;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, true, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ lvol = g_lvol;
+
+ spdk_lvol_create_snapshot(lvol, "snap", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT_STRING_EQUAL(g_lvol->name, "snap");
+
+ /* Lvol has to be closed (or destroyed) before unloading lvol store. */
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvserrno = -1;
+
+ spdk_lvol_close(lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvserrno = -1;
+
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_snapshot_fail(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvol *lvol, *snap;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, true, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ lvol = g_lvol;
+
+ spdk_lvol_create_snapshot(NULL, "snap", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno < 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol == NULL);
+
+ spdk_lvol_create_snapshot(lvol, "", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno < 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol == NULL);
+
+ spdk_lvol_create_snapshot(lvol, NULL, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno < 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol == NULL);
+
+ spdk_lvol_create_snapshot(lvol, "snap", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT_STRING_EQUAL(g_lvol->name, "snap");
+
+ snap = g_lvol;
+
+ spdk_lvol_create_snapshot(lvol, "snap", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno < 0);
+
+ spdk_lvol_close(lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvserrno = -1;
+
+ spdk_lvol_close(snap, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvserrno = -1;
+
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_clone(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvol *lvol;
+ struct spdk_lvol *snap;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, true, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ lvol = g_lvol;
+
+ spdk_lvol_create_snapshot(lvol, "snap", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT_STRING_EQUAL(g_lvol->name, "snap");
+
+ snap = g_lvol;
+
+ spdk_lvol_create_clone(snap, "clone", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT_STRING_EQUAL(g_lvol->name, "clone");
+
+ /* Lvol has to be closed (or destroyed) before unloading lvol store. */
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvserrno = -1;
+
+ spdk_lvol_close(snap, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvserrno = -1;
+
+ spdk_lvol_close(lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvserrno = -1;
+
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_clone_fail(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvol *lvol;
+ struct spdk_lvol *snap;
+ struct spdk_lvol *clone;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, true, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ lvol = g_lvol;
+
+ spdk_lvol_create_snapshot(lvol, "snap", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT_STRING_EQUAL(g_lvol->name, "snap");
+
+ snap = g_lvol;
+
+ spdk_lvol_create_clone(NULL, "clone", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno < 0);
+
+ spdk_lvol_create_clone(snap, "", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno < 0);
+
+ spdk_lvol_create_clone(snap, NULL, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno < 0);
+
+ spdk_lvol_create_clone(snap, "clone", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT_STRING_EQUAL(g_lvol->name, "clone");
+
+ clone = g_lvol;
+
+ spdk_lvol_create_clone(snap, "clone", lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno < 0);
+
+ /* Lvol has to be closed (or destroyed) before unloading lvol store. */
+ spdk_lvol_close(clone, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvserrno = -1;
+
+ spdk_lvol_close(snap, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvserrno = -1;
+
+ spdk_lvol_close(lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvserrno = -1;
+
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_names(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ struct spdk_lvol_store *lvs;
+ struct spdk_lvol *lvol, *lvol2;
+ char fullname[SPDK_LVOL_NAME_MAX];
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ g_lvol_store = NULL;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ lvs = g_lvol_store;
+
+ rc = spdk_lvol_create(lvs, NULL, 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == -EINVAL);
+
+ rc = spdk_lvol_create(lvs, "", 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == -EINVAL);
+
+ memset(fullname, 'x', sizeof(fullname));
+ rc = spdk_lvol_create(lvs, fullname, 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == -EINVAL);
+
+ g_lvserrno = -1;
+ rc = spdk_lvol_create(lvs, "lvol", 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ lvol = g_lvol;
+
+ rc = spdk_lvol_create(lvs, "lvol", 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == -EEXIST);
+
+ g_lvserrno = -1;
+ rc = spdk_lvol_create(lvs, "lvol2", 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ lvol2 = g_lvol;
+
+ spdk_lvol_close(lvol, close_cb, NULL);
+ spdk_lvol_destroy(lvol, lvol_op_complete, NULL);
+
+ g_lvserrno = -1;
+ g_lvol = NULL;
+ rc = spdk_lvol_create(lvs, "lvol", 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ lvol = g_lvol;
+
+ spdk_lvol_close(lvol, close_cb, NULL);
+ spdk_lvol_destroy(lvol, destroy_cb, NULL);
+
+ spdk_lvol_close(lvol2, close_cb, NULL);
+ spdk_lvol_destroy(lvol2, destroy_cb, NULL);
+
+ /* Simulate creating two lvols with same name simultaneously. */
+ lvol = calloc(1, sizeof(*lvol));
+ SPDK_CU_ASSERT_FATAL(lvol != NULL);
+ snprintf(lvol->name, sizeof(lvol->name), "tmp_name");
+ TAILQ_INSERT_TAIL(&lvs->pending_lvols, lvol, link);
+ rc = spdk_lvol_create(lvs, "tmp_name", 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == -EEXIST);
+
+ /* Remove name from temporary list and try again. */
+ TAILQ_REMOVE(&lvs->pending_lvols, lvol, link);
+ free(lvol);
+
+ rc = spdk_lvol_create(lvs, "tmp_name", 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ lvol = g_lvol;
+
+ spdk_lvol_close(lvol, close_cb, NULL);
+ spdk_lvol_destroy(lvol, destroy_cb, NULL);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_destroy(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ spdk_free_thread();
+}
+
+static void
+lvol_rename(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ struct spdk_lvol_store *lvs;
+ struct spdk_lvol *lvol, *lvol2;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ g_lvol_store = NULL;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ lvs = g_lvol_store;
+
+ /* Trying to create new lvol */
+ g_lvserrno = -1;
+ rc = spdk_lvol_create(lvs, "lvol", 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ lvol = g_lvol;
+
+ /* Trying to create second lvol with existing lvol name */
+ g_lvserrno = -1;
+ g_lvol = NULL;
+ rc = spdk_lvol_create(lvs, "lvol", 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == -EEXIST);
+ CU_ASSERT(g_lvserrno == -1);
+ SPDK_CU_ASSERT_FATAL(g_lvol == NULL);
+
+ /* Trying to create second lvol with non existing name */
+ g_lvserrno = -1;
+ rc = spdk_lvol_create(lvs, "lvol2", 1, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ lvol2 = g_lvol;
+
+ /* Trying to rename lvol with not existing name */
+ spdk_lvol_rename(lvol, "lvol_new", lvol_op_complete, NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+ CU_ASSERT_STRING_EQUAL(lvol->name, "lvol_new");
+
+ /* Trying to rename lvol with other lvol name */
+ spdk_lvol_rename(lvol2, "lvol_new", lvol_op_complete, NULL);
+ CU_ASSERT(g_lvolerrno == -EEXIST);
+ CU_ASSERT_STRING_NOT_EQUAL(lvol2->name, "lvol_new");
+
+ spdk_lvol_close(lvol, close_cb, NULL);
+ spdk_lvol_destroy(lvol, lvol_op_complete, NULL);
+
+ spdk_lvol_close(lvol2, close_cb, NULL);
+ spdk_lvol_destroy(lvol2, lvol_op_complete, NULL);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_destroy(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ spdk_free_thread();
+}
+
+static void
+lvs_rename(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ struct spdk_lvol_store *lvs, *lvs2;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+ g_lvserrno = -1;
+ g_lvol_store = NULL;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ lvs = g_lvol_store;
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "unimportant_lvs_name");
+ g_lvserrno = -1;
+ g_lvol_store = NULL;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+ lvs2 = g_lvol_store;
+
+ /* Trying to rename lvs with new name */
+ spdk_lvs_rename(lvs, "new_lvs_name", lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name");
+
+ /* Trying to rename lvs with name lvs already has */
+ spdk_lvs_rename(lvs, "new_lvs_name", lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name");
+
+ /* Trying to rename lvs with name already existing */
+ spdk_lvs_rename(lvs2, "new_lvs_name", lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == -EEXIST);
+ CU_ASSERT_STRING_EQUAL(lvs2->name, "unimportant_lvs_name");
+
+ /* Trying to rename lvs with another rename process started with the same name */
+ /* Simulate renaming process in progress */
+ snprintf(lvs2->new_name, sizeof(lvs2->new_name), "another_new_lvs_name");
+ CU_ASSERT_STRING_EQUAL(lvs2->new_name, "another_new_lvs_name");
+ /* Start second process */
+ spdk_lvs_rename(lvs, "another_new_lvs_name", lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno == -EEXIST);
+ CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name");
+ /* reverting lvs2 new name to proper value */
+ snprintf(lvs2->new_name, sizeof(lvs2->new_name), "unimportant_lvs_name");
+ CU_ASSERT_STRING_EQUAL(lvs2->new_name, "unimportant_lvs_name");
+
+ /* Simulate error while lvs rename */
+ g_lvs_rename_blob_open_error = true;
+ spdk_lvs_rename(lvs, "complete_new_lvs_name", lvol_store_op_complete, NULL);
+ CU_ASSERT(g_lvserrno != 0);
+ CU_ASSERT_STRING_EQUAL(lvs->name, "new_lvs_name");
+ CU_ASSERT_STRING_EQUAL(lvs->new_name, "new_lvs_name");
+ g_lvs_rename_blob_open_error = false;
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_destroy(lvs, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_destroy(lvs2, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ spdk_free_thread();
+}
+static void lvol_refcnt(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ struct spdk_lvol *lvol;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT(g_lvol->ref_count == 1);
+
+ lvol = g_lvol;
+ spdk_lvol_open(g_lvol, lvol_op_with_handle_complete, NULL);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+ CU_ASSERT(lvol->ref_count == 2);
+
+ /* Trying to destroy lvol while its open should fail */
+ spdk_lvol_destroy(lvol, lvol_op_complete, NULL);
+ CU_ASSERT(g_lvolerrno != 0);
+
+ spdk_lvol_close(lvol, lvol_op_complete, NULL);
+ CU_ASSERT(lvol->ref_count == 1);
+ CU_ASSERT(g_lvolerrno == 0);
+
+ spdk_lvol_close(lvol, lvol_op_complete, NULL);
+ CU_ASSERT(lvol->ref_count == 0);
+ CU_ASSERT(g_lvolerrno == 0);
+
+ /* Try to close already closed lvol */
+ spdk_lvol_close(lvol, lvol_op_complete, NULL);
+ CU_ASSERT(lvol->ref_count == 0);
+ CU_ASSERT(g_lvolerrno != 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_create_thin_provisioned(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ CU_ASSERT(g_lvol->blob->thin_provisioned == false);
+
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ spdk_lvol_destroy(g_lvol, destroy_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, true, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ CU_ASSERT(g_lvol->blob->thin_provisioned == true);
+
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ spdk_lvol_destroy(g_lvol, destroy_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_inflate(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ g_inflate_rc = -1;
+ spdk_lvol_inflate(g_lvol, lvol_op_complete, NULL);
+ CU_ASSERT(g_lvolerrno != 0);
+
+ g_inflate_rc = 0;
+ spdk_lvol_inflate(g_lvol, lvol_op_complete, NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ spdk_lvol_destroy(g_lvol, destroy_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ /* Make sure that all references to the io_channel was closed after
+ * inflate call
+ */
+ CU_ASSERT(g_io_channel == NULL);
+
+ spdk_free_thread();
+}
+
+static void
+lvol_decouple_parent(void)
+{
+ struct lvol_ut_bs_dev dev;
+ struct spdk_lvs_opts opts;
+ int rc = 0;
+
+ init_dev(&dev);
+
+ spdk_allocate_thread(_lvol_send_msg, NULL, NULL, NULL, NULL);
+
+ spdk_lvs_opts_init(&opts);
+ snprintf(opts.name, sizeof(opts.name), "lvs");
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
+
+ spdk_lvol_create(g_lvol_store, "lvol", 10, false, lvol_op_with_handle_complete, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ SPDK_CU_ASSERT_FATAL(g_lvol != NULL);
+
+ g_inflate_rc = -1;
+ spdk_lvol_decouple_parent(g_lvol, lvol_op_complete, NULL);
+ CU_ASSERT(g_lvolerrno != 0);
+
+ g_inflate_rc = 0;
+ spdk_lvol_decouple_parent(g_lvol, lvol_op_complete, NULL);
+ CU_ASSERT(g_lvolerrno == 0);
+
+ spdk_lvol_close(g_lvol, close_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+ spdk_lvol_destroy(g_lvol, destroy_cb, NULL);
+ CU_ASSERT(g_lvserrno == 0);
+
+ g_lvserrno = -1;
+ rc = spdk_lvs_unload(g_lvol_store, lvol_store_op_complete, NULL);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_lvserrno == 0);
+ g_lvol_store = NULL;
+
+ free_dev(&dev);
+
+ /* Make sure that all references to the io_channel was closed after
+ * inflate call
+ */
+ CU_ASSERT(g_io_channel == NULL);
+
+ spdk_free_thread();
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("lvol", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "lvs_init_unload_success", lvs_init_unload_success) == NULL ||
+ CU_add_test(suite, "lvs_init_destroy_success", lvs_init_destroy_success) == NULL ||
+ CU_add_test(suite, "lvs_init_opts_success", lvs_init_opts_success) == NULL ||
+ CU_add_test(suite, "lvs_unload_lvs_is_null_fail", lvs_unload_lvs_is_null_fail) == NULL ||
+ CU_add_test(suite, "lvs_names", lvs_names) == NULL ||
+ CU_add_test(suite, "lvol_create_destroy_success", lvol_create_destroy_success) == NULL ||
+ CU_add_test(suite, "lvol_create_fail", lvol_create_fail) == NULL ||
+ CU_add_test(suite, "lvol_destroy_fail", lvol_destroy_fail) == NULL ||
+ CU_add_test(suite, "lvol_close_fail", lvol_close_fail) == NULL ||
+ CU_add_test(suite, "lvol_close_success", lvol_close_success) == NULL ||
+ CU_add_test(suite, "lvol_resize", lvol_resize) == NULL ||
+ CU_add_test(suite, "lvs_load", lvs_load) == NULL ||
+ CU_add_test(suite, "lvols_load", lvols_load) == NULL ||
+ CU_add_test(suite, "lvol_open", lvol_open) == NULL ||
+ CU_add_test(suite, "lvol_load", lvs_load) == NULL ||
+ CU_add_test(suite, "lvs_load", lvols_load) == NULL ||
+ CU_add_test(suite, "lvol_open", lvol_open) == NULL ||
+ CU_add_test(suite, "lvol_snapshot", lvol_snapshot) == NULL ||
+ CU_add_test(suite, "lvol_snapshot_fail", lvol_snapshot_fail) == NULL ||
+ CU_add_test(suite, "lvol_clone", lvol_clone) == NULL ||
+ CU_add_test(suite, "lvol_clone_fail", lvol_clone_fail) == NULL ||
+ CU_add_test(suite, "lvol_refcnt", lvol_refcnt) == NULL ||
+ CU_add_test(suite, "lvol_names", lvol_names) == NULL ||
+ CU_add_test(suite, "lvol_create_thin_provisioned", lvol_create_thin_provisioned) == NULL ||
+ CU_add_test(suite, "lvol_rename", lvol_rename) == NULL ||
+ CU_add_test(suite, "lvs_rename", lvs_rename) == NULL ||
+ CU_add_test(suite, "lvol_inflate", lvol_inflate) == NULL ||
+ CU_add_test(suite, "lvol_decouple_parent", lvol_decouple_parent) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvme/Makefile b/src/spdk/test/unit/lib/nvme/Makefile
new file mode 100644
index 00000000..fb17a2d0
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/Makefile
@@ -0,0 +1,47 @@
+#
+# 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.c nvme_ctrlr.c nvme_ctrlr_cmd.c nvme_ctrlr_ocssd_cmd.c nvme_ns.c nvme_ns_cmd.c nvme_ns_ocssd_cmd.c nvme_pcie.c nvme_qpair.c \
+ nvme_quirks.c \
+
+DIRS-$(CONFIG_RDMA) += nvme_rdma.c
+
+.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/unit/lib/nvme/nvme.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme.c/.gitignore
new file mode 100644
index 00000000..90c0c167
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme.c/.gitignore
@@ -0,0 +1 @@
+nvme_ut
diff --git a/src/spdk/test/unit/lib/nvme/nvme.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme.c/Makefile
new file mode 100644
index 00000000..4202cf54
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = nvme_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvme/nvme.c/nvme_ut.c b/src/spdk/test/unit/lib/nvme/nvme.c/nvme_ut.c
new file mode 100644
index 00000000..6925a2cf
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme.c/nvme_ut.c
@@ -0,0 +1,1135 @@
+/*-
+ * 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_cunit.h"
+
+#include "spdk/env.h"
+
+#include "nvme/nvme.c"
+
+#include "spdk_internal/mock.h"
+
+#include "common/lib/test_env.c"
+
+DEFINE_STUB_V(nvme_ctrlr_fail,
+ (struct spdk_nvme_ctrlr *ctrlr, bool hot_remove))
+
+DEFINE_STUB_V(nvme_ctrlr_proc_get_ref, (struct spdk_nvme_ctrlr *ctrlr))
+
+DEFINE_STUB_V(nvme_ctrlr_proc_put_ref, (struct spdk_nvme_ctrlr *ctrlr))
+
+DEFINE_STUB(spdk_pci_nvme_enumerate, int,
+ (spdk_pci_enum_cb enum_cb, void *enum_ctx), -1)
+
+DEFINE_STUB(spdk_pci_device_get_id, struct spdk_pci_id,
+ (struct spdk_pci_device *pci_dev),
+ MOCK_STRUCT_INIT(.vendor_id = 0xffff, .device_id = 0xffff,
+ .subvendor_id = 0xffff, .subdevice_id = 0xffff))
+
+DEFINE_STUB(spdk_nvme_transport_available, bool,
+ (enum spdk_nvme_transport_type trtype), true)
+
+DEFINE_STUB(nvme_ctrlr_add_process, int,
+ (struct spdk_nvme_ctrlr *ctrlr, void *devhandle), 0)
+
+DEFINE_STUB(nvme_ctrlr_process_init, int,
+ (struct spdk_nvme_ctrlr *ctrlr), 0)
+
+DEFINE_STUB(spdk_pci_device_get_addr, struct spdk_pci_addr,
+ (struct spdk_pci_device *pci_dev), {0})
+
+DEFINE_STUB(nvme_ctrlr_get_ref_count, int,
+ (struct spdk_nvme_ctrlr *ctrlr), 0)
+
+DEFINE_STUB(dummy_probe_cb, bool,
+ (void *cb_ctx, const struct spdk_nvme_transport_id *trid,
+ struct spdk_nvme_ctrlr_opts *opts), false)
+
+DEFINE_STUB(nvme_transport_ctrlr_construct, struct spdk_nvme_ctrlr *,
+ (const struct spdk_nvme_transport_id *trid,
+ const struct spdk_nvme_ctrlr_opts *opts,
+ void *devhandle), NULL)
+
+DEFINE_STUB(spdk_nvme_qpair_process_completions, int32_t,
+ (struct spdk_nvme_qpair *qpair,
+ uint32_t max_completions), 0);
+
+static bool ut_destruct_called = false;
+void
+nvme_ctrlr_destruct(struct spdk_nvme_ctrlr *ctrlr)
+{
+ ut_destruct_called = true;
+}
+
+void
+spdk_nvme_ctrlr_get_default_ctrlr_opts(struct spdk_nvme_ctrlr_opts *opts, size_t opts_size)
+{
+ memset(opts, 0, sizeof(*opts));
+}
+
+static void
+memset_trid(struct spdk_nvme_transport_id *trid1, struct spdk_nvme_transport_id *trid2)
+{
+ memset(trid1, 0, sizeof(struct spdk_nvme_transport_id));
+ memset(trid2, 0, sizeof(struct spdk_nvme_transport_id));
+}
+
+static bool ut_check_trtype = false;
+int
+nvme_transport_ctrlr_scan(const struct spdk_nvme_transport_id *trid,
+ void *cb_ctx,
+ spdk_nvme_probe_cb probe_cb,
+ spdk_nvme_remove_cb remove_cb,
+ bool direct_connect)
+{
+ struct spdk_nvme_ctrlr *ctrlr = NULL;
+
+ if (ut_check_trtype == true) {
+ CU_ASSERT(trid->trtype == SPDK_NVME_TRANSPORT_PCIE);
+ }
+
+ if (direct_connect == true && probe_cb) {
+ nvme_robust_mutex_unlock(&g_spdk_nvme_driver->lock);
+ ctrlr = spdk_nvme_get_ctrlr_by_trid(trid);
+ nvme_robust_mutex_lock(&g_spdk_nvme_driver->lock);
+ probe_cb(cb_ctx, trid, &ctrlr->opts);
+ }
+ return 0;
+}
+
+static bool ut_attach_cb_called = false;
+static void
+dummy_attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
+ struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts)
+{
+ ut_attach_cb_called = true;
+}
+
+static void
+test_spdk_nvme_probe(void)
+{
+ int rc = 0;
+ const struct spdk_nvme_transport_id *trid = NULL;
+ void *cb_ctx = NULL;
+ spdk_nvme_probe_cb probe_cb = NULL;
+ spdk_nvme_attach_cb attach_cb = dummy_attach_cb;
+ spdk_nvme_remove_cb remove_cb = NULL;
+ struct spdk_nvme_ctrlr ctrlr;
+ pthread_mutexattr_t attr;
+ struct nvme_driver dummy;
+ g_spdk_nvme_driver = &dummy;
+
+ /* driver init fails */
+ MOCK_SET(spdk_process_is_primary, false);
+ MOCK_SET(spdk_memzone_lookup, NULL);
+ rc = spdk_nvme_probe(trid, cb_ctx, probe_cb, attach_cb, remove_cb);
+ CU_ASSERT(rc == -1);
+
+ /*
+ * For secondary processes, the attach_cb should automatically get
+ * called for any controllers already initialized by the primary
+ * process.
+ */
+ MOCK_SET(spdk_nvme_transport_available, false);
+ MOCK_SET(spdk_process_is_primary, true);
+ dummy.initialized = true;
+ g_spdk_nvme_driver = &dummy;
+ rc = spdk_nvme_probe(trid, cb_ctx, probe_cb, attach_cb, remove_cb);
+ CU_ASSERT(rc == -1);
+
+ /* driver init passes, transport available, secondary call attach_cb */
+ MOCK_SET(spdk_nvme_transport_available, true);
+ MOCK_SET(spdk_process_is_primary, false);
+ MOCK_SET(spdk_memzone_lookup, g_spdk_nvme_driver);
+ dummy.initialized = true;
+ memset(&ctrlr, 0, sizeof(struct spdk_nvme_ctrlr));
+ CU_ASSERT(pthread_mutexattr_init(&attr) == 0);
+ CU_ASSERT(pthread_mutex_init(&dummy.lock, &attr) == 0);
+ TAILQ_INIT(&dummy.shared_attached_ctrlrs);
+ TAILQ_INSERT_TAIL(&dummy.shared_attached_ctrlrs, &ctrlr, tailq);
+ ut_attach_cb_called = false;
+ /* setup nvme_transport_ctrlr_scan() stub to also check the trype */
+ ut_check_trtype = true;
+ rc = spdk_nvme_probe(trid, cb_ctx, probe_cb, attach_cb, remove_cb);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(ut_attach_cb_called == true);
+
+ /* driver init passes, transport available, we are primary */
+ MOCK_SET(spdk_process_is_primary, true);
+ TAILQ_INIT(&g_nvme_init_ctrlrs);
+ rc = spdk_nvme_probe(trid, cb_ctx, probe_cb, attach_cb, remove_cb);
+ CU_ASSERT(rc == 0);
+
+ g_spdk_nvme_driver = NULL;
+ /* reset to pre-test values */
+ MOCK_CLEAR(spdk_memzone_lookup);
+ ut_check_trtype = false;
+
+ pthread_mutex_destroy(&dummy.lock);
+ pthread_mutexattr_destroy(&attr);
+}
+
+static void
+test_spdk_nvme_connect(void)
+{
+ struct spdk_nvme_ctrlr *ret_ctrlr = NULL;
+ struct spdk_nvme_transport_id trid = {};
+ struct spdk_nvme_ctrlr_opts opts = {};
+ struct spdk_nvme_ctrlr ctrlr;
+ pthread_mutexattr_t attr;
+ struct nvme_driver dummy;
+
+ /* initialize the variable to prepare the test */
+ dummy.initialized = true;
+ TAILQ_INIT(&dummy.shared_attached_ctrlrs);
+ g_spdk_nvme_driver = &dummy;
+ CU_ASSERT(pthread_mutexattr_init(&attr) == 0);
+ CU_ASSERT(pthread_mutex_init(&g_spdk_nvme_driver->lock, &attr) == 0);
+
+ /* set NULL trid pointer to test immediate return */
+ ret_ctrlr = spdk_nvme_connect(NULL, NULL, 0);
+ CU_ASSERT(ret_ctrlr == NULL);
+
+ /* driver init passes, transport available, secondary process connects ctrlr */
+ MOCK_SET(spdk_process_is_primary, false);
+ MOCK_SET(spdk_memzone_lookup, g_spdk_nvme_driver);
+ MOCK_SET(spdk_nvme_transport_available, true);
+ memset(&trid, 0, sizeof(trid));
+ trid.trtype = SPDK_NVME_TRANSPORT_PCIE;
+ ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0);
+ CU_ASSERT(ret_ctrlr == NULL);
+
+ /* driver init passes, setup one ctrlr on the attached_list */
+ memset(&ctrlr, 0, sizeof(struct spdk_nvme_ctrlr));
+ snprintf(ctrlr.trid.traddr, sizeof(ctrlr.trid.traddr), "0000:01:00.0");
+ ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_PCIE;
+ TAILQ_INSERT_TAIL(&g_spdk_nvme_driver->shared_attached_ctrlrs, &ctrlr, tailq);
+ /* get the ctrlr from the attached list */
+ snprintf(trid.traddr, sizeof(trid.traddr), "0000:01:00.0");
+ ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0);
+ CU_ASSERT(ret_ctrlr == &ctrlr);
+ /* get the ctrlr from the attached list with default ctrlr opts */
+ ctrlr.opts.num_io_queues = DEFAULT_MAX_IO_QUEUES;
+ ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0);
+ CU_ASSERT(ret_ctrlr == &ctrlr);
+ CU_ASSERT_EQUAL(ret_ctrlr->opts.num_io_queues, DEFAULT_MAX_IO_QUEUES);
+ /* get the ctrlr from the attached list with default ctrlr opts and consistent opts_size */
+ opts.num_io_queues = 1;
+ ret_ctrlr = spdk_nvme_connect(&trid, &opts, sizeof(opts));
+ CU_ASSERT(ret_ctrlr == &ctrlr);
+ CU_ASSERT_EQUAL(ret_ctrlr->opts.num_io_queues, 1);
+ /* remove the attached ctrlr on the attached_list */
+ CU_ASSERT(spdk_nvme_detach(&ctrlr) == 0);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_nvme_driver->shared_attached_ctrlrs));
+
+ /* driver init passes, transport available, primary process connects ctrlr */
+ MOCK_SET(spdk_process_is_primary, true);
+ /* setup one ctrlr on the attached_list */
+ memset(&ctrlr, 0, sizeof(struct spdk_nvme_ctrlr));
+ snprintf(ctrlr.trid.traddr, sizeof(ctrlr.trid.traddr), "0000:02:00.0");
+ ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_PCIE;
+ TAILQ_INSERT_TAIL(&g_spdk_nvme_driver->shared_attached_ctrlrs, &ctrlr, tailq);
+ /* get the ctrlr from the attached list */
+ snprintf(trid.traddr, sizeof(trid.traddr), "0000:02:00.0");
+ ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0);
+ CU_ASSERT(ret_ctrlr == &ctrlr);
+ /* get the ctrlr from the attached list with default ctrlr opts */
+ ctrlr.opts.num_io_queues = DEFAULT_MAX_IO_QUEUES;
+ ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0);
+ CU_ASSERT(ret_ctrlr == &ctrlr);
+ CU_ASSERT_EQUAL(ret_ctrlr->opts.num_io_queues, DEFAULT_MAX_IO_QUEUES);
+ /* get the ctrlr from the attached list with default ctrlr opts and consistent opts_size */
+ opts.num_io_queues = 2;
+ ret_ctrlr = spdk_nvme_connect(&trid, &opts, sizeof(opts));
+ CU_ASSERT(ret_ctrlr == &ctrlr);
+ CU_ASSERT_EQUAL(ret_ctrlr->opts.num_io_queues, 2);
+ /* remove the attached ctrlr on the attached_list */
+ CU_ASSERT(spdk_nvme_detach(ret_ctrlr) == 0);
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_nvme_driver->shared_attached_ctrlrs));
+
+ /* test driver init failure return */
+ MOCK_SET(spdk_process_is_primary, false);
+ MOCK_SET(spdk_memzone_lookup, NULL);
+ ret_ctrlr = spdk_nvme_connect(&trid, NULL, 0);
+ CU_ASSERT(ret_ctrlr == NULL);
+}
+
+static void
+test_nvme_init_controllers(void)
+{
+ int rc = 0;
+ struct nvme_driver test_driver;
+ void *cb_ctx = NULL;
+ spdk_nvme_attach_cb attach_cb = dummy_attach_cb;
+ struct spdk_nvme_ctrlr ctrlr;
+ pthread_mutexattr_t attr;
+
+ g_spdk_nvme_driver = &test_driver;
+ memset(&ctrlr, 0, sizeof(struct spdk_nvme_ctrlr));
+ ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_PCIE;
+ CU_ASSERT(pthread_mutexattr_init(&attr) == 0);
+ CU_ASSERT(pthread_mutex_init(&test_driver.lock, &attr) == 0);
+ TAILQ_INIT(&g_nvme_init_ctrlrs);
+ TAILQ_INSERT_TAIL(&g_nvme_init_ctrlrs, &ctrlr, tailq);
+ TAILQ_INIT(&test_driver.shared_attached_ctrlrs);
+
+ /*
+ * Try to initialize, but nvme_ctrlr_process_init will fail.
+ * Verify correct behavior when it does.
+ */
+ MOCK_SET(nvme_ctrlr_process_init, 1);
+ g_spdk_nvme_driver->initialized = false;
+ ut_destruct_called = false;
+ rc = nvme_init_controllers(cb_ctx, attach_cb);
+ CU_ASSERT(rc == -1);
+ CU_ASSERT(g_spdk_nvme_driver->initialized == true);
+ CU_ASSERT(TAILQ_EMPTY(&g_nvme_init_ctrlrs));
+ CU_ASSERT(ut_destruct_called == true);
+
+ /*
+ * Controller init OK, need to move the controller state machine
+ * forward by setting the ctrl state so that it can be moved
+ * the shared_attached_ctrlrs list.
+ */
+ TAILQ_INSERT_TAIL(&g_nvme_init_ctrlrs, &ctrlr, tailq);
+ ctrlr.state = NVME_CTRLR_STATE_READY;
+ MOCK_SET(nvme_ctrlr_process_init, 0);
+ rc = nvme_init_controllers(cb_ctx, attach_cb);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(ut_attach_cb_called == true);
+ CU_ASSERT(TAILQ_EMPTY(&g_nvme_init_ctrlrs));
+ CU_ASSERT(TAILQ_EMPTY(&g_nvme_attached_ctrlrs));
+ CU_ASSERT(TAILQ_FIRST(&g_spdk_nvme_driver->shared_attached_ctrlrs) == &ctrlr);
+ TAILQ_REMOVE(&g_spdk_nvme_driver->shared_attached_ctrlrs, &ctrlr, tailq);
+
+ /*
+ * Non-PCIe controllers should be added to the per-process list, not the shared list.
+ */
+ memset(&ctrlr, 0, sizeof(struct spdk_nvme_ctrlr));
+ ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_RDMA;
+ TAILQ_INSERT_TAIL(&g_nvme_init_ctrlrs, &ctrlr, tailq);
+ ctrlr.state = NVME_CTRLR_STATE_READY;
+ MOCK_SET(nvme_ctrlr_process_init, 0);
+ rc = nvme_init_controllers(cb_ctx, attach_cb);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(ut_attach_cb_called == true);
+ CU_ASSERT(TAILQ_EMPTY(&g_nvme_init_ctrlrs));
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_nvme_driver->shared_attached_ctrlrs));
+ CU_ASSERT(TAILQ_FIRST(&g_nvme_attached_ctrlrs) == &ctrlr);
+ TAILQ_REMOVE(&g_nvme_attached_ctrlrs, &ctrlr, tailq);
+
+ g_spdk_nvme_driver = NULL;
+ pthread_mutexattr_destroy(&attr);
+ pthread_mutex_destroy(&test_driver.lock);
+}
+
+static void
+test_nvme_driver_init(void)
+{
+ int rc;
+ struct nvme_driver dummy;
+ g_spdk_nvme_driver = &dummy;
+
+ /* adjust this so testing doesn't take so long */
+ g_nvme_driver_timeout_ms = 100;
+
+ /* process is primary and mem already reserved */
+ MOCK_SET(spdk_process_is_primary, true);
+ dummy.initialized = true;
+ rc = nvme_driver_init();
+ CU_ASSERT(rc == 0);
+
+ /*
+ * Process is primary and mem not yet reserved but the call
+ * to spdk_memzone_reserve() returns NULL.
+ */
+ g_spdk_nvme_driver = NULL;
+ MOCK_SET(spdk_process_is_primary, true);
+ MOCK_SET(spdk_memzone_reserve, NULL);
+ rc = nvme_driver_init();
+ CU_ASSERT(rc == -1);
+
+ /* process is not primary, no mem already reserved */
+ MOCK_SET(spdk_process_is_primary, false);
+ MOCK_SET(spdk_memzone_lookup, NULL);
+ g_spdk_nvme_driver = NULL;
+ rc = nvme_driver_init();
+ CU_ASSERT(rc == -1);
+
+ /* process is not primary, mem is already reserved & init'd */
+ MOCK_SET(spdk_process_is_primary, false);
+ MOCK_SET(spdk_memzone_lookup, (void *)&dummy);
+ dummy.initialized = true;
+ rc = nvme_driver_init();
+ CU_ASSERT(rc == 0);
+
+ /* process is not primary, mem is reserved but not initialized */
+ /* and times out */
+ MOCK_SET(spdk_process_is_primary, false);
+ MOCK_SET(spdk_memzone_reserve, (void *)&dummy);
+ dummy.initialized = false;
+ rc = nvme_driver_init();
+ CU_ASSERT(rc == -1);
+
+ /* process is primary, got mem but mutex won't init */
+ MOCK_SET(spdk_process_is_primary, true);
+ MOCK_SET(spdk_memzone_reserve, (void *)&dummy);
+ MOCK_SET(pthread_mutexattr_init, -1);
+ g_spdk_nvme_driver = NULL;
+ dummy.initialized = true;
+ rc = nvme_driver_init();
+ /* for FreeBSD we can't can't effectively mock this path */
+#ifndef __FreeBSD__
+ CU_ASSERT(rc != 0);
+#else
+ CU_ASSERT(rc == 0);
+#endif
+
+ /* process is primary, got mem, mutex OK */
+ MOCK_SET(spdk_process_is_primary, true);
+ MOCK_CLEAR(pthread_mutexattr_init);
+ g_spdk_nvme_driver = NULL;
+ rc = nvme_driver_init();
+ CU_ASSERT(g_spdk_nvme_driver->initialized == false);
+ CU_ASSERT(TAILQ_EMPTY(&g_nvme_init_ctrlrs));
+ CU_ASSERT(TAILQ_EMPTY(&g_spdk_nvme_driver->shared_attached_ctrlrs));
+ CU_ASSERT(rc == 0);
+
+ g_spdk_nvme_driver = NULL;
+ MOCK_CLEAR(spdk_memzone_reserve);
+ MOCK_CLEAR(spdk_memzone_lookup);
+}
+
+static void
+test_spdk_nvme_detach(void)
+{
+ int rc = 1;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_ctrlr *ret_ctrlr;
+ struct nvme_driver test_driver;
+
+ memset(&ctrlr, 0, sizeof(ctrlr));
+ ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_PCIE;
+
+ g_spdk_nvme_driver = &test_driver;
+ TAILQ_INIT(&test_driver.shared_attached_ctrlrs);
+ TAILQ_INSERT_TAIL(&test_driver.shared_attached_ctrlrs, &ctrlr, tailq);
+ CU_ASSERT(pthread_mutex_init(&test_driver.lock, NULL) == 0);
+
+ /*
+ * Controllers are ref counted so mock the function that returns
+ * the ref count so that detach will actually call the destruct
+ * function which we've mocked simply to verify that it gets
+ * called (we aren't testing what the real destruct function does
+ * here.)
+ */
+ MOCK_SET(nvme_ctrlr_get_ref_count, 0);
+ rc = spdk_nvme_detach(&ctrlr);
+ ret_ctrlr = TAILQ_FIRST(&test_driver.shared_attached_ctrlrs);
+ CU_ASSERT(ret_ctrlr == NULL);
+ CU_ASSERT(ut_destruct_called == true);
+ CU_ASSERT(rc == 0);
+
+ /*
+ * Mock the ref count to 1 so we confirm that the destruct
+ * function is not called and that attached ctrl list is
+ * not empty.
+ */
+ MOCK_SET(nvme_ctrlr_get_ref_count, 1);
+ TAILQ_INSERT_TAIL(&test_driver.shared_attached_ctrlrs, &ctrlr, tailq);
+ ut_destruct_called = false;
+ rc = spdk_nvme_detach(&ctrlr);
+ ret_ctrlr = TAILQ_FIRST(&test_driver.shared_attached_ctrlrs);
+ CU_ASSERT(ret_ctrlr != NULL);
+ CU_ASSERT(ut_destruct_called == false);
+ CU_ASSERT(rc == 0);
+
+ /*
+ * Non-PCIe controllers should be on the per-process attached_ctrlrs list, not the
+ * shared_attached_ctrlrs list. Test an RDMA controller and ensure it is removed
+ * from the correct list.
+ */
+ memset(&ctrlr, 0, sizeof(ctrlr));
+ ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_RDMA;
+ TAILQ_INIT(&g_nvme_attached_ctrlrs);
+ TAILQ_INSERT_TAIL(&g_nvme_attached_ctrlrs, &ctrlr, tailq);
+ MOCK_SET(nvme_ctrlr_get_ref_count, 0);
+ rc = spdk_nvme_detach(&ctrlr);
+ CU_ASSERT(TAILQ_EMPTY(&g_nvme_attached_ctrlrs));
+ CU_ASSERT(ut_destruct_called == true);
+ CU_ASSERT(rc == 0);
+
+ g_spdk_nvme_driver = NULL;
+ pthread_mutex_destroy(&test_driver.lock);
+}
+
+static void
+test_nvme_completion_poll_cb(void)
+{
+ struct nvme_completion_poll_status status;
+ struct spdk_nvme_cpl cpl;
+
+ memset(&status, 0x0, sizeof(status));
+ memset(&cpl, 0xff, sizeof(cpl));
+
+ nvme_completion_poll_cb(&status, &cpl);
+ CU_ASSERT(status.done == true);
+ CU_ASSERT(memcmp(&cpl, &status.cpl,
+ sizeof(struct spdk_nvme_cpl)) == 0);
+}
+
+/* stub callback used by test_nvme_user_copy_cmd_complete() */
+static struct spdk_nvme_cpl ut_spdk_nvme_cpl = {0};
+static void
+dummy_cb(void *user_cb_arg, struct spdk_nvme_cpl *cpl)
+{
+ ut_spdk_nvme_cpl = *cpl;
+}
+
+static void
+test_nvme_user_copy_cmd_complete(void)
+{
+ struct nvme_request req;
+ int test_data = 0xdeadbeef;
+ int buff_size = sizeof(int);
+ void *buff;
+ static struct spdk_nvme_cpl cpl;
+
+ memset(&req, 0, sizeof(req));
+ memset(&cpl, 0x5a, sizeof(cpl));
+
+ /* test without a user buffer provided */
+ req.user_cb_fn = (void *)dummy_cb;
+ nvme_user_copy_cmd_complete(&req, &cpl);
+ CU_ASSERT(memcmp(&ut_spdk_nvme_cpl, &cpl, sizeof(cpl)) == 0);
+
+ /* test with a user buffer provided */
+ req.user_buffer = malloc(buff_size);
+ SPDK_CU_ASSERT_FATAL(req.user_buffer != NULL);
+ memset(req.user_buffer, 0, buff_size);
+ req.payload_size = buff_size;
+ buff = spdk_dma_zmalloc(buff_size, 0x100, NULL);
+ SPDK_CU_ASSERT_FATAL(buff != NULL);
+ req.payload = NVME_PAYLOAD_CONTIG(buff, NULL);
+ memcpy(buff, &test_data, buff_size);
+ req.cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE;
+ req.pid = getpid();
+
+ /* zero out the test value set in the callback */
+ memset(&ut_spdk_nvme_cpl, 0, sizeof(ut_spdk_nvme_cpl));
+
+ nvme_user_copy_cmd_complete(&req, &cpl);
+ CU_ASSERT(memcmp(req.user_buffer, &test_data, buff_size) == 0);
+ CU_ASSERT(memcmp(&ut_spdk_nvme_cpl, &cpl, sizeof(cpl)) == 0);
+
+ /*
+ * Now test the same path as above but this time choose an opc
+ * that results in a different data transfer type.
+ */
+ memset(&ut_spdk_nvme_cpl, 0, sizeof(ut_spdk_nvme_cpl));
+ memset(req.user_buffer, 0, buff_size);
+ buff = spdk_dma_zmalloc(buff_size, 0x100, NULL);
+ SPDK_CU_ASSERT_FATAL(buff != NULL);
+ req.payload = NVME_PAYLOAD_CONTIG(buff, NULL);
+ memcpy(buff, &test_data, buff_size);
+ req.cmd.opc = SPDK_NVME_OPC_SET_FEATURES;
+ nvme_user_copy_cmd_complete(&req, &cpl);
+ CU_ASSERT(memcmp(req.user_buffer, &test_data, buff_size) != 0);
+ CU_ASSERT(memcmp(&ut_spdk_nvme_cpl, &cpl, sizeof(cpl)) == 0);
+
+ /* clean up */
+ free(req.user_buffer);
+}
+
+static void
+test_nvme_allocate_request_null(void)
+{
+ struct spdk_nvme_qpair qpair;
+ spdk_nvme_cmd_cb cb_fn = (spdk_nvme_cmd_cb)0x1234;
+ void *cb_arg = (void *)0x5678;
+ struct nvme_request *req = NULL;
+ struct nvme_request dummy_req;
+
+ STAILQ_INIT(&qpair.free_req);
+ STAILQ_INIT(&qpair.queued_req);
+
+ /*
+ * Put a dummy on the queue so we can make a request
+ * and confirm that what comes back is what we expect.
+ */
+ STAILQ_INSERT_HEAD(&qpair.free_req, &dummy_req, stailq);
+
+ req = nvme_allocate_request_null(&qpair, cb_fn, cb_arg);
+
+ /*
+ * Compare the req with the parmaters that we passed in
+ * as well as what the function is supposed to update.
+ */
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+ CU_ASSERT(req->cb_fn == cb_fn);
+ CU_ASSERT(req->cb_arg == cb_arg);
+ CU_ASSERT(req->pid == getpid());
+ CU_ASSERT(nvme_payload_type(&req->payload) == NVME_PAYLOAD_TYPE_CONTIG);
+ CU_ASSERT(req->payload.md == NULL);
+ CU_ASSERT(req->payload.contig_or_cb_arg == NULL);
+}
+
+static void
+test_nvme_allocate_request(void)
+{
+ struct spdk_nvme_qpair qpair;
+ struct nvme_payload payload;
+ uint32_t payload_struct_size = sizeof(payload);
+ spdk_nvme_cmd_cb cb_fn = (spdk_nvme_cmd_cb)0x1234;
+ void *cb_arg = (void *)0x6789;
+ struct nvme_request *req = NULL;
+ struct nvme_request dummy_req;
+
+ /* Fill the whole payload struct with a known pattern */
+ memset(&payload, 0x5a, payload_struct_size);
+ STAILQ_INIT(&qpair.free_req);
+ STAILQ_INIT(&qpair.queued_req);
+
+ /* Test trying to allocate a request when no requests are available */
+ req = nvme_allocate_request(&qpair, &payload, payload_struct_size,
+ cb_fn, cb_arg);
+ CU_ASSERT(req == NULL);
+
+ /* put a dummy on the queue, and then allocate one */
+ STAILQ_INSERT_HEAD(&qpair.free_req, &dummy_req, stailq);
+ req = nvme_allocate_request(&qpair, &payload, payload_struct_size,
+ cb_fn, cb_arg);
+
+ /* all the req elements should now match the passed in paramters */
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+ CU_ASSERT(req->cb_fn == cb_fn);
+ CU_ASSERT(req->cb_arg == cb_arg);
+ CU_ASSERT(memcmp(&req->payload, &payload, payload_struct_size) == 0);
+ CU_ASSERT(req->payload_size == payload_struct_size);
+ CU_ASSERT(req->qpair == &qpair);
+ CU_ASSERT(req->pid == getpid());
+}
+
+static void
+test_nvme_free_request(void)
+{
+ struct nvme_request match_req;
+ struct spdk_nvme_qpair qpair;
+ struct nvme_request *req;
+
+ /* put a req on the Q, take it off and compare */
+ memset(&match_req.cmd, 0x5a, sizeof(struct spdk_nvme_cmd));
+ match_req.qpair = &qpair;
+ /* the code under tests asserts this condition */
+ match_req.num_children = 0;
+ STAILQ_INIT(&qpair.free_req);
+
+ nvme_free_request(&match_req);
+ req = STAILQ_FIRST(&match_req.qpair->free_req);
+ CU_ASSERT(req == &match_req);
+}
+
+static void
+test_nvme_allocate_request_user_copy(void)
+{
+ struct spdk_nvme_qpair qpair;
+ spdk_nvme_cmd_cb cb_fn = (spdk_nvme_cmd_cb)0x12345;
+ void *cb_arg = (void *)0x12345;
+ bool host_to_controller = true;
+ struct nvme_request *req;
+ struct nvme_request dummy_req;
+ int test_data = 0xdeadbeef;
+ void *buffer = NULL;
+ uint32_t payload_size = sizeof(int);
+
+ STAILQ_INIT(&qpair.free_req);
+ STAILQ_INIT(&qpair.queued_req);
+
+ /* no buffer or valid payload size, early NULL return */
+ req = nvme_allocate_request_user_copy(&qpair, buffer, payload_size, cb_fn,
+ cb_arg, host_to_controller);
+ CU_ASSERT(req == NULL);
+
+ /* good buffer and valid payload size */
+ buffer = malloc(payload_size);
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+ memcpy(buffer, &test_data, payload_size);
+
+ /* put a dummy on the queue */
+ STAILQ_INSERT_HEAD(&qpair.free_req, &dummy_req, stailq);
+
+ MOCK_CLEAR(spdk_malloc)
+ MOCK_CLEAR(spdk_zmalloc)
+ req = nvme_allocate_request_user_copy(&qpair, buffer, payload_size, cb_fn,
+ cb_arg, host_to_controller);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+ CU_ASSERT(req->user_cb_fn == cb_fn);
+ CU_ASSERT(req->user_cb_arg == cb_arg);
+ CU_ASSERT(req->user_buffer == buffer);
+ CU_ASSERT(req->cb_arg == req);
+ CU_ASSERT(memcmp(req->payload.contig_or_cb_arg, buffer, payload_size) == 0);
+ spdk_dma_free(req->payload.contig_or_cb_arg);
+
+ /* same thing but additional path coverage, no copy */
+ host_to_controller = false;
+ STAILQ_INSERT_HEAD(&qpair.free_req, &dummy_req, stailq);
+
+ req = nvme_allocate_request_user_copy(&qpair, buffer, payload_size, cb_fn,
+ cb_arg, host_to_controller);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+ CU_ASSERT(req->user_cb_fn == cb_fn);
+ CU_ASSERT(req->user_cb_arg == cb_arg);
+ CU_ASSERT(req->user_buffer == buffer);
+ CU_ASSERT(req->cb_arg == req);
+ CU_ASSERT(memcmp(req->payload.contig_or_cb_arg, buffer, payload_size) != 0);
+ spdk_dma_free(req->payload.contig_or_cb_arg);
+
+ /* good buffer and valid payload size but make spdk_dma_zmalloc fail */
+ /* set the mock pointer to NULL for spdk_dma_zmalloc */
+ MOCK_SET(spdk_dma_zmalloc, NULL);
+ req = nvme_allocate_request_user_copy(&qpair, buffer, payload_size, cb_fn,
+ cb_arg, host_to_controller);
+ CU_ASSERT(req == NULL);
+ free(buffer);
+ MOCK_CLEAR(spdk_dma_zmalloc);
+}
+
+static void
+test_nvme_ctrlr_probe(void)
+{
+ int rc = 0;
+ struct spdk_nvme_ctrlr ctrlr = {};
+ const struct spdk_nvme_transport_id trid = {};
+ void *devhandle = NULL;
+ void *cb_ctx = NULL;
+ struct spdk_nvme_ctrlr *dummy = NULL;
+
+ /* test when probe_cb returns false */
+ MOCK_SET(dummy_probe_cb, false);
+ rc = nvme_ctrlr_probe(&trid, devhandle, dummy_probe_cb, cb_ctx);
+ CU_ASSERT(rc == 1);
+
+ /* probe_cb returns true but we can't construct a ctrl */
+ MOCK_SET(dummy_probe_cb, true);
+ MOCK_SET(nvme_transport_ctrlr_construct, NULL);
+ rc = nvme_ctrlr_probe(&trid, devhandle, dummy_probe_cb, cb_ctx);
+ CU_ASSERT(rc == -1);
+
+ /* happy path */
+ g_spdk_nvme_driver = malloc(sizeof(struct nvme_driver));
+ SPDK_CU_ASSERT_FATAL(g_spdk_nvme_driver != NULL);
+ MOCK_SET(dummy_probe_cb, true);
+ MOCK_SET(nvme_transport_ctrlr_construct, &ctrlr);
+ TAILQ_INIT(&g_nvme_init_ctrlrs);
+ rc = nvme_ctrlr_probe(&trid, devhandle, dummy_probe_cb, cb_ctx);
+ CU_ASSERT(rc == 0);
+ dummy = TAILQ_FIRST(&g_nvme_init_ctrlrs);
+ CU_ASSERT(dummy == ut_nvme_transport_ctrlr_construct);
+ TAILQ_REMOVE(&g_nvme_init_ctrlrs, dummy, tailq);
+ MOCK_CLEAR_P(nvme_transport_ctrlr_construct);
+
+ free(g_spdk_nvme_driver);
+}
+
+static void
+test_nvme_robust_mutex_init_shared(void)
+{
+ pthread_mutex_t mtx;
+ int rc = 0;
+
+ /* test where both pthread calls succeed */
+ MOCK_SET(pthread_mutexattr_init, 0);
+ MOCK_SET(pthread_mutex_init, 0);
+ rc = nvme_robust_mutex_init_shared(&mtx);
+ CU_ASSERT(rc == 0);
+
+ /* test where we can't init attr's but init mutex works */
+ MOCK_SET(pthread_mutexattr_init, -1);
+ MOCK_SET(pthread_mutex_init, 0);
+ rc = nvme_robust_mutex_init_shared(&mtx);
+ /* for FreeBSD the only possible return value is 0 */
+#ifndef __FreeBSD__
+ CU_ASSERT(rc != 0);
+#else
+ CU_ASSERT(rc == 0);
+#endif
+
+ /* test where we can init attr's but the mutex init fails */
+ MOCK_SET(pthread_mutexattr_init, 0);
+ MOCK_SET(pthread_mutex_init, -1);
+ rc = nvme_robust_mutex_init_shared(&mtx);
+ /* for FreeBSD the only possible return value is 0 */
+#ifndef __FreeBSD__
+ CU_ASSERT(rc != 0);
+#else
+ CU_ASSERT(rc == 0);
+#endif
+}
+
+static void
+test_opc_data_transfer(void)
+{
+ enum spdk_nvme_data_transfer xfer;
+
+ xfer = spdk_nvme_opc_get_data_transfer(SPDK_NVME_OPC_FLUSH);
+ CU_ASSERT(xfer == SPDK_NVME_DATA_NONE);
+
+ xfer = spdk_nvme_opc_get_data_transfer(SPDK_NVME_OPC_WRITE);
+ CU_ASSERT(xfer == SPDK_NVME_DATA_HOST_TO_CONTROLLER);
+
+ xfer = spdk_nvme_opc_get_data_transfer(SPDK_NVME_OPC_READ);
+ CU_ASSERT(xfer == SPDK_NVME_DATA_CONTROLLER_TO_HOST);
+
+ xfer = spdk_nvme_opc_get_data_transfer(SPDK_NVME_OPC_GET_LOG_PAGE);
+ CU_ASSERT(xfer == SPDK_NVME_DATA_CONTROLLER_TO_HOST);
+}
+
+static void
+test_trid_parse_and_compare(void)
+{
+ struct spdk_nvme_transport_id trid1, trid2;
+ int ret;
+
+ /* set trid1 trid2 value to id parse */
+ ret = spdk_nvme_transport_id_parse(NULL, "trtype:PCIe traddr:0000:04:00.0");
+ CU_ASSERT(ret == -EINVAL);
+ memset(&trid1, 0, sizeof(trid1));
+ ret = spdk_nvme_transport_id_parse(&trid1, NULL);
+ CU_ASSERT(ret == -EINVAL);
+ ret = spdk_nvme_transport_id_parse(NULL, NULL);
+ CU_ASSERT(ret == -EINVAL);
+ memset(&trid1, 0, sizeof(trid1));
+ ret = spdk_nvme_transport_id_parse(&trid1, "trtype-PCIe traddr-0000-04-00.0");
+ CU_ASSERT(ret == -EINVAL);
+ memset(&trid1, 0, sizeof(trid1));
+ ret = spdk_nvme_transport_id_parse(&trid1, "trtype-PCIe traddr-0000-04-00.0-:");
+ CU_ASSERT(ret == -EINVAL);
+ memset(&trid1, 0, sizeof(trid1));
+ ret = spdk_nvme_transport_id_parse(&trid1, " \t\n:");
+ CU_ASSERT(ret == -EINVAL);
+ memset(&trid1, 0, sizeof(trid1));
+ CU_ASSERT(spdk_nvme_transport_id_parse(&trid1,
+ "trtype:rdma\n"
+ "adrfam:ipv4\n"
+ "traddr:192.168.100.8\n"
+ "trsvcid:4420\n"
+ "subnqn:nqn.2014-08.org.nvmexpress.discovery") == 0);
+ CU_ASSERT(trid1.trtype == SPDK_NVME_TRANSPORT_RDMA);
+ CU_ASSERT(trid1.adrfam == SPDK_NVMF_ADRFAM_IPV4);
+ CU_ASSERT(strcmp(trid1.traddr, "192.168.100.8") == 0);
+ CU_ASSERT(strcmp(trid1.trsvcid, "4420") == 0);
+ CU_ASSERT(strcmp(trid1.subnqn, "nqn.2014-08.org.nvmexpress.discovery") == 0);
+
+ memset(&trid2, 0, sizeof(trid2));
+ CU_ASSERT(spdk_nvme_transport_id_parse(&trid2, "trtype:PCIe traddr:0000:04:00.0") == 0);
+ CU_ASSERT(trid2.trtype == SPDK_NVME_TRANSPORT_PCIE);
+ CU_ASSERT(strcmp(trid2.traddr, "0000:04:00.0") == 0);
+
+ CU_ASSERT(spdk_nvme_transport_id_compare(&trid1, &trid2) != 0);
+
+ /* set trid1 trid2 and test id_compare */
+ memset_trid(&trid1, &trid2);
+ trid1.adrfam = SPDK_NVMF_ADRFAM_IPV6;
+ trid2.adrfam = SPDK_NVMF_ADRFAM_IPV4;
+ ret = spdk_nvme_transport_id_compare(&trid1, &trid2);
+ CU_ASSERT(ret > 0);
+
+ memset_trid(&trid1, &trid2);
+ snprintf(trid1.traddr, sizeof(trid1.traddr), "192.168.100.8");
+ snprintf(trid2.traddr, sizeof(trid2.traddr), "192.168.100.9");
+ ret = spdk_nvme_transport_id_compare(&trid1, &trid2);
+ CU_ASSERT(ret < 0);
+
+ memset_trid(&trid1, &trid2);
+ snprintf(trid1.trsvcid, sizeof(trid1.trsvcid), "4420");
+ snprintf(trid2.trsvcid, sizeof(trid2.trsvcid), "4421");
+ ret = spdk_nvme_transport_id_compare(&trid1, &trid2);
+ CU_ASSERT(ret < 0);
+
+ memset_trid(&trid1, &trid2);
+ snprintf(trid1.subnqn, sizeof(trid1.subnqn), "subnqn:nqn.2016-08.org.nvmexpress.discovery");
+ snprintf(trid2.subnqn, sizeof(trid2.subnqn), "subnqn:nqn.2017-08.org.nvmexpress.discovery");
+ ret = spdk_nvme_transport_id_compare(&trid1, &trid2);
+ CU_ASSERT(ret < 0);
+
+ memset_trid(&trid1, &trid2);
+ snprintf(trid1.subnqn, sizeof(trid1.subnqn), "subnqn:nqn.2016-08.org.nvmexpress.discovery");
+ snprintf(trid2.subnqn, sizeof(trid2.subnqn), "subnqn:nqn.2016-08.org.nvmexpress.discovery");
+ ret = spdk_nvme_transport_id_compare(&trid1, &trid2);
+ CU_ASSERT(ret == 0);
+
+ memset_trid(&trid1, &trid2);
+ snprintf(trid1.subnqn, sizeof(trid1.subnqn), "subnqn:nqn.2016-08.org.nvmexpress.discovery");
+ snprintf(trid2.subnqn, sizeof(trid2.subnqn), "subnqn:nqn.2016-08.org.Nvmexpress.discovery");
+ ret = spdk_nvme_transport_id_compare(&trid1, &trid2);
+ CU_ASSERT(ret > 0);
+
+ memset_trid(&trid1, &trid2);
+ ret = spdk_nvme_transport_id_compare(&trid1, &trid2);
+ CU_ASSERT(ret == 0);
+
+ /* Compare PCI addresses via spdk_pci_addr_compare (rather than as strings) */
+ memset_trid(&trid1, &trid2);
+ CU_ASSERT(spdk_nvme_transport_id_parse(&trid1, "trtype:PCIe traddr:0000:04:00.0") == 0);
+ CU_ASSERT(spdk_nvme_transport_id_parse(&trid2, "trtype:PCIe traddr:04:00.0") == 0);
+ CU_ASSERT(spdk_nvme_transport_id_compare(&trid1, &trid2) == 0);
+
+ memset_trid(&trid1, &trid2);
+ CU_ASSERT(spdk_nvme_transport_id_parse(&trid1, "trtype:PCIe traddr:0000:05:00.0") == 0);
+ CU_ASSERT(spdk_nvme_transport_id_parse(&trid2, "trtype:PCIe traddr:04:00.0") == 0);
+ CU_ASSERT(spdk_nvme_transport_id_compare(&trid1, &trid2) > 0);
+
+ memset_trid(&trid1, &trid2);
+ CU_ASSERT(spdk_nvme_transport_id_parse(&trid1, "trtype:PCIe traddr:0000:04:00.0") == 0);
+ CU_ASSERT(spdk_nvme_transport_id_parse(&trid2, "trtype:PCIe traddr:05:00.0") == 0);
+ CU_ASSERT(spdk_nvme_transport_id_compare(&trid1, &trid2) < 0);
+
+ memset_trid(&trid1, &trid2);
+ CU_ASSERT(spdk_nvme_transport_id_parse(&trid1, "trtype=PCIe traddr=0000:04:00.0") == 0);
+ CU_ASSERT(spdk_nvme_transport_id_parse(&trid2, "trtype=PCIe traddr=05:00.0") == 0);
+ CU_ASSERT(spdk_nvme_transport_id_compare(&trid1, &trid2) < 0);
+}
+
+static void
+test_spdk_nvme_transport_id_parse_trtype(void)
+{
+
+ enum spdk_nvme_transport_type *trtype;
+ enum spdk_nvme_transport_type sct;
+ char *str;
+
+ trtype = NULL;
+ str = "unit_test";
+
+ /* test function returned value when trtype is NULL but str not NULL */
+ CU_ASSERT(spdk_nvme_transport_id_parse_trtype(trtype, str) == (-EINVAL));
+
+ /* test function returned value when str is NULL but trtype not NULL */
+ trtype = &sct;
+ str = NULL;
+ CU_ASSERT(spdk_nvme_transport_id_parse_trtype(trtype, str) == (-EINVAL));
+
+ /* test function returned value when str and strtype not NULL, but str value
+ * not "PCIe" or "RDMA" */
+ str = "unit_test";
+ CU_ASSERT(spdk_nvme_transport_id_parse_trtype(trtype, str) == (-ENOENT));
+
+ /* test trtype value when use function "strcasecmp" to compare str and "PCIe",not case-sensitive */
+ str = "PCIe";
+ spdk_nvme_transport_id_parse_trtype(trtype, str);
+ CU_ASSERT((*trtype) == SPDK_NVME_TRANSPORT_PCIE);
+
+ str = "pciE";
+ spdk_nvme_transport_id_parse_trtype(trtype, str);
+ CU_ASSERT((*trtype) == SPDK_NVME_TRANSPORT_PCIE);
+
+ /* test trtype value when use function "strcasecmp" to compare str and "RDMA",not case-sensitive */
+ str = "RDMA";
+ spdk_nvme_transport_id_parse_trtype(trtype, str);
+ CU_ASSERT((*trtype) == SPDK_NVME_TRANSPORT_RDMA);
+
+ str = "rdma";
+ spdk_nvme_transport_id_parse_trtype(trtype, str);
+ CU_ASSERT((*trtype) == SPDK_NVME_TRANSPORT_RDMA);
+
+}
+
+static void
+test_spdk_nvme_transport_id_parse_adrfam(void)
+{
+
+ enum spdk_nvmf_adrfam *adrfam;
+ enum spdk_nvmf_adrfam sct;
+ char *str;
+
+ adrfam = NULL;
+ str = "unit_test";
+
+ /* test function returned value when adrfam is NULL but str not NULL */
+ CU_ASSERT(spdk_nvme_transport_id_parse_adrfam(adrfam, str) == (-EINVAL));
+
+ /* test function returned value when str is NULL but adrfam not NULL */
+ adrfam = &sct;
+ str = NULL;
+ CU_ASSERT(spdk_nvme_transport_id_parse_adrfam(adrfam, str) == (-EINVAL));
+
+ /* test function returned value when str and adrfam not NULL, but str value
+ * not "IPv4" or "IPv6" or "IB" or "FC" */
+ str = "unit_test";
+ CU_ASSERT(spdk_nvme_transport_id_parse_adrfam(adrfam, str) == (-ENOENT));
+
+ /* test adrfam value when use function "strcasecmp" to compare str and "IPv4",not case-sensitive */
+ str = "IPv4";
+ spdk_nvme_transport_id_parse_adrfam(adrfam, str);
+ CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IPV4);
+
+ str = "ipV4";
+ spdk_nvme_transport_id_parse_adrfam(adrfam, str);
+ CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IPV4);
+
+ /* test adrfam value when use function "strcasecmp" to compare str and "IPv6",not case-sensitive */
+ str = "IPv6";
+ spdk_nvme_transport_id_parse_adrfam(adrfam, str);
+ CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IPV6);
+
+ str = "ipV6";
+ spdk_nvme_transport_id_parse_adrfam(adrfam, str);
+ CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IPV6);
+
+ /* test adrfam value when use function "strcasecmp" to compare str and "IB",not case-sensitive */
+ str = "IB";
+ spdk_nvme_transport_id_parse_adrfam(adrfam, str);
+ CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IB);
+
+ str = "ib";
+ spdk_nvme_transport_id_parse_adrfam(adrfam, str);
+ CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_IB);
+
+ /* test adrfam value when use function "strcasecmp" to compare str and "FC",not case-sensitive */
+ str = "FC";
+ spdk_nvme_transport_id_parse_adrfam(adrfam, str);
+ CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_FC);
+
+ str = "fc";
+ spdk_nvme_transport_id_parse_adrfam(adrfam, str);
+ CU_ASSERT((*adrfam) == SPDK_NVMF_ADRFAM_FC);
+
+}
+
+static void
+test_trid_trtype_str(void)
+{
+ const char *s;
+
+ s = spdk_nvme_transport_id_trtype_str(-5);
+ CU_ASSERT(s == NULL);
+
+ s = spdk_nvme_transport_id_trtype_str(SPDK_NVME_TRANSPORT_PCIE);
+ SPDK_CU_ASSERT_FATAL(s != NULL);
+ CU_ASSERT(strcmp(s, "PCIe") == 0);
+
+ s = spdk_nvme_transport_id_trtype_str(SPDK_NVME_TRANSPORT_RDMA);
+ SPDK_CU_ASSERT_FATAL(s != NULL);
+ CU_ASSERT(strcmp(s, "RDMA") == 0);
+}
+
+static void
+test_trid_adrfam_str(void)
+{
+ const char *s;
+
+ s = spdk_nvme_transport_id_adrfam_str(-5);
+ CU_ASSERT(s == NULL);
+
+ s = spdk_nvme_transport_id_adrfam_str(SPDK_NVMF_ADRFAM_IPV4);
+ SPDK_CU_ASSERT_FATAL(s != NULL);
+ CU_ASSERT(strcmp(s, "IPv4") == 0);
+
+ s = spdk_nvme_transport_id_adrfam_str(SPDK_NVMF_ADRFAM_IPV6);
+ SPDK_CU_ASSERT_FATAL(s != NULL);
+ CU_ASSERT(strcmp(s, "IPv6") == 0);
+
+ s = spdk_nvme_transport_id_adrfam_str(SPDK_NVMF_ADRFAM_IB);
+ SPDK_CU_ASSERT_FATAL(s != NULL);
+ CU_ASSERT(strcmp(s, "IB") == 0);
+
+ s = spdk_nvme_transport_id_adrfam_str(SPDK_NVMF_ADRFAM_FC);
+ SPDK_CU_ASSERT_FATAL(s != NULL);
+ CU_ASSERT(strcmp(s, "FC") == 0);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvme", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test_opc_data_transfer",
+ test_opc_data_transfer) == NULL ||
+ CU_add_test(suite, "test_spdk_nvme_transport_id_parse_trtype",
+ test_spdk_nvme_transport_id_parse_trtype) == NULL ||
+ CU_add_test(suite, "test_spdk_nvme_transport_id_parse_adrfam",
+ test_spdk_nvme_transport_id_parse_adrfam) == NULL ||
+ CU_add_test(suite, "test_trid_parse_and_compare",
+ test_trid_parse_and_compare) == NULL ||
+ CU_add_test(suite, "test_trid_trtype_str",
+ test_trid_trtype_str) == NULL ||
+ CU_add_test(suite, "test_trid_adrfam_str",
+ test_trid_adrfam_str) == NULL ||
+ CU_add_test(suite, "test_nvme_ctrlr_probe",
+ test_nvme_ctrlr_probe) == NULL ||
+ CU_add_test(suite, "test_spdk_nvme_probe",
+ test_spdk_nvme_probe) == NULL ||
+ CU_add_test(suite, "test_spdk_nvme_connect",
+ test_spdk_nvme_connect) == NULL ||
+ CU_add_test(suite, "test_nvme_init_controllers",
+ test_nvme_init_controllers) == NULL ||
+ CU_add_test(suite, "test_nvme_driver_init",
+ test_nvme_driver_init) == NULL ||
+ CU_add_test(suite, "test_spdk_nvme_detach",
+ test_spdk_nvme_detach) == NULL ||
+ CU_add_test(suite, "test_nvme_completion_poll_cb",
+ test_nvme_completion_poll_cb) == NULL ||
+ CU_add_test(suite, "test_nvme_user_copy_cmd_complete",
+ test_nvme_user_copy_cmd_complete) == NULL ||
+ CU_add_test(suite, "test_nvme_allocate_request_null",
+ test_nvme_allocate_request_null) == NULL ||
+ CU_add_test(suite, "test_nvme_allocate_request",
+ test_nvme_allocate_request) == NULL ||
+ CU_add_test(suite, "test_nvme_free_request",
+ test_nvme_free_request) == NULL ||
+ CU_add_test(suite, "test_nvme_allocate_request_user_copy",
+ test_nvme_allocate_request_user_copy) == NULL ||
+ CU_add_test(suite, "test_nvme_robust_mutex_init_shared",
+ test_nvme_robust_mutex_init_shared) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/.gitignore
new file mode 100644
index 00000000..97a75bee
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/.gitignore
@@ -0,0 +1 @@
+nvme_ctrlr_ut
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/Makefile
new file mode 100644
index 00000000..3ce33dc4
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = nvme_ctrlr_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/nvme_ctrlr_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/nvme_ctrlr_ut.c
new file mode 100644
index 00000000..db7469ff
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr.c/nvme_ctrlr_ut.c
@@ -0,0 +1,1795 @@
+/*-
+ * 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_cunit.h"
+
+#include "spdk_internal/log.h"
+
+#include "common/lib/test_env.c"
+
+struct spdk_trace_flag SPDK_LOG_NVME = {
+ .name = "nvme",
+ .enabled = false,
+};
+
+#include "nvme/nvme_ctrlr.c"
+#include "nvme/nvme_quirks.c"
+
+pid_t g_spdk_nvme_pid;
+
+struct nvme_driver _g_nvme_driver = {
+ .lock = PTHREAD_MUTEX_INITIALIZER,
+};
+
+struct nvme_driver *g_spdk_nvme_driver = &_g_nvme_driver;
+
+struct spdk_nvme_registers g_ut_nvme_regs = {};
+
+__thread int nvme_thread_ioq_index = -1;
+
+uint32_t set_size = 1;
+
+int set_status_cpl = -1;
+
+DEFINE_STUB(nvme_ctrlr_cmd_set_host_id, int,
+ (struct spdk_nvme_ctrlr *ctrlr, void *host_id, uint32_t host_id_size,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg), 0);
+DEFINE_STUB(nvme_ctrlr_identify_ns, int, (struct spdk_nvme_ns *ns), 0);
+DEFINE_STUB(nvme_ctrlr_identify_id_desc, int, (struct spdk_nvme_ns *ns), 0);
+DEFINE_STUB_V(nvme_ns_set_identify_data, (struct spdk_nvme_ns *ns));
+
+struct spdk_nvme_ctrlr *nvme_transport_ctrlr_construct(const struct spdk_nvme_transport_id *trid,
+ const struct spdk_nvme_ctrlr_opts *opts,
+ void *devhandle)
+{
+ return NULL;
+}
+
+int
+nvme_transport_ctrlr_destruct(struct spdk_nvme_ctrlr *ctrlr)
+{
+ nvme_ctrlr_destruct_finish(ctrlr);
+
+ return 0;
+}
+
+int
+nvme_transport_ctrlr_enable(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return 0;
+}
+
+int
+nvme_transport_ctrlr_set_reg_4(struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, uint32_t value)
+{
+ SPDK_CU_ASSERT_FATAL(offset <= sizeof(struct spdk_nvme_registers) - 4);
+ *(uint32_t *)((uintptr_t)&g_ut_nvme_regs + offset) = value;
+ return 0;
+}
+
+int
+nvme_transport_ctrlr_set_reg_8(struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, uint64_t value)
+{
+ SPDK_CU_ASSERT_FATAL(offset <= sizeof(struct spdk_nvme_registers) - 8);
+ *(uint64_t *)((uintptr_t)&g_ut_nvme_regs + offset) = value;
+ return 0;
+}
+
+int
+nvme_transport_ctrlr_get_reg_4(struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, uint32_t *value)
+{
+ SPDK_CU_ASSERT_FATAL(offset <= sizeof(struct spdk_nvme_registers) - 4);
+ *value = *(uint32_t *)((uintptr_t)&g_ut_nvme_regs + offset);
+ return 0;
+}
+
+int
+nvme_transport_ctrlr_get_reg_8(struct spdk_nvme_ctrlr *ctrlr, uint32_t offset, uint64_t *value)
+{
+ SPDK_CU_ASSERT_FATAL(offset <= sizeof(struct spdk_nvme_registers) - 8);
+ *value = *(uint64_t *)((uintptr_t)&g_ut_nvme_regs + offset);
+ return 0;
+}
+
+uint32_t
+nvme_transport_ctrlr_get_max_xfer_size(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return UINT32_MAX;
+}
+
+uint16_t
+nvme_transport_ctrlr_get_max_sges(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return 1;
+}
+
+void *
+nvme_transport_ctrlr_alloc_cmb_io_buffer(struct spdk_nvme_ctrlr *ctrlr, size_t size)
+{
+ return NULL;
+}
+
+int
+nvme_transport_ctrlr_free_cmb_io_buffer(struct spdk_nvme_ctrlr *ctrlr, void *buf, size_t size)
+{
+ return 0;
+}
+
+struct spdk_nvme_qpair *
+nvme_transport_ctrlr_create_io_qpair(struct spdk_nvme_ctrlr *ctrlr, uint16_t qid,
+ const struct spdk_nvme_io_qpair_opts *opts)
+{
+ struct spdk_nvme_qpair *qpair;
+
+ qpair = calloc(1, sizeof(*qpair));
+ SPDK_CU_ASSERT_FATAL(qpair != NULL);
+
+ qpair->ctrlr = ctrlr;
+ qpair->id = qid;
+ qpair->qprio = opts->qprio;
+
+ return qpair;
+}
+
+int
+nvme_transport_ctrlr_delete_io_qpair(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_qpair *qpair)
+{
+ free(qpair);
+ return 0;
+}
+
+int
+nvme_transport_ctrlr_reinit_io_qpair(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_qpair *qpair)
+{
+ return 0;
+}
+
+int
+nvme_transport_qpair_reset(struct spdk_nvme_qpair *qpair)
+{
+ return 0;
+}
+
+int
+nvme_driver_init(void)
+{
+ return 0;
+}
+
+int nvme_qpair_init(struct spdk_nvme_qpair *qpair, uint16_t id,
+ struct spdk_nvme_ctrlr *ctrlr,
+ enum spdk_nvme_qprio qprio,
+ uint32_t num_requests)
+{
+ qpair->id = id;
+ qpair->qprio = qprio;
+ qpair->ctrlr = ctrlr;
+
+ return 0;
+}
+
+static void
+fake_cpl_success(spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ struct spdk_nvme_cpl cpl = {};
+
+ cpl.status.sc = SPDK_NVME_SC_SUCCESS;
+ cb_fn(cb_arg, &cpl);
+}
+
+int
+spdk_nvme_ctrlr_cmd_set_feature(struct spdk_nvme_ctrlr *ctrlr, uint8_t feature,
+ uint32_t cdw11, uint32_t cdw12, void *payload, uint32_t payload_size,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ CU_ASSERT(0);
+ return -1;
+}
+
+int
+spdk_nvme_ctrlr_cmd_get_feature(struct spdk_nvme_ctrlr *ctrlr, uint8_t feature,
+ uint32_t cdw11, void *payload, uint32_t payload_size,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ CU_ASSERT(0);
+ return -1;
+}
+
+int
+spdk_nvme_ctrlr_cmd_get_log_page(struct spdk_nvme_ctrlr *ctrlr, uint8_t log_page,
+ uint32_t nsid, void *payload, uint32_t payload_size,
+ uint64_t offset, spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ fake_cpl_success(cb_fn, cb_arg);
+ return 0;
+}
+
+int
+nvme_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_ASYNC_EVENT_REQUEST);
+
+ /*
+ * For the purposes of this unit test, we don't need to bother emulating request submission.
+ */
+
+ return 0;
+}
+
+int32_t
+spdk_nvme_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions)
+{
+ return 0;
+}
+
+void
+nvme_qpair_disable(struct spdk_nvme_qpair *qpair)
+{
+}
+
+void
+nvme_qpair_enable(struct spdk_nvme_qpair *qpair)
+{
+}
+
+void
+nvme_completion_poll_cb(void *arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct nvme_completion_poll_status *status = arg;
+
+ status->cpl = *cpl;
+ status->done = true;
+}
+
+int
+spdk_nvme_wait_for_completion_robust_lock(
+ struct spdk_nvme_qpair *qpair,
+ struct nvme_completion_poll_status *status,
+ pthread_mutex_t *robust_mutex)
+{
+ status->done = true;
+ memset(&status->cpl, 0, sizeof(status->cpl));
+ status->cpl.status.sc = 0;
+ if (set_status_cpl == 1) {
+ status->cpl.status.sc = 1;
+ }
+ return spdk_nvme_cpl_is_error(&status->cpl) ? -EIO : 0;
+}
+
+int
+spdk_nvme_wait_for_completion(struct spdk_nvme_qpair *qpair,
+ struct nvme_completion_poll_status *status)
+{
+ return spdk_nvme_wait_for_completion_robust_lock(qpair, status, NULL);
+}
+
+
+int
+nvme_ctrlr_cmd_set_async_event_config(struct spdk_nvme_ctrlr *ctrlr,
+ union spdk_nvme_feat_async_event_configuration config, spdk_nvme_cmd_cb cb_fn,
+ void *cb_arg)
+{
+ fake_cpl_success(cb_fn, cb_arg);
+ return 0;
+}
+
+int
+nvme_ctrlr_cmd_identify(struct spdk_nvme_ctrlr *ctrlr, uint8_t cns, uint16_t cntid, uint32_t nsid,
+ void *payload, size_t payload_size,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ if (cns == SPDK_NVME_IDENTIFY_ACTIVE_NS_LIST) {
+ uint32_t count = 0;
+ uint32_t i = 0;
+ struct spdk_nvme_ns_list *ns_list = (struct spdk_nvme_ns_list *)payload;
+
+ for (i = 1; i <= ctrlr->num_ns; i++) {
+ if (i <= nsid) {
+ continue;
+ }
+
+ ns_list->ns_list[count++] = i;
+ if (count == SPDK_COUNTOF(ns_list->ns_list)) {
+ break;
+ }
+ }
+
+ }
+ fake_cpl_success(cb_fn, cb_arg);
+ return 0;
+}
+
+int
+nvme_ctrlr_cmd_set_num_queues(struct spdk_nvme_ctrlr *ctrlr,
+ uint32_t num_queues, spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ fake_cpl_success(cb_fn, cb_arg);
+ return 0;
+}
+
+int
+nvme_ctrlr_cmd_get_num_queues(struct spdk_nvme_ctrlr *ctrlr,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ fake_cpl_success(cb_fn, cb_arg);
+ return 0;
+}
+
+int
+nvme_ctrlr_cmd_attach_ns(struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid,
+ struct spdk_nvme_ctrlr_list *payload, spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ return 0;
+}
+
+int
+nvme_ctrlr_cmd_detach_ns(struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid,
+ struct spdk_nvme_ctrlr_list *payload, spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ return 0;
+}
+
+int
+nvme_ctrlr_cmd_create_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns_data *payload,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ return 0;
+}
+
+int
+nvme_ctrlr_cmd_delete_ns(struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid, spdk_nvme_cmd_cb cb_fn,
+ void *cb_arg)
+{
+ return 0;
+}
+
+int
+nvme_ctrlr_cmd_format(struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid, struct spdk_nvme_format *format,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ return 0;
+}
+
+int
+nvme_ctrlr_cmd_fw_commit(struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_fw_commit *fw_commit,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ CU_ASSERT(fw_commit->ca == SPDK_NVME_FW_COMMIT_REPLACE_IMG);
+ if (fw_commit->fs == 0) {
+ return -1;
+ }
+ set_status_cpl = 1;
+ if (ctrlr->is_resetting == true) {
+ set_status_cpl = 0;
+ }
+ return 0;
+}
+
+int
+nvme_ctrlr_cmd_fw_image_download(struct spdk_nvme_ctrlr *ctrlr,
+ uint32_t size, uint32_t offset, void *payload,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ if ((size != 0 && payload == NULL) || (size == 0 && payload != NULL)) {
+ return -1;
+ }
+ CU_ASSERT(offset == 0);
+ return 0;
+}
+
+void
+nvme_ns_destruct(struct spdk_nvme_ns *ns)
+{
+}
+
+int
+nvme_ns_construct(struct spdk_nvme_ns *ns, uint32_t id,
+ struct spdk_nvme_ctrlr *ctrlr)
+{
+ return 0;
+}
+
+#define DECLARE_AND_CONSTRUCT_CTRLR() \
+ struct spdk_nvme_ctrlr ctrlr = {}; \
+ struct spdk_nvme_qpair adminq = {}; \
+ struct nvme_request req; \
+ \
+ STAILQ_INIT(&adminq.free_req); \
+ STAILQ_INSERT_HEAD(&adminq.free_req, &req, stailq); \
+ ctrlr.adminq = &adminq;
+
+static void
+test_nvme_ctrlr_init_en_1_rdy_0(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs));
+
+ /*
+ * Initial state: CC.EN = 1, CSTS.RDY = 0
+ */
+ g_ut_nvme_regs.cc.bits.en = 1;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_1);
+
+ /*
+ * Transition to CSTS.RDY = 1.
+ * init() should set CC.EN = 0.
+ */
+ g_ut_nvme_regs.csts.bits.rdy = 1;
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0);
+
+ /*
+ * Transition to CSTS.RDY = 0.
+ */
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+
+ /*
+ * Transition to CC.EN = 1
+ */
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+
+ /*
+ * Transition to CSTS.RDY = 1.
+ */
+ g_ut_nvme_regs.csts.bits.rdy = 1;
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE);
+
+ /*
+ * Transition to READY.
+ */
+ while (ctrlr.state != NVME_CTRLR_STATE_READY) {
+ nvme_ctrlr_process_init(&ctrlr);
+ }
+
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+}
+
+static void
+test_nvme_ctrlr_init_en_1_rdy_1(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs));
+
+ /*
+ * Initial state: CC.EN = 1, CSTS.RDY = 1
+ * init() should set CC.EN = 0.
+ */
+ g_ut_nvme_regs.cc.bits.en = 1;
+ g_ut_nvme_regs.csts.bits.rdy = 1;
+
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0);
+
+ /*
+ * Transition to CSTS.RDY = 0.
+ */
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+
+ /*
+ * Transition to CC.EN = 1
+ */
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+
+ /*
+ * Transition to CSTS.RDY = 1.
+ */
+ g_ut_nvme_regs.csts.bits.rdy = 1;
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE);
+
+ /*
+ * Transition to READY.
+ */
+ while (ctrlr.state != NVME_CTRLR_STATE_READY) {
+ nvme_ctrlr_process_init(&ctrlr);
+ }
+
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+}
+
+static void
+test_nvme_ctrlr_init_en_0_rdy_0_ams_rr(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs));
+
+ /*
+ * Initial state: CC.EN = 0, CSTS.RDY = 0
+ * init() should set CC.EN = 1.
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Default round robin enabled
+ */
+ g_ut_nvme_regs.cap.bits.ams = 0x0;
+ ctrlr.cap = g_ut_nvme_regs.cap;
+
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ /*
+ * Case 1: default round robin arbitration mechanism selected
+ */
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_RR;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_RR);
+ CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_RR);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 2: weighted round robin arbitration mechanism selected
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_WRR;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 3: vendor specific arbitration mechanism selected
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 4: invalid arbitration mechanism selected
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS + 1;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 5: reset to default round robin arbitration mechanism
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_RR;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_RR);
+ CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_RR);
+
+ /*
+ * Transition to CSTS.RDY = 1.
+ */
+ g_ut_nvme_regs.csts.bits.rdy = 1;
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE);
+
+ /*
+ * Transition to READY.
+ */
+ while (ctrlr.state != NVME_CTRLR_STATE_READY) {
+ nvme_ctrlr_process_init(&ctrlr);
+ }
+
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+}
+
+static void
+test_nvme_ctrlr_init_en_0_rdy_0_ams_wrr(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs));
+
+ /*
+ * Initial state: CC.EN = 0, CSTS.RDY = 0
+ * init() should set CC.EN = 1.
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Weighted round robin enabled
+ */
+ g_ut_nvme_regs.cap.bits.ams = SPDK_NVME_CAP_AMS_WRR;
+ ctrlr.cap = g_ut_nvme_regs.cap;
+
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ /*
+ * Case 1: default round robin arbitration mechanism selected
+ */
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_RR;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_RR);
+ CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_RR);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 2: weighted round robin arbitration mechanism selected
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_WRR;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_WRR);
+ CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_WRR);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 3: vendor specific arbitration mechanism selected
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 4: invalid arbitration mechanism selected
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS + 1;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 5: reset to weighted round robin arbitration mechanism
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_WRR;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_WRR);
+ CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_WRR);
+
+ /*
+ * Transition to CSTS.RDY = 1.
+ */
+ g_ut_nvme_regs.csts.bits.rdy = 1;
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE);
+
+ /*
+ * Transition to READY.
+ */
+ while (ctrlr.state != NVME_CTRLR_STATE_READY) {
+ nvme_ctrlr_process_init(&ctrlr);
+ }
+
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+}
+static void
+test_nvme_ctrlr_init_en_0_rdy_0_ams_vs(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs));
+
+ /*
+ * Initial state: CC.EN = 0, CSTS.RDY = 0
+ * init() should set CC.EN = 1.
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Default round robin enabled
+ */
+ g_ut_nvme_regs.cap.bits.ams = SPDK_NVME_CAP_AMS_VS;
+ ctrlr.cap = g_ut_nvme_regs.cap;
+
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ /*
+ * Case 1: default round robin arbitration mechanism selected
+ */
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_RR;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_RR);
+ CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_RR);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 2: weighted round robin arbitration mechanism selected
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_WRR;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 3: vendor specific arbitration mechanism selected
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_VS);
+ CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_VS);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 4: invalid arbitration mechanism selected
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS + 1;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) != 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 0);
+
+ /*
+ * Complete and destroy the controller
+ */
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+
+ /*
+ * Reset to initial state
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ /*
+ * Case 5: reset to vendor specific arbitration mechanism
+ */
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ ctrlr.opts.arb_mechanism = SPDK_NVME_CC_AMS_VS;
+
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.ams == SPDK_NVME_CC_AMS_VS);
+ CU_ASSERT(ctrlr.opts.arb_mechanism == SPDK_NVME_CC_AMS_VS);
+
+ /*
+ * Transition to CSTS.RDY = 1.
+ */
+ g_ut_nvme_regs.csts.bits.rdy = 1;
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE);
+
+ /*
+ * Transition to READY.
+ */
+ while (ctrlr.state != NVME_CTRLR_STATE_READY) {
+ nvme_ctrlr_process_init(&ctrlr);
+ }
+
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+}
+
+static void
+test_nvme_ctrlr_init_en_0_rdy_0(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs));
+
+ /*
+ * Initial state: CC.EN = 0, CSTS.RDY = 0
+ * init() should set CC.EN = 1.
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+
+ /*
+ * Transition to CSTS.RDY = 1.
+ */
+ g_ut_nvme_regs.csts.bits.rdy = 1;
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE);
+
+ /*
+ * Transition to READY.
+ */
+ while (ctrlr.state != NVME_CTRLR_STATE_READY) {
+ nvme_ctrlr_process_init(&ctrlr);
+ }
+
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+}
+
+static void
+test_nvme_ctrlr_init_en_0_rdy_1(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ memset(&g_ut_nvme_regs, 0, sizeof(g_ut_nvme_regs));
+
+ /*
+ * Initial state: CC.EN = 0, CSTS.RDY = 1
+ */
+ g_ut_nvme_regs.cc.bits.en = 0;
+ g_ut_nvme_regs.csts.bits.rdy = 1;
+
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(&ctrlr) == 0);
+ ctrlr.cdata.nn = 1;
+ ctrlr.page_size = 0x1000;
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_INIT);
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_DISABLE_WAIT_FOR_READY_0);
+
+ /*
+ * Transition to CSTS.RDY = 0.
+ */
+ g_ut_nvme_regs.csts.bits.rdy = 0;
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE);
+
+ /*
+ * Transition to CC.EN = 1
+ */
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_WAIT_FOR_READY_1);
+ CU_ASSERT(g_ut_nvme_regs.cc.bits.en == 1);
+
+ /*
+ * Transition to CSTS.RDY = 1.
+ */
+ g_ut_nvme_regs.csts.bits.rdy = 1;
+ CU_ASSERT(nvme_ctrlr_process_init(&ctrlr) == 0);
+ CU_ASSERT(ctrlr.state == NVME_CTRLR_STATE_ENABLE_ADMIN_QUEUE);
+
+ /*
+ * Transition to READY.
+ */
+ while (ctrlr.state != NVME_CTRLR_STATE_READY) {
+ nvme_ctrlr_process_init(&ctrlr);
+ }
+
+ g_ut_nvme_regs.csts.bits.shst = SPDK_NVME_SHST_COMPLETE;
+ nvme_ctrlr_destruct(&ctrlr);
+}
+
+static void
+setup_qpairs(struct spdk_nvme_ctrlr *ctrlr, uint32_t num_io_queues)
+{
+ uint32_t i;
+
+ CU_ASSERT(pthread_mutex_init(&ctrlr->ctrlr_lock, NULL) == 0);
+
+ SPDK_CU_ASSERT_FATAL(nvme_ctrlr_construct(ctrlr) == 0);
+
+ ctrlr->page_size = 0x1000;
+ ctrlr->opts.num_io_queues = num_io_queues;
+ ctrlr->free_io_qids = spdk_bit_array_create(num_io_queues + 1);
+ SPDK_CU_ASSERT_FATAL(ctrlr->free_io_qids != NULL);
+
+ spdk_bit_array_clear(ctrlr->free_io_qids, 0);
+ for (i = 1; i <= num_io_queues; i++) {
+ spdk_bit_array_set(ctrlr->free_io_qids, i);
+ }
+}
+
+static void
+cleanup_qpairs(struct spdk_nvme_ctrlr *ctrlr)
+{
+ nvme_ctrlr_destruct(ctrlr);
+}
+
+static void
+test_alloc_io_qpair_rr_1(void)
+{
+ struct spdk_nvme_io_qpair_opts opts;
+ struct spdk_nvme_ctrlr ctrlr = {};
+ struct spdk_nvme_qpair *q0;
+
+ setup_qpairs(&ctrlr, 1);
+
+ /*
+ * Fake to simulate the controller with default round robin
+ * arbitration mechanism.
+ */
+ g_ut_nvme_regs.cc.bits.ams = SPDK_NVME_CC_AMS_RR;
+
+ spdk_nvme_ctrlr_get_default_io_qpair_opts(&ctrlr, &opts, sizeof(opts));
+
+ q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, NULL, 0);
+ SPDK_CU_ASSERT_FATAL(q0 != NULL);
+ SPDK_CU_ASSERT_FATAL(q0->qprio == 0);
+ /* Only 1 I/O qpair was allocated, so this should fail */
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, NULL, 0) == NULL);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0);
+
+ /*
+ * Now that the qpair has been returned to the free list,
+ * we should be able to allocate it again.
+ */
+ q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, NULL, 0);
+ SPDK_CU_ASSERT_FATAL(q0 != NULL);
+ SPDK_CU_ASSERT_FATAL(q0->qprio == 0);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0);
+
+ /* Only 0 qprio is acceptable for default round robin arbitration mechanism */
+ opts.qprio = 1;
+ q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q0 == NULL);
+
+ opts.qprio = 2;
+ q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q0 == NULL);
+
+ opts.qprio = 3;
+ q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q0 == NULL);
+
+ /* Only 0 ~ 3 qprio is acceptable */
+ opts.qprio = 4;
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)) == NULL);
+
+ cleanup_qpairs(&ctrlr);
+}
+
+static void
+test_alloc_io_qpair_wrr_1(void)
+{
+ struct spdk_nvme_io_qpair_opts opts;
+ struct spdk_nvme_ctrlr ctrlr = {};
+ struct spdk_nvme_qpair *q0, *q1;
+
+ setup_qpairs(&ctrlr, 2);
+
+ /*
+ * Fake to simulate the controller with weighted round robin
+ * arbitration mechanism.
+ */
+ g_ut_nvme_regs.cc.bits.ams = SPDK_NVME_CC_AMS_WRR;
+
+ spdk_nvme_ctrlr_get_default_io_qpair_opts(&ctrlr, &opts, sizeof(opts));
+
+ /*
+ * Allocate 2 qpairs and free them
+ */
+ opts.qprio = 0;
+ q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q0 != NULL);
+ SPDK_CU_ASSERT_FATAL(q0->qprio == 0);
+
+ opts.qprio = 1;
+ q1 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q1 != NULL);
+ SPDK_CU_ASSERT_FATAL(q1->qprio == 1);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q1) == 0);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0);
+
+ /*
+ * Allocate 2 qpairs and free them in the reverse order
+ */
+ opts.qprio = 2;
+ q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q0 != NULL);
+ SPDK_CU_ASSERT_FATAL(q0->qprio == 2);
+
+ opts.qprio = 3;
+ q1 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q1 != NULL);
+ SPDK_CU_ASSERT_FATAL(q1->qprio == 3);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q1) == 0);
+
+ /* Only 0 ~ 3 qprio is acceptable */
+ opts.qprio = 4;
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)) == NULL);
+
+ cleanup_qpairs(&ctrlr);
+}
+
+static void
+test_alloc_io_qpair_wrr_2(void)
+{
+ struct spdk_nvme_io_qpair_opts opts;
+ struct spdk_nvme_ctrlr ctrlr = {};
+ struct spdk_nvme_qpair *q0, *q1, *q2, *q3;
+
+ setup_qpairs(&ctrlr, 4);
+
+ /*
+ * Fake to simulate the controller with weighted round robin
+ * arbitration mechanism.
+ */
+ g_ut_nvme_regs.cc.bits.ams = SPDK_NVME_CC_AMS_WRR;
+
+ spdk_nvme_ctrlr_get_default_io_qpair_opts(&ctrlr, &opts, sizeof(opts));
+
+ opts.qprio = 0;
+ q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q0 != NULL);
+ SPDK_CU_ASSERT_FATAL(q0->qprio == 0);
+
+ opts.qprio = 1;
+ q1 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q1 != NULL);
+ SPDK_CU_ASSERT_FATAL(q1->qprio == 1);
+
+ opts.qprio = 2;
+ q2 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q2 != NULL);
+ SPDK_CU_ASSERT_FATAL(q2->qprio == 2);
+
+ opts.qprio = 3;
+ q3 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q3 != NULL);
+ SPDK_CU_ASSERT_FATAL(q3->qprio == 3);
+
+ /* Only 4 I/O qpairs was allocated, so this should fail */
+ opts.qprio = 0;
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts)) == NULL);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q3) == 0);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q2) == 0);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q1) == 0);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0);
+
+ /*
+ * Now that the qpair has been returned to the free list,
+ * we should be able to allocate it again.
+ *
+ * Allocate 4 I/O qpairs and half of them with same qprio.
+ */
+ opts.qprio = 1;
+ q0 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q0 != NULL);
+ SPDK_CU_ASSERT_FATAL(q0->qprio == 1);
+
+ opts.qprio = 1;
+ q1 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q1 != NULL);
+ SPDK_CU_ASSERT_FATAL(q1->qprio == 1);
+
+ opts.qprio = 3;
+ q2 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q2 != NULL);
+ SPDK_CU_ASSERT_FATAL(q2->qprio == 3);
+
+ opts.qprio = 3;
+ q3 = spdk_nvme_ctrlr_alloc_io_qpair(&ctrlr, &opts, sizeof(opts));
+ SPDK_CU_ASSERT_FATAL(q3 != NULL);
+ SPDK_CU_ASSERT_FATAL(q3->qprio == 3);
+
+ /*
+ * Free all I/O qpairs in reverse order
+ */
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q0) == 0);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q1) == 0);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q2) == 0);
+ SPDK_CU_ASSERT_FATAL(spdk_nvme_ctrlr_free_io_qpair(q3) == 0);
+
+ cleanup_qpairs(&ctrlr);
+}
+
+static void
+test_nvme_ctrlr_fail(void)
+{
+ struct spdk_nvme_ctrlr ctrlr = {};
+
+ ctrlr.opts.num_io_queues = 0;
+ nvme_ctrlr_fail(&ctrlr, false);
+
+ CU_ASSERT(ctrlr.is_failed == true);
+}
+
+static void
+test_nvme_ctrlr_construct_intel_support_log_page_list(void)
+{
+ bool res;
+ struct spdk_nvme_ctrlr ctrlr = {};
+ struct spdk_nvme_intel_log_page_directory payload = {};
+ struct spdk_pci_id pci_id = {};
+
+ /* Get quirks for a device with all 0 vendor/device id */
+ ctrlr.quirks = nvme_get_quirks(&pci_id);
+ CU_ASSERT(ctrlr.quirks == 0);
+
+ nvme_ctrlr_construct_intel_support_log_page_list(&ctrlr, &payload);
+ res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_TEMPERATURE);
+ CU_ASSERT(res == false);
+
+ /* Set the vendor to Intel, but provide no device id */
+ ctrlr.cdata.vid = pci_id.vendor_id = SPDK_PCI_VID_INTEL;
+ payload.temperature_statistics_log_len = 1;
+ ctrlr.quirks = nvme_get_quirks(&pci_id);
+ memset(ctrlr.log_page_supported, 0, sizeof(ctrlr.log_page_supported));
+
+ nvme_ctrlr_construct_intel_support_log_page_list(&ctrlr, &payload);
+ res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_PAGE_DIRECTORY);
+ CU_ASSERT(res == true);
+ res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_TEMPERATURE);
+ CU_ASSERT(res == true);
+ res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_READ_CMD_LATENCY);
+ CU_ASSERT(res == false);
+ res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_SMART);
+ CU_ASSERT(res == false);
+
+ /* set valid vendor id, device id and sub device id */
+ ctrlr.cdata.vid = SPDK_PCI_VID_INTEL;
+ payload.temperature_statistics_log_len = 0;
+ pci_id.vendor_id = SPDK_PCI_VID_INTEL;
+ pci_id.device_id = 0x0953;
+ pci_id.subvendor_id = SPDK_PCI_VID_INTEL;
+ pci_id.subdevice_id = 0x3702;
+ ctrlr.quirks = nvme_get_quirks(&pci_id);
+ memset(ctrlr.log_page_supported, 0, sizeof(ctrlr.log_page_supported));
+
+ nvme_ctrlr_construct_intel_support_log_page_list(&ctrlr, &payload);
+ res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_PAGE_DIRECTORY);
+ CU_ASSERT(res == true);
+ res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_TEMPERATURE);
+ CU_ASSERT(res == false);
+ res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_READ_CMD_LATENCY);
+ CU_ASSERT(res == true);
+ res = spdk_nvme_ctrlr_is_log_page_supported(&ctrlr, SPDK_NVME_INTEL_LOG_SMART);
+ CU_ASSERT(res == false);
+}
+
+static void
+test_nvme_ctrlr_set_supported_features(void)
+{
+ bool res;
+ struct spdk_nvme_ctrlr ctrlr = {};
+
+ /* set a invalid vendor id */
+ ctrlr.cdata.vid = 0xFFFF;
+ nvme_ctrlr_set_supported_features(&ctrlr);
+ res = spdk_nvme_ctrlr_is_feature_supported(&ctrlr, SPDK_NVME_FEAT_ARBITRATION);
+ CU_ASSERT(res == true);
+ res = spdk_nvme_ctrlr_is_feature_supported(&ctrlr, SPDK_NVME_INTEL_FEAT_MAX_LBA);
+ CU_ASSERT(res == false);
+
+ ctrlr.cdata.vid = SPDK_PCI_VID_INTEL;
+ nvme_ctrlr_set_supported_features(&ctrlr);
+ res = spdk_nvme_ctrlr_is_feature_supported(&ctrlr, SPDK_NVME_FEAT_ARBITRATION);
+ CU_ASSERT(res == true);
+ res = spdk_nvme_ctrlr_is_feature_supported(&ctrlr, SPDK_NVME_INTEL_FEAT_MAX_LBA);
+ CU_ASSERT(res == true);
+}
+
+static void
+test_ctrlr_get_default_ctrlr_opts(void)
+{
+ struct spdk_nvme_ctrlr_opts opts = {};
+
+ CU_ASSERT(spdk_uuid_parse(&g_spdk_nvme_driver->default_extended_host_id,
+ "e53e9258-c93b-48b5-be1a-f025af6d232a") == 0);
+
+ memset(&opts, 0, sizeof(opts));
+
+ /* set a smaller opts_size */
+ CU_ASSERT(sizeof(opts) > 8);
+ spdk_nvme_ctrlr_get_default_ctrlr_opts(&opts, 8);
+ CU_ASSERT_EQUAL(opts.num_io_queues, DEFAULT_MAX_IO_QUEUES);
+ CU_ASSERT_TRUE(opts.use_cmb_sqs);
+ /* check below fields are not initialized by default value */
+ CU_ASSERT_EQUAL(opts.arb_mechanism, 0);
+ CU_ASSERT_EQUAL(opts.keep_alive_timeout_ms, 0);
+ CU_ASSERT_EQUAL(opts.io_queue_size, 0);
+ CU_ASSERT_EQUAL(opts.io_queue_requests, 0);
+ for (int i = 0; i < 8; i++) {
+ CU_ASSERT(opts.host_id[i] == 0);
+ }
+ for (int i = 0; i < 16; i++) {
+ CU_ASSERT(opts.extended_host_id[i] == 0);
+ }
+ CU_ASSERT(strlen(opts.hostnqn) == 0);
+ CU_ASSERT(strlen(opts.src_addr) == 0);
+ CU_ASSERT(strlen(opts.src_svcid) == 0);
+
+ /* set a consistent opts_size */
+ spdk_nvme_ctrlr_get_default_ctrlr_opts(&opts, sizeof(opts));
+ CU_ASSERT_EQUAL(opts.num_io_queues, DEFAULT_MAX_IO_QUEUES);
+ CU_ASSERT_TRUE(opts.use_cmb_sqs);
+ CU_ASSERT_EQUAL(opts.arb_mechanism, SPDK_NVME_CC_AMS_RR);
+ CU_ASSERT_EQUAL(opts.keep_alive_timeout_ms, 10 * 1000);
+ CU_ASSERT_EQUAL(opts.io_queue_size, DEFAULT_IO_QUEUE_SIZE);
+ CU_ASSERT_EQUAL(opts.io_queue_requests, DEFAULT_IO_QUEUE_REQUESTS);
+ for (int i = 0; i < 8; i++) {
+ CU_ASSERT(opts.host_id[i] == 0);
+ }
+ CU_ASSERT_STRING_EQUAL(opts.hostnqn,
+ "2014-08.org.nvmexpress:uuid:e53e9258-c93b-48b5-be1a-f025af6d232a");
+ CU_ASSERT(memcmp(opts.extended_host_id, &g_spdk_nvme_driver->default_extended_host_id,
+ sizeof(opts.extended_host_id)) == 0);
+ CU_ASSERT(strlen(opts.src_addr) == 0);
+ CU_ASSERT(strlen(opts.src_svcid) == 0);
+}
+
+static void
+test_ctrlr_get_default_io_qpair_opts(void)
+{
+ struct spdk_nvme_ctrlr ctrlr = {};
+ struct spdk_nvme_io_qpair_opts opts = {};
+
+ memset(&opts, 0, sizeof(opts));
+
+ /* set a smaller opts_size */
+ ctrlr.opts.io_queue_size = DEFAULT_IO_QUEUE_SIZE;
+ CU_ASSERT(sizeof(opts) > 8);
+ spdk_nvme_ctrlr_get_default_io_qpair_opts(&ctrlr, &opts, 8);
+ CU_ASSERT_EQUAL(opts.qprio, SPDK_NVME_QPRIO_URGENT);
+ CU_ASSERT_EQUAL(opts.io_queue_size, DEFAULT_IO_QUEUE_SIZE);
+ /* check below field is not initialized by default value */
+ CU_ASSERT_EQUAL(opts.io_queue_requests, 0);
+
+ /* set a consistent opts_size */
+ ctrlr.opts.io_queue_size = DEFAULT_IO_QUEUE_SIZE;
+ ctrlr.opts.io_queue_requests = DEFAULT_IO_QUEUE_REQUESTS;
+ spdk_nvme_ctrlr_get_default_io_qpair_opts(&ctrlr, &opts, sizeof(opts));
+ CU_ASSERT_EQUAL(opts.qprio, SPDK_NVME_QPRIO_URGENT);
+ CU_ASSERT_EQUAL(opts.io_queue_size, DEFAULT_IO_QUEUE_SIZE);
+ CU_ASSERT_EQUAL(opts.io_queue_requests, DEFAULT_IO_QUEUE_REQUESTS);
+}
+
+#if 0 /* TODO: move to PCIe-specific unit test */
+static void
+test_nvme_ctrlr_alloc_cmb(void)
+{
+ int rc;
+ uint64_t offset;
+ struct spdk_nvme_ctrlr ctrlr = {};
+
+ ctrlr.cmb_size = 0x1000000;
+ ctrlr.cmb_current_offset = 0x100;
+ rc = nvme_ctrlr_alloc_cmb(&ctrlr, 0x200, 0x1000, &offset);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(offset == 0x1000);
+ CU_ASSERT(ctrlr.cmb_current_offset == 0x1200);
+
+ rc = nvme_ctrlr_alloc_cmb(&ctrlr, 0x800, 0x1000, &offset);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(offset == 0x2000);
+ CU_ASSERT(ctrlr.cmb_current_offset == 0x2800);
+
+ rc = nvme_ctrlr_alloc_cmb(&ctrlr, 0x800000, 0x100000, &offset);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(offset == 0x100000);
+ CU_ASSERT(ctrlr.cmb_current_offset == 0x900000);
+
+ rc = nvme_ctrlr_alloc_cmb(&ctrlr, 0x8000000, 0x1000, &offset);
+ CU_ASSERT(rc == -1);
+}
+#endif
+
+static void
+test_spdk_nvme_ctrlr_update_firmware(void)
+{
+ struct spdk_nvme_ctrlr ctrlr = {};
+ void *payload = NULL;
+ int point_payload = 1;
+ int slot = 0;
+ int ret = 0;
+ struct spdk_nvme_status status;
+ enum spdk_nvme_fw_commit_action commit_action = SPDK_NVME_FW_COMMIT_REPLACE_IMG;
+
+ /* Set invalid size check function return value */
+ set_size = 5;
+ ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status);
+ CU_ASSERT(ret == -1);
+
+ /* When payload is NULL but set_size < min_page_size */
+ set_size = 4;
+ ctrlr.min_page_size = 5;
+ ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status);
+ CU_ASSERT(ret == -1);
+
+ /* When payload not NULL but min_page_size is 0 */
+ set_size = 4;
+ ctrlr.min_page_size = 0;
+ payload = &point_payload;
+ ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status);
+ CU_ASSERT(ret == -1);
+
+ /* Check firmware image download when payload not NULL and min_page_size not 0 , status.cpl value is 1 */
+ set_status_cpl = 1;
+ set_size = 4;
+ ctrlr.min_page_size = 5;
+ payload = &point_payload;
+ ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status);
+ CU_ASSERT(ret == -ENXIO);
+
+ /* Check firmware image download and set status.cpl value is 0 */
+ set_status_cpl = 0;
+ set_size = 4;
+ ctrlr.min_page_size = 5;
+ payload = &point_payload;
+ ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status);
+ CU_ASSERT(ret == -1);
+
+ /* Check firmware commit */
+ ctrlr.is_resetting = false;
+ set_status_cpl = 0;
+ slot = 1;
+ set_size = 4;
+ ctrlr.min_page_size = 5;
+ payload = &point_payload;
+ ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status);
+ CU_ASSERT(ret == -ENXIO);
+
+ /* Set size check firmware download and firmware commit */
+ ctrlr.is_resetting = true;
+ set_status_cpl = 0;
+ slot = 1;
+ set_size = 4;
+ ctrlr.min_page_size = 5;
+ payload = &point_payload;
+ ret = spdk_nvme_ctrlr_update_firmware(&ctrlr, payload, set_size, slot, commit_action, &status);
+ CU_ASSERT(ret == 0);
+
+ set_status_cpl = 0;
+}
+
+int
+nvme_ctrlr_cmd_doorbell_buffer_config(struct spdk_nvme_ctrlr *ctrlr, uint64_t prp1, uint64_t prp2,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ fake_cpl_success(cb_fn, cb_arg);
+ return 0;
+}
+
+static void
+test_spdk_nvme_ctrlr_doorbell_buffer_config(void)
+{
+ struct spdk_nvme_ctrlr ctrlr = {};
+ int ret = -1;
+
+ ctrlr.cdata.oacs.doorbell_buffer_config = 1;
+ ctrlr.trid.trtype = SPDK_NVME_TRANSPORT_PCIE;
+ ctrlr.page_size = 0x1000;
+ MOCK_CLEAR(spdk_malloc)
+ MOCK_CLEAR(spdk_zmalloc)
+ MOCK_CLEAR(spdk_dma_malloc)
+ MOCK_CLEAR(spdk_dma_zmalloc)
+ ret = nvme_ctrlr_set_doorbell_buffer_config(&ctrlr);
+ CU_ASSERT(ret == 0);
+ nvme_ctrlr_free_doorbell_buffer(&ctrlr);
+}
+
+static void
+test_nvme_ctrlr_test_active_ns(void)
+{
+ uint32_t nsid, minor;
+ size_t ns_id_count;
+ struct spdk_nvme_ctrlr ctrlr = {};
+
+ ctrlr.page_size = 0x1000;
+
+ for (minor = 0; minor <= 2; minor++) {
+ ctrlr.cdata.ver.bits.mjr = 1;
+ ctrlr.cdata.ver.bits.mnr = minor;
+ ctrlr.cdata.ver.bits.ter = 0;
+ ctrlr.num_ns = 1531;
+ nvme_ctrlr_identify_active_ns(&ctrlr);
+
+ for (nsid = 1; nsid <= ctrlr.num_ns; nsid++) {
+ CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, nsid) == true);
+ }
+ ctrlr.num_ns = 1559;
+ for (; nsid <= ctrlr.num_ns; nsid++) {
+ CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, nsid) == false);
+ }
+ ctrlr.num_ns = 1531;
+ for (nsid = 0; nsid < ctrlr.num_ns; nsid++) {
+ ctrlr.active_ns_list[nsid] = 0;
+ }
+ CU_ASSERT(spdk_nvme_ctrlr_get_first_active_ns(&ctrlr) == 0);
+
+ ctrlr.active_ns_list[0] = 1;
+ CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, 1) == true);
+ CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, 2) == false);
+ nsid = spdk_nvme_ctrlr_get_first_active_ns(&ctrlr);
+ CU_ASSERT(nsid == 1);
+
+ ctrlr.active_ns_list[1] = 3;
+ CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, 1) == true);
+ CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, 2) == false);
+ CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, 3) == true);
+ nsid = spdk_nvme_ctrlr_get_next_active_ns(&ctrlr, nsid);
+ CU_ASSERT(nsid == 3);
+ nsid = spdk_nvme_ctrlr_get_next_active_ns(&ctrlr, nsid);
+ CU_ASSERT(nsid == 0);
+
+ memset(ctrlr.active_ns_list, 0, ctrlr.num_ns);
+ for (nsid = 0; nsid < ctrlr.num_ns; nsid++) {
+ ctrlr.active_ns_list[nsid] = nsid + 1;
+ }
+
+ ns_id_count = 0;
+ for (nsid = spdk_nvme_ctrlr_get_first_active_ns(&ctrlr);
+ nsid != 0; nsid = spdk_nvme_ctrlr_get_next_active_ns(&ctrlr, nsid)) {
+ CU_ASSERT(spdk_nvme_ctrlr_is_active_ns(&ctrlr, nsid) == true);
+ ns_id_count++;
+ }
+ CU_ASSERT(ns_id_count == ctrlr.num_ns);
+
+ nvme_ctrlr_destruct(&ctrlr);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvme_ctrlr", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test nvme_ctrlr init CC.EN = 1 CSTS.RDY = 0",
+ test_nvme_ctrlr_init_en_1_rdy_0) == NULL
+ || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 1 CSTS.RDY = 1",
+ test_nvme_ctrlr_init_en_1_rdy_1) == NULL
+ || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 0 CSTS.RDY = 0",
+ test_nvme_ctrlr_init_en_0_rdy_0) == NULL
+ || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 0 CSTS.RDY = 1",
+ test_nvme_ctrlr_init_en_0_rdy_1) == NULL
+ || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 0 CSTS.RDY = 0 AMS = RR",
+ test_nvme_ctrlr_init_en_0_rdy_0_ams_rr) == NULL
+ || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 0 CSTS.RDY = 0 AMS = WRR",
+ test_nvme_ctrlr_init_en_0_rdy_0_ams_wrr) == NULL
+ || CU_add_test(suite, "test nvme_ctrlr init CC.EN = 0 CSTS.RDY = 0 AMS = VS",
+ test_nvme_ctrlr_init_en_0_rdy_0_ams_vs) == NULL
+ || CU_add_test(suite, "alloc_io_qpair_rr 1", test_alloc_io_qpair_rr_1) == NULL
+ || CU_add_test(suite, "get_default_ctrlr_opts", test_ctrlr_get_default_ctrlr_opts) == NULL
+ || CU_add_test(suite, "get_default_io_qpair_opts", test_ctrlr_get_default_io_qpair_opts) == NULL
+ || CU_add_test(suite, "alloc_io_qpair_wrr 1", test_alloc_io_qpair_wrr_1) == NULL
+ || CU_add_test(suite, "alloc_io_qpair_wrr 2", test_alloc_io_qpair_wrr_2) == NULL
+ || CU_add_test(suite, "test nvme ctrlr function update_firmware",
+ test_spdk_nvme_ctrlr_update_firmware) == NULL
+ || CU_add_test(suite, "test nvme_ctrlr function nvme_ctrlr_fail", test_nvme_ctrlr_fail) == NULL
+ || CU_add_test(suite, "test nvme ctrlr function nvme_ctrlr_construct_intel_support_log_page_list",
+ test_nvme_ctrlr_construct_intel_support_log_page_list) == NULL
+ || CU_add_test(suite, "test nvme ctrlr function nvme_ctrlr_set_supported_features",
+ test_nvme_ctrlr_set_supported_features) == NULL
+ || CU_add_test(suite, "test nvme ctrlr function nvme_ctrlr_set_doorbell_buffer_config",
+ test_spdk_nvme_ctrlr_doorbell_buffer_config) == NULL
+#if 0 /* TODO: move to PCIe-specific unit test */
+ || CU_add_test(suite, "test nvme ctrlr function nvme_ctrlr_alloc_cmb",
+ test_nvme_ctrlr_alloc_cmb) == NULL
+#endif
+ || CU_add_test(suite, "test nvme ctrlr function test_nvme_ctrlr_test_active_ns",
+ test_nvme_ctrlr_test_active_ns) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/.gitignore
new file mode 100644
index 00000000..1568b476
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/.gitignore
@@ -0,0 +1 @@
+nvme_ctrlr_cmd_ut
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/Makefile
new file mode 100644
index 00000000..5c647dd3
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = nvme_ctrlr_cmd_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/nvme_ctrlr_cmd_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/nvme_ctrlr_cmd_ut.c
new file mode 100644
index 00000000..8cbc4476
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_cmd.c/nvme_ctrlr_cmd_ut.c
@@ -0,0 +1,645 @@
+
+/*-
+ * 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_cunit.h"
+
+#include "nvme/nvme_ctrlr_cmd.c"
+
+#define CTRLR_CDATA_ELPE 5
+
+pid_t g_spdk_nvme_pid;
+
+struct nvme_request g_req;
+
+uint32_t error_num_entries;
+uint32_t health_log_nsid = 1;
+uint8_t feature = 1;
+uint32_t feature_cdw11 = 1;
+uint32_t feature_cdw12 = 1;
+uint8_t get_feature = 1;
+uint32_t get_feature_cdw11 = 1;
+uint32_t fw_img_size = 1024;
+uint32_t fw_img_offset = 0;
+uint16_t abort_cid = 1;
+uint16_t abort_sqid = 1;
+uint32_t namespace_management_nsid = 1;
+uint32_t format_nvme_nsid = 1;
+
+uint32_t expected_feature_ns = 2;
+uint32_t expected_feature_cdw10 = SPDK_NVME_FEAT_LBA_RANGE_TYPE;
+uint32_t expected_feature_cdw11 = 1;
+uint32_t expected_feature_cdw12 = 1;
+
+typedef void (*verify_request_fn_t)(struct nvme_request *req);
+verify_request_fn_t verify_fn;
+
+static void verify_firmware_log_page(struct nvme_request *req)
+{
+ uint32_t temp_cdw10;
+
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE);
+ CU_ASSERT(req->cmd.nsid == SPDK_NVME_GLOBAL_NS_TAG);
+
+ temp_cdw10 = ((sizeof(struct spdk_nvme_firmware_page) / sizeof(uint32_t) - 1) << 16) |
+ SPDK_NVME_LOG_FIRMWARE_SLOT;
+ CU_ASSERT(req->cmd.cdw10 == temp_cdw10);
+}
+
+static void verify_health_log_page(struct nvme_request *req)
+{
+ uint32_t temp_cdw10;
+
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE);
+ CU_ASSERT(req->cmd.nsid == health_log_nsid);
+
+ temp_cdw10 = ((sizeof(struct spdk_nvme_health_information_page) / sizeof(uint32_t) - 1) << 16) |
+ SPDK_NVME_LOG_HEALTH_INFORMATION;
+ CU_ASSERT(req->cmd.cdw10 == temp_cdw10);
+}
+
+static void verify_error_log_page(struct nvme_request *req)
+{
+ uint32_t temp_cdw10;
+
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE);
+ CU_ASSERT(req->cmd.nsid == SPDK_NVME_GLOBAL_NS_TAG);
+
+ temp_cdw10 = (((sizeof(struct spdk_nvme_error_information_entry) * error_num_entries) /
+ sizeof(uint32_t) - 1) << 16) | SPDK_NVME_LOG_ERROR;
+ CU_ASSERT(req->cmd.cdw10 == temp_cdw10);
+}
+
+static void verify_set_feature_cmd(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_SET_FEATURES);
+ CU_ASSERT(req->cmd.cdw10 == feature);
+ CU_ASSERT(req->cmd.cdw11 == feature_cdw11);
+ CU_ASSERT(req->cmd.cdw12 == feature_cdw12);
+}
+
+static void verify_set_feature_ns_cmd(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_SET_FEATURES);
+ CU_ASSERT(req->cmd.cdw10 == expected_feature_cdw10);
+ CU_ASSERT(req->cmd.cdw11 == expected_feature_cdw11);
+ CU_ASSERT(req->cmd.cdw12 == expected_feature_cdw12);
+ CU_ASSERT(req->cmd.nsid == expected_feature_ns);
+}
+
+static void verify_get_feature_cmd(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_FEATURES);
+ CU_ASSERT(req->cmd.cdw10 == get_feature);
+ CU_ASSERT(req->cmd.cdw11 == get_feature_cdw11);
+}
+
+static void verify_get_feature_ns_cmd(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_FEATURES);
+ CU_ASSERT(req->cmd.cdw10 == expected_feature_cdw10);
+ CU_ASSERT(req->cmd.cdw11 == expected_feature_cdw11);
+ CU_ASSERT(req->cmd.nsid == expected_feature_ns);
+}
+
+static void verify_abort_cmd(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_ABORT);
+ CU_ASSERT(req->cmd.cdw10 == (((uint32_t)abort_cid << 16) | abort_sqid));
+}
+
+static void verify_io_raw_cmd(struct nvme_request *req)
+{
+ struct spdk_nvme_cmd command = {};
+
+ CU_ASSERT(memcmp(&req->cmd, &command, sizeof(req->cmd)) == 0);
+}
+
+static void verify_io_raw_cmd_with_md(struct nvme_request *req)
+{
+ struct spdk_nvme_cmd command = {};
+
+ CU_ASSERT(memcmp(&req->cmd, &command, sizeof(req->cmd)) == 0);
+}
+
+static void verify_intel_smart_log_page(struct nvme_request *req)
+{
+ uint32_t temp_cdw10;
+
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE);
+ CU_ASSERT(req->cmd.nsid == health_log_nsid);
+
+ temp_cdw10 = ((sizeof(struct spdk_nvme_intel_smart_information_page) /
+ sizeof(uint32_t) - 1) << 16) |
+ SPDK_NVME_INTEL_LOG_SMART;
+ CU_ASSERT(req->cmd.cdw10 == temp_cdw10);
+}
+
+static void verify_intel_temperature_log_page(struct nvme_request *req)
+{
+ uint32_t temp_cdw10;
+
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE);
+
+ temp_cdw10 = ((sizeof(struct spdk_nvme_intel_temperature_page) / sizeof(uint32_t) - 1) << 16) |
+ SPDK_NVME_INTEL_LOG_TEMPERATURE;
+ CU_ASSERT(req->cmd.cdw10 == temp_cdw10);
+}
+
+static void verify_intel_read_latency_log_page(struct nvme_request *req)
+{
+ uint32_t temp_cdw10;
+
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE);
+
+ temp_cdw10 = ((sizeof(struct spdk_nvme_intel_rw_latency_page) / sizeof(uint32_t) - 1) << 16) |
+ SPDK_NVME_INTEL_LOG_READ_CMD_LATENCY;
+ CU_ASSERT(req->cmd.cdw10 == temp_cdw10);
+}
+
+static void verify_intel_write_latency_log_page(struct nvme_request *req)
+{
+ uint32_t temp_cdw10;
+
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE);
+
+ temp_cdw10 = ((sizeof(struct spdk_nvme_intel_rw_latency_page) / sizeof(uint32_t) - 1) << 16) |
+ SPDK_NVME_INTEL_LOG_WRITE_CMD_LATENCY;
+ CU_ASSERT(req->cmd.cdw10 == temp_cdw10);
+}
+
+static void verify_intel_get_log_page_directory(struct nvme_request *req)
+{
+ uint32_t temp_cdw10;
+
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE);
+
+ temp_cdw10 = ((sizeof(struct spdk_nvme_intel_log_page_directory) / sizeof(uint32_t) - 1) << 16) |
+ SPDK_NVME_INTEL_LOG_PAGE_DIRECTORY;
+ CU_ASSERT(req->cmd.cdw10 == temp_cdw10);
+}
+
+static void verify_intel_marketing_description_log_page(struct nvme_request *req)
+{
+ uint32_t temp_cdw10;
+
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_GET_LOG_PAGE);
+
+ temp_cdw10 = ((sizeof(struct spdk_nvme_intel_marketing_description_page) / sizeof(
+ uint32_t) - 1) << 16) |
+ SPDK_NVME_INTEL_MARKETING_DESCRIPTION;
+ CU_ASSERT(req->cmd.cdw10 == temp_cdw10);
+}
+
+static void verify_namespace_attach(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_NS_ATTACHMENT);
+ CU_ASSERT(req->cmd.cdw10 == SPDK_NVME_NS_CTRLR_ATTACH);
+ CU_ASSERT(req->cmd.nsid == namespace_management_nsid);
+}
+
+static void verify_namespace_detach(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_NS_ATTACHMENT);
+ CU_ASSERT(req->cmd.cdw10 == SPDK_NVME_NS_CTRLR_DETACH);
+ CU_ASSERT(req->cmd.nsid == namespace_management_nsid);
+}
+
+static void verify_namespace_create(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_NS_MANAGEMENT);
+ CU_ASSERT(req->cmd.cdw10 == SPDK_NVME_NS_MANAGEMENT_CREATE);
+ CU_ASSERT(req->cmd.nsid == 0);
+}
+
+static void verify_namespace_delete(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_NS_MANAGEMENT);
+ CU_ASSERT(req->cmd.cdw10 == SPDK_NVME_NS_MANAGEMENT_DELETE);
+ CU_ASSERT(req->cmd.nsid == namespace_management_nsid);
+}
+
+static void verify_format_nvme(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_FORMAT_NVM);
+ CU_ASSERT(req->cmd.cdw10 == 0);
+ CU_ASSERT(req->cmd.nsid == format_nvme_nsid);
+}
+
+static void verify_fw_commit(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_FIRMWARE_COMMIT);
+ CU_ASSERT(req->cmd.cdw10 == 0x09);
+}
+
+static void verify_fw_image_download(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_NVME_OPC_FIRMWARE_IMAGE_DOWNLOAD);
+ CU_ASSERT(req->cmd.cdw10 == (fw_img_size >> 2) - 1);
+ CU_ASSERT(req->cmd.cdw11 == fw_img_offset >> 2);
+}
+
+struct nvme_request *
+nvme_allocate_request_user_copy(struct spdk_nvme_qpair *qpair, void *buffer, uint32_t payload_size,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg, bool host_to_controller)
+{
+ /* For the unit test, we don't actually need to copy the buffer */
+ return nvme_allocate_request_contig(qpair, buffer, payload_size, cb_fn, cb_arg);
+}
+
+int
+nvme_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req)
+{
+ verify_fn(req);
+ /* stop analyzer from thinking stack variable addresses are stored in a global */
+ memset(req, 0, sizeof(*req));
+
+ return 0;
+}
+
+int
+nvme_ctrlr_submit_admin_request(struct spdk_nvme_ctrlr *ctrlr, struct nvme_request *req)
+{
+ verify_fn(req);
+ /* stop analyzer from thinking stack variable addresses are stored in a global */
+ memset(req, 0, sizeof(*req));
+
+ return 0;
+}
+
+#define DECLARE_AND_CONSTRUCT_CTRLR() \
+ struct spdk_nvme_ctrlr ctrlr = {}; \
+ struct spdk_nvme_qpair adminq = {}; \
+ struct nvme_request req; \
+ \
+ STAILQ_INIT(&adminq.free_req); \
+ STAILQ_INSERT_HEAD(&adminq.free_req, &req, stailq); \
+ ctrlr.adminq = &adminq;
+
+static void
+test_firmware_get_log_page(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_firmware_page payload = {};
+
+ verify_fn = verify_firmware_log_page;
+
+ spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_LOG_FIRMWARE_SLOT, SPDK_NVME_GLOBAL_NS_TAG,
+ &payload,
+ sizeof(payload), 0, NULL, NULL);
+}
+
+static void
+test_health_get_log_page(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_health_information_page payload = {};
+
+ verify_fn = verify_health_log_page;
+
+ spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_LOG_HEALTH_INFORMATION, health_log_nsid,
+ &payload,
+ sizeof(payload), 0, NULL, NULL);
+}
+
+static void
+test_error_get_log_page(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_error_information_entry payload = {};
+
+ ctrlr.cdata.elpe = CTRLR_CDATA_ELPE;
+
+ verify_fn = verify_error_log_page;
+
+ /* valid page */
+ error_num_entries = 1;
+ spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_LOG_ERROR, SPDK_NVME_GLOBAL_NS_TAG, &payload,
+ sizeof(payload), 0, NULL, NULL);
+}
+
+static void test_intel_smart_get_log_page(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_intel_smart_information_page payload = {};
+
+ verify_fn = verify_intel_smart_log_page;
+
+ spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_LOG_SMART, health_log_nsid, &payload,
+ sizeof(payload), 0, NULL, NULL);
+}
+
+static void test_intel_temperature_get_log_page(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_intel_temperature_page payload = {};
+
+ verify_fn = verify_intel_temperature_log_page;
+
+ spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_LOG_TEMPERATURE, SPDK_NVME_GLOBAL_NS_TAG,
+ &payload, sizeof(payload), 0, NULL, NULL);
+}
+
+static void test_intel_read_latency_get_log_page(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_intel_rw_latency_page payload = {};
+
+ verify_fn = verify_intel_read_latency_log_page;
+
+ spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_LOG_READ_CMD_LATENCY,
+ SPDK_NVME_GLOBAL_NS_TAG,
+ &payload, sizeof(payload), 0, NULL, NULL);
+}
+
+static void test_intel_write_latency_get_log_page(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_intel_rw_latency_page payload = {};
+
+ verify_fn = verify_intel_write_latency_log_page;
+
+ spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_LOG_WRITE_CMD_LATENCY,
+ SPDK_NVME_GLOBAL_NS_TAG,
+ &payload, sizeof(payload), 0, NULL, NULL);
+}
+
+static void test_intel_get_log_page_directory(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_intel_log_page_directory payload = {};
+
+ verify_fn = verify_intel_get_log_page_directory;
+
+ spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_LOG_PAGE_DIRECTORY,
+ SPDK_NVME_GLOBAL_NS_TAG,
+ &payload, sizeof(payload), 0, NULL, NULL);
+}
+
+static void test_intel_marketing_description_get_log_page(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_intel_marketing_description_page payload = {};
+
+ verify_fn = verify_intel_marketing_description_log_page;
+
+ spdk_nvme_ctrlr_cmd_get_log_page(&ctrlr, SPDK_NVME_INTEL_MARKETING_DESCRIPTION,
+ SPDK_NVME_GLOBAL_NS_TAG,
+ &payload, sizeof(payload), 0, NULL, NULL);
+}
+
+static void test_generic_get_log_pages(void)
+{
+ test_error_get_log_page();
+ test_health_get_log_page();
+ test_firmware_get_log_page();
+}
+
+static void test_intel_get_log_pages(void)
+{
+ test_intel_get_log_page_directory();
+ test_intel_smart_get_log_page();
+ test_intel_temperature_get_log_page();
+ test_intel_read_latency_get_log_page();
+ test_intel_write_latency_get_log_page();
+ test_intel_marketing_description_get_log_page();
+}
+
+static void
+test_set_feature_cmd(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ verify_fn = verify_set_feature_cmd;
+
+ spdk_nvme_ctrlr_cmd_set_feature(&ctrlr, feature, feature_cdw11, feature_cdw12, NULL, 0, NULL, NULL);
+}
+
+static void
+test_get_feature_ns_cmd(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ verify_fn = verify_get_feature_ns_cmd;
+
+ spdk_nvme_ctrlr_cmd_get_feature_ns(&ctrlr, expected_feature_cdw10,
+ expected_feature_cdw11, NULL, 0,
+ NULL, NULL, expected_feature_ns);
+}
+
+static void
+test_set_feature_ns_cmd(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ verify_fn = verify_set_feature_ns_cmd;
+
+ spdk_nvme_ctrlr_cmd_set_feature_ns(&ctrlr, expected_feature_cdw10,
+ expected_feature_cdw11, expected_feature_cdw12,
+ NULL, 0, NULL, NULL, expected_feature_ns);
+}
+
+static void
+test_get_feature_cmd(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ verify_fn = verify_get_feature_cmd;
+
+ spdk_nvme_ctrlr_cmd_get_feature(&ctrlr, get_feature, get_feature_cdw11, NULL, 0, NULL, NULL);
+}
+
+static void
+test_abort_cmd(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_qpair qpair = {};
+
+ STAILQ_INIT(&ctrlr.queued_aborts);
+
+ verify_fn = verify_abort_cmd;
+
+ qpair.id = abort_sqid;
+ spdk_nvme_ctrlr_cmd_abort(&ctrlr, &qpair, abort_cid, NULL, NULL);
+}
+
+static void
+test_io_raw_cmd(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_qpair qpair = {};
+ struct spdk_nvme_cmd cmd = {};
+
+ verify_fn = verify_io_raw_cmd;
+
+ spdk_nvme_ctrlr_cmd_io_raw(&ctrlr, &qpair, &cmd, NULL, 1, NULL, NULL);
+}
+
+static void
+test_io_raw_cmd_with_md(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_qpair qpair = {};
+ struct spdk_nvme_cmd cmd = {};
+
+ verify_fn = verify_io_raw_cmd_with_md;
+
+ spdk_nvme_ctrlr_cmd_io_raw_with_md(&ctrlr, &qpair, &cmd, NULL, 1, NULL, NULL, NULL);
+}
+
+static void
+test_get_log_pages(void)
+{
+ test_generic_get_log_pages();
+ test_intel_get_log_pages();
+}
+
+static void
+test_namespace_attach(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_ctrlr_list payload = {};
+
+ verify_fn = verify_namespace_attach;
+
+ nvme_ctrlr_cmd_attach_ns(&ctrlr, namespace_management_nsid, &payload, NULL, NULL);
+}
+
+static void
+test_namespace_detach(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_ctrlr_list payload = {};
+
+ verify_fn = verify_namespace_detach;
+
+ nvme_ctrlr_cmd_detach_ns(&ctrlr, namespace_management_nsid, &payload, NULL, NULL);
+}
+
+static void
+test_namespace_create(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_ns_data payload = {};
+
+ verify_fn = verify_namespace_create;
+ nvme_ctrlr_cmd_create_ns(&ctrlr, &payload, NULL, NULL);
+}
+
+static void
+test_namespace_delete(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ verify_fn = verify_namespace_delete;
+ nvme_ctrlr_cmd_delete_ns(&ctrlr, namespace_management_nsid, NULL, NULL);
+}
+
+static void
+test_format_nvme(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_format format = {};
+
+ verify_fn = verify_format_nvme;
+
+ nvme_ctrlr_cmd_format(&ctrlr, format_nvme_nsid, &format, NULL, NULL);
+}
+
+static void
+test_fw_commit(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+ struct spdk_nvme_fw_commit fw_commit = {};
+
+ fw_commit.ca = SPDK_NVME_FW_COMMIT_REPLACE_AND_ENABLE_IMG;
+ fw_commit.fs = 1;
+
+ verify_fn = verify_fw_commit;
+
+ nvme_ctrlr_cmd_fw_commit(&ctrlr, &fw_commit, NULL, NULL);
+}
+
+static void
+test_fw_image_download(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ verify_fn = verify_fw_image_download;
+
+ nvme_ctrlr_cmd_fw_image_download(&ctrlr, fw_img_size, fw_img_offset, NULL,
+ NULL, NULL);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvme_ctrlr_cmd", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test ctrlr cmd get_log_pages", test_get_log_pages) == NULL
+ || CU_add_test(suite, "test ctrlr cmd set_feature", test_set_feature_cmd) == NULL
+ || CU_add_test(suite, "test ctrlr cmd set_feature_ns", test_set_feature_ns_cmd) == NULL
+ || CU_add_test(suite, "test ctrlr cmd get_feature", test_get_feature_cmd) == NULL
+ || CU_add_test(suite, "test ctrlr cmd get_feature_ns", test_get_feature_ns_cmd) == NULL
+ || CU_add_test(suite, "test ctrlr cmd abort_cmd", test_abort_cmd) == NULL
+ || CU_add_test(suite, "test ctrlr cmd io_raw_cmd", test_io_raw_cmd) == NULL
+ || CU_add_test(suite, "test ctrlr cmd io_raw_cmd_with_md", test_io_raw_cmd_with_md) == NULL
+ || CU_add_test(suite, "test ctrlr cmd namespace_attach", test_namespace_attach) == NULL
+ || CU_add_test(suite, "test ctrlr cmd namespace_detach", test_namespace_detach) == NULL
+ || CU_add_test(suite, "test ctrlr cmd namespace_create", test_namespace_create) == NULL
+ || CU_add_test(suite, "test ctrlr cmd namespace_delete", test_namespace_delete) == NULL
+ || CU_add_test(suite, "test ctrlr cmd format_nvme", test_format_nvme) == NULL
+ || CU_add_test(suite, "test ctrlr cmd fw_commit", test_fw_commit) == NULL
+ || CU_add_test(suite, "test ctrlr cmd fw_image_download", test_fw_image_download) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/.gitignore
new file mode 100644
index 00000000..2813105d
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/.gitignore
@@ -0,0 +1 @@
+nvme_ctrlr_ocssd_cmd_ut
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/Makefile
new file mode 100644
index 00000000..9446b8d5
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = nvme_ctrlr_ocssd_cmd_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/nvme_ctrlr_ocssd_cmd_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/nvme_ctrlr_ocssd_cmd_ut.c
new file mode 100644
index 00000000..98eccf34
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ctrlr_ocssd_cmd.c/nvme_ctrlr_ocssd_cmd_ut.c
@@ -0,0 +1,116 @@
+/*-
+ * 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_cunit.h"
+
+#include "nvme/nvme_ctrlr_ocssd_cmd.c"
+
+#define DECLARE_AND_CONSTRUCT_CTRLR() \
+ struct spdk_nvme_ctrlr ctrlr = {}; \
+ struct spdk_nvme_qpair adminq = {}; \
+ struct nvme_request req; \
+ \
+ STAILQ_INIT(&adminq.free_req); \
+ STAILQ_INSERT_HEAD(&adminq.free_req, &req, stailq); \
+ ctrlr.adminq = &adminq;
+
+pid_t g_spdk_nvme_pid;
+struct nvme_request g_req;
+typedef void (*verify_request_fn_t)(struct nvme_request *req);
+verify_request_fn_t verify_fn;
+
+static const uint32_t expected_geometry_ns = 1;
+
+int
+nvme_ctrlr_submit_admin_request(struct spdk_nvme_ctrlr *ctrlr, struct nvme_request *req)
+{
+ verify_fn(req);
+ memset(req, 0, sizeof(*req));
+ return 0;
+}
+
+struct nvme_request *
+nvme_allocate_request_user_copy(struct spdk_nvme_qpair *qpair, void *buffer, uint32_t payload_size,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg, bool host_to_controller)
+{
+ /* For the unit test, we don't actually need to copy the buffer */
+ return nvme_allocate_request_contig(qpair, buffer, payload_size, cb_fn, cb_arg);
+}
+
+static void verify_geometry_cmd(struct nvme_request *req)
+{
+ CU_ASSERT(req->cmd.opc == SPDK_OCSSD_OPC_GEOMETRY);
+ CU_ASSERT(req->cmd.nsid == expected_geometry_ns);
+}
+
+static void
+test_geometry_cmd(void)
+{
+ DECLARE_AND_CONSTRUCT_CTRLR();
+
+ struct spdk_ocssd_geometry_data geo;
+
+ verify_fn = verify_geometry_cmd;
+
+ spdk_nvme_ocssd_ctrlr_cmd_geometry(&ctrlr, expected_geometry_ns, &geo,
+ sizeof(geo), NULL, NULL);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvme_ctrlr_cmd", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test ocssd ctrlr geometry cmd ", test_geometry_cmd) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ns.c/.gitignore
new file mode 100644
index 00000000..ada0ec86
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ns.c/.gitignore
@@ -0,0 +1 @@
+nvme_ns_ut
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ns.c/Makefile
new file mode 100644
index 00000000..add85ee9
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ns.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = nvme_ns_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns.c/nvme_ns_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ns.c/nvme_ns_ut.c
new file mode 100644
index 00000000..cdfb4951
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ns.c/nvme_ns_ut.c
@@ -0,0 +1,163 @@
+/*-
+ * 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_cunit.h"
+
+#include "spdk/env.h"
+
+#include "nvme/nvme_ns.c"
+
+#include "common/lib/test_env.c"
+
+SPDK_LOG_REGISTER_COMPONENT("nvme", SPDK_LOG_NVME)
+
+DEFINE_STUB(spdk_nvme_wait_for_completion_robust_lock, int,
+ (struct spdk_nvme_qpair *qpair,
+ struct nvme_completion_poll_status *status,
+ pthread_mutex_t *robust_mutex), 0);
+
+int
+nvme_ctrlr_cmd_identify(struct spdk_nvme_ctrlr *ctrlr, uint8_t cns, uint16_t cntid, uint32_t nsid,
+ void *payload, size_t payload_size,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ return -1;
+}
+
+void
+nvme_completion_poll_cb(void *arg, const struct spdk_nvme_cpl *cpl)
+{
+}
+
+int32_t
+spdk_nvme_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions)
+{
+ return -1;
+}
+
+static void
+test_nvme_ns_construct(void)
+{
+ struct spdk_nvme_ns ns = {};
+ uint32_t id = 1;
+ struct spdk_nvme_ctrlr ctrlr = {};
+
+ nvme_ns_construct(&ns, id, &ctrlr);
+ CU_ASSERT(ns.id == 1);
+}
+
+static void
+test_nvme_ns_uuid(void)
+{
+ struct spdk_nvme_ns ns = {};
+ const struct spdk_uuid *uuid;
+ struct spdk_uuid expected_uuid;
+
+ memset(&expected_uuid, 0xA5, sizeof(expected_uuid));
+
+ /* Empty list - no UUID should be found */
+ memset(ns.id_desc_list, 0, sizeof(ns.id_desc_list));
+ uuid = spdk_nvme_ns_get_uuid(&ns);
+ CU_ASSERT(uuid == NULL);
+
+ /* NGUID only (no UUID in list) */
+ memset(ns.id_desc_list, 0, sizeof(ns.id_desc_list));
+ ns.id_desc_list[0] = 0x02; /* NIDT == NGUID */
+ ns.id_desc_list[1] = 0x10; /* NIDL */
+ memset(&ns.id_desc_list[4], 0xCC, 0x10);
+ uuid = spdk_nvme_ns_get_uuid(&ns);
+ CU_ASSERT(uuid == NULL);
+
+ /* Just UUID in the list */
+ memset(ns.id_desc_list, 0, sizeof(ns.id_desc_list));
+ ns.id_desc_list[0] = 0x03; /* NIDT == UUID */
+ ns.id_desc_list[1] = 0x10; /* NIDL */
+ memcpy(&ns.id_desc_list[4], &expected_uuid, sizeof(expected_uuid));
+ uuid = spdk_nvme_ns_get_uuid(&ns);
+ SPDK_CU_ASSERT_FATAL(uuid != NULL);
+ CU_ASSERT(memcmp(uuid, &expected_uuid, sizeof(*uuid)) == 0);
+
+ /* UUID followed by NGUID */
+ memset(ns.id_desc_list, 0, sizeof(ns.id_desc_list));
+ ns.id_desc_list[0] = 0x03; /* NIDT == UUID */
+ ns.id_desc_list[1] = 0x10; /* NIDL */
+ memcpy(&ns.id_desc_list[4], &expected_uuid, sizeof(expected_uuid));
+ ns.id_desc_list[20] = 0x02; /* NIDT == NGUID */
+ ns.id_desc_list[21] = 0x10; /* NIDL */
+ memset(&ns.id_desc_list[24], 0xCC, 0x10);
+ uuid = spdk_nvme_ns_get_uuid(&ns);
+ SPDK_CU_ASSERT_FATAL(uuid != NULL);
+ CU_ASSERT(memcmp(uuid, &expected_uuid, sizeof(*uuid)) == 0);
+
+ /* NGUID followed by UUID */
+ memset(ns.id_desc_list, 0, sizeof(ns.id_desc_list));
+ ns.id_desc_list[0] = 0x02; /* NIDT == NGUID */
+ ns.id_desc_list[1] = 0x10; /* NIDL */
+ memset(&ns.id_desc_list[4], 0xCC, 0x10);
+ ns.id_desc_list[20] = 0x03; /* NIDT = UUID */
+ ns.id_desc_list[21] = 0x10; /* NIDL */
+ memcpy(&ns.id_desc_list[24], &expected_uuid, sizeof(expected_uuid));
+ uuid = spdk_nvme_ns_get_uuid(&ns);
+ SPDK_CU_ASSERT_FATAL(uuid != NULL);
+ CU_ASSERT(memcmp(uuid, &expected_uuid, sizeof(*uuid)) == 0);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvme", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test_nvme_ns", test_nvme_ns_construct) == NULL ||
+ CU_add_test(suite, "test_nvme_ns_uuid", test_nvme_ns_uuid) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/.gitignore
new file mode 100644
index 00000000..5583ec23
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/.gitignore
@@ -0,0 +1 @@
+nvme_ns_cmd_ut
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/Makefile
new file mode 100644
index 00000000..ff451d72
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = nvme_ns_cmd_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/nvme_ns_cmd_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/nvme_ns_cmd_ut.c
new file mode 100644
index 00000000..f17ffa35
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ns_cmd.c/nvme_ns_cmd_ut.c
@@ -0,0 +1,1440 @@
+/*-
+ * 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_cunit.h"
+
+#include "nvme/nvme_ns_cmd.c"
+#include "nvme/nvme.c"
+
+#include "common/lib/test_env.c"
+
+DEFINE_STUB(spdk_nvme_qpair_process_completions, int32_t,
+ (struct spdk_nvme_qpair *qpair,
+ uint32_t max_completions), 0);
+
+static struct nvme_driver _g_nvme_driver = {
+ .lock = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static struct nvme_request *g_request = NULL;
+
+int
+spdk_pci_nvme_enumerate(spdk_pci_enum_cb enum_cb, void *enum_ctx)
+{
+ return -1;
+}
+
+static void nvme_request_reset_sgl(void *cb_arg, uint32_t sgl_offset)
+{
+}
+
+static int nvme_request_next_sge(void *cb_arg, void **address, uint32_t *length)
+{
+ uint32_t *lba_count = cb_arg;
+
+ /*
+ * We need to set address to something here, since the SGL splitting code will
+ * use it to determine PRP compatibility. Just use a rather arbitrary address
+ * for now - these tests will not actually cause data to be read from or written
+ * to this address.
+ */
+ *address = (void *)(uintptr_t)0x10000000;
+ *length = *lba_count;
+ return 0;
+}
+
+bool
+spdk_nvme_transport_available(enum spdk_nvme_transport_type trtype)
+{
+ return true;
+}
+
+struct spdk_nvme_ctrlr *nvme_transport_ctrlr_construct(const struct spdk_nvme_transport_id *trid,
+ const struct spdk_nvme_ctrlr_opts *opts,
+ void *devhandle)
+{
+ return NULL;
+}
+
+void
+nvme_ctrlr_destruct(struct spdk_nvme_ctrlr *ctrlr)
+{
+}
+
+int
+nvme_ctrlr_add_process(struct spdk_nvme_ctrlr *ctrlr, void *devhandle)
+{
+ return 0;
+}
+
+int
+nvme_ctrlr_process_init(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return 0;
+}
+
+void
+nvme_ctrlr_fail(struct spdk_nvme_ctrlr *ctrlr, bool hot_remove)
+{
+}
+
+struct spdk_pci_addr
+spdk_pci_device_get_addr(struct spdk_pci_device *pci_dev)
+{
+ struct spdk_pci_addr pci_addr;
+
+ memset(&pci_addr, 0, sizeof(pci_addr));
+ return pci_addr;
+}
+
+struct spdk_pci_id
+spdk_pci_device_get_id(struct spdk_pci_device *pci_dev)
+{
+ struct spdk_pci_id pci_id;
+
+ memset(&pci_id, 0xFF, sizeof(pci_id));
+
+ return pci_id;
+}
+
+void
+spdk_nvme_ctrlr_get_default_ctrlr_opts(struct spdk_nvme_ctrlr_opts *opts, size_t opts_size)
+{
+ memset(opts, 0, sizeof(*opts));
+}
+
+uint32_t
+spdk_nvme_ns_get_sector_size(struct spdk_nvme_ns *ns)
+{
+ return ns->sector_size;
+}
+
+uint32_t
+spdk_nvme_ns_get_max_io_xfer_size(struct spdk_nvme_ns *ns)
+{
+ return ns->ctrlr->max_xfer_size;
+}
+
+int
+nvme_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req)
+{
+ g_request = req;
+
+ return 0;
+}
+
+void
+nvme_ctrlr_proc_get_ref(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return;
+}
+
+void
+nvme_ctrlr_proc_put_ref(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return;
+}
+
+int
+nvme_ctrlr_get_ref_count(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return 0;
+}
+
+int
+nvme_transport_ctrlr_scan(const struct spdk_nvme_transport_id *trid,
+ void *cb_ctx,
+ spdk_nvme_probe_cb probe_cb,
+ spdk_nvme_remove_cb remove_cb,
+ bool direct_connect)
+{
+ return 0;
+}
+
+static void
+prepare_for_test(struct spdk_nvme_ns *ns, struct spdk_nvme_ctrlr *ctrlr,
+ struct spdk_nvme_qpair *qpair,
+ uint32_t sector_size, uint32_t md_size, uint32_t max_xfer_size,
+ uint32_t stripe_size, bool extended_lba)
+{
+ uint32_t num_requests = 32;
+ uint32_t i;
+
+ ctrlr->max_xfer_size = max_xfer_size;
+ /*
+ * Clear the flags field - we especially want to make sure the SGL_SUPPORTED flag is not set
+ * so that we test the SGL splitting path.
+ */
+ ctrlr->flags = 0;
+ ctrlr->min_page_size = 4096;
+ ctrlr->page_size = 4096;
+ memset(&ctrlr->opts, 0, sizeof(ctrlr->opts));
+ memset(ns, 0, sizeof(*ns));
+ ns->ctrlr = ctrlr;
+ ns->sector_size = sector_size;
+ ns->extended_lba_size = sector_size;
+ if (extended_lba) {
+ ns->flags |= SPDK_NVME_NS_EXTENDED_LBA_SUPPORTED;
+ ns->extended_lba_size += md_size;
+ }
+ ns->md_size = md_size;
+ ns->sectors_per_max_io = spdk_nvme_ns_get_max_io_xfer_size(ns) / ns->extended_lba_size;
+ ns->sectors_per_stripe = stripe_size / ns->extended_lba_size;
+
+ memset(qpair, 0, sizeof(*qpair));
+ qpair->ctrlr = ctrlr;
+ qpair->req_buf = calloc(num_requests, sizeof(struct nvme_request));
+ SPDK_CU_ASSERT_FATAL(qpair->req_buf != NULL);
+
+ for (i = 0; i < num_requests; i++) {
+ struct nvme_request *req = qpair->req_buf + i * sizeof(struct nvme_request);
+
+ STAILQ_INSERT_HEAD(&qpair->free_req, req, stailq);
+ }
+
+ g_request = NULL;
+}
+
+static void
+cleanup_after_test(struct spdk_nvme_qpair *qpair)
+{
+ free(qpair->req_buf);
+}
+
+static void
+nvme_cmd_interpret_rw(const struct spdk_nvme_cmd *cmd,
+ uint64_t *lba, uint32_t *num_blocks)
+{
+ *lba = *(const uint64_t *)&cmd->cdw10;
+ *num_blocks = (cmd->cdw12 & 0xFFFFu) + 1;
+}
+
+static void
+split_test(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_qpair qpair;
+ struct spdk_nvme_ctrlr ctrlr;
+ void *payload;
+ uint64_t lba, cmd_lba;
+ uint32_t lba_count, cmd_lba_count;
+ int rc;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false);
+ payload = malloc(512);
+ lba = 0;
+ lba_count = 1;
+
+ rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+
+ CU_ASSERT(g_request->num_children == 0);
+ nvme_cmd_interpret_rw(&g_request->cmd, &cmd_lba, &cmd_lba_count);
+ CU_ASSERT(cmd_lba == lba);
+ CU_ASSERT(cmd_lba_count == lba_count);
+
+ free(payload);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+split_test2(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ struct nvme_request *child;
+ void *payload;
+ uint64_t lba, cmd_lba;
+ uint32_t lba_count, cmd_lba_count;
+ int rc;
+
+ /*
+ * Controller has max xfer of 128 KB (256 blocks).
+ * Submit an I/O of 256 KB starting at LBA 0, which should be split
+ * on the max I/O boundary into two I/Os of 128 KB.
+ */
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false);
+ payload = malloc(256 * 1024);
+ lba = 0;
+ lba_count = (256 * 1024) / 512;
+
+ rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+
+ CU_ASSERT(g_request->num_children == 2);
+
+ child = TAILQ_FIRST(&g_request->children);
+ nvme_request_remove_child(g_request, child);
+ nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count);
+ CU_ASSERT(child->num_children == 0);
+ CU_ASSERT(child->payload_size == 128 * 1024);
+ CU_ASSERT(cmd_lba == 0);
+ CU_ASSERT(cmd_lba_count == 256); /* 256 * 512 byte blocks = 128 KB */
+ nvme_free_request(child);
+
+ child = TAILQ_FIRST(&g_request->children);
+ nvme_request_remove_child(g_request, child);
+ nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count);
+ CU_ASSERT(child->num_children == 0);
+ CU_ASSERT(child->payload_size == 128 * 1024);
+ CU_ASSERT(cmd_lba == 256);
+ CU_ASSERT(cmd_lba_count == 256);
+ nvme_free_request(child);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_request->children));
+
+ free(payload);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+split_test3(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ struct nvme_request *child;
+ void *payload;
+ uint64_t lba, cmd_lba;
+ uint32_t lba_count, cmd_lba_count;
+ int rc;
+
+ /*
+ * Controller has max xfer of 128 KB (256 blocks).
+ * Submit an I/O of 256 KB starting at LBA 10, which should be split
+ * into two I/Os:
+ * 1) LBA = 10, count = 256 blocks
+ * 2) LBA = 266, count = 256 blocks
+ */
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false);
+ payload = malloc(256 * 1024);
+ lba = 10; /* Start at an LBA that isn't aligned to the stripe size */
+ lba_count = (256 * 1024) / 512;
+
+ rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 2);
+
+ child = TAILQ_FIRST(&g_request->children);
+ nvme_request_remove_child(g_request, child);
+ nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count);
+ CU_ASSERT(child->num_children == 0);
+ CU_ASSERT(child->payload_size == 128 * 1024);
+ CU_ASSERT(cmd_lba == 10);
+ CU_ASSERT(cmd_lba_count == 256);
+ nvme_free_request(child);
+
+ child = TAILQ_FIRST(&g_request->children);
+ nvme_request_remove_child(g_request, child);
+ nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count);
+ CU_ASSERT(child->num_children == 0);
+ CU_ASSERT(child->payload_size == 128 * 1024);
+ CU_ASSERT(cmd_lba == 266);
+ CU_ASSERT(cmd_lba_count == 256);
+ nvme_free_request(child);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_request->children));
+
+ free(payload);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+split_test4(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ struct nvme_request *child;
+ void *payload;
+ uint64_t lba, cmd_lba;
+ uint32_t lba_count, cmd_lba_count;
+ int rc;
+
+ /*
+ * Controller has max xfer of 128 KB (256 blocks) and a stripe size of 128 KB.
+ * (Same as split_test3 except with driver-assisted striping enabled.)
+ * Submit an I/O of 256 KB starting at LBA 10, which should be split
+ * into three I/Os:
+ * 1) LBA = 10, count = 246 blocks (less than max I/O size to align to stripe size)
+ * 2) LBA = 256, count = 256 blocks (aligned to stripe size and max I/O size)
+ * 3) LBA = 512, count = 10 blocks (finish off the remaining I/O size)
+ */
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 128 * 1024, false);
+ payload = malloc(256 * 1024);
+ lba = 10; /* Start at an LBA that isn't aligned to the stripe size */
+ lba_count = (256 * 1024) / 512;
+
+ rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL,
+ SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 3);
+
+ child = TAILQ_FIRST(&g_request->children);
+ nvme_request_remove_child(g_request, child);
+ nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count);
+ CU_ASSERT(child->num_children == 0);
+ CU_ASSERT(child->payload_size == (256 - 10) * 512);
+ CU_ASSERT(child->payload_offset == 0);
+ CU_ASSERT(cmd_lba == 10);
+ CU_ASSERT(cmd_lba_count == 256 - 10);
+ CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS) != 0);
+ CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_LIMITED_RETRY) == 0);
+ nvme_free_request(child);
+
+ child = TAILQ_FIRST(&g_request->children);
+ nvme_request_remove_child(g_request, child);
+ nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count);
+ CU_ASSERT(child->num_children == 0);
+ CU_ASSERT(child->payload_size == 128 * 1024);
+ CU_ASSERT(child->payload_offset == (256 - 10) * 512);
+ CU_ASSERT(cmd_lba == 256);
+ CU_ASSERT(cmd_lba_count == 256);
+ CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS) != 0);
+ CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_LIMITED_RETRY) == 0);
+ nvme_free_request(child);
+
+ child = TAILQ_FIRST(&g_request->children);
+ nvme_request_remove_child(g_request, child);
+ nvme_cmd_interpret_rw(&child->cmd, &cmd_lba, &cmd_lba_count);
+ CU_ASSERT(child->num_children == 0);
+ CU_ASSERT(child->payload_size == 10 * 512);
+ CU_ASSERT(child->payload_offset == (512 - 10) * 512);
+ CU_ASSERT(cmd_lba == 512);
+ CU_ASSERT(cmd_lba_count == 10);
+ CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS) != 0);
+ CU_ASSERT((child->cmd.cdw12 & SPDK_NVME_IO_FLAGS_LIMITED_RETRY) == 0);
+ nvme_free_request(child);
+
+ CU_ASSERT(TAILQ_EMPTY(&g_request->children));
+
+ free(payload);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_cmd_child_request(void)
+{
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ int rc = 0;
+ struct nvme_request *child, *tmp;
+ void *payload;
+ uint64_t lba = 0x1000;
+ uint32_t i = 0;
+ uint32_t offset = 0;
+ uint32_t sector_size = 512;
+ uint32_t max_io_size = 128 * 1024;
+ uint32_t sectors_per_max_io = max_io_size / sector_size;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_io_size, 0, false);
+
+ payload = malloc(128 * 1024);
+ rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, sectors_per_max_io, NULL, NULL, 0);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->payload_offset == 0);
+ CU_ASSERT(g_request->num_children == 0);
+ nvme_free_request(g_request);
+
+ rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, sectors_per_max_io - 1, NULL, NULL, 0);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->payload_offset == 0);
+ CU_ASSERT(g_request->num_children == 0);
+ nvme_free_request(g_request);
+
+ rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, sectors_per_max_io * 4, NULL, NULL, 0);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->num_children == 4);
+
+ rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, (DEFAULT_IO_QUEUE_REQUESTS + 1) * sector_size,
+ NULL,
+ NULL, 0);
+ SPDK_CU_ASSERT_FATAL(rc == -EINVAL);
+
+ TAILQ_FOREACH_SAFE(child, &g_request->children, child_tailq, tmp) {
+ nvme_request_remove_child(g_request, child);
+ CU_ASSERT(child->payload_offset == offset);
+ CU_ASSERT(child->cmd.opc == SPDK_NVME_OPC_READ);
+ CU_ASSERT(child->cmd.nsid == ns.id);
+ CU_ASSERT(child->cmd.cdw10 == (lba + sectors_per_max_io * i));
+ CU_ASSERT(child->cmd.cdw12 == ((sectors_per_max_io - 1) | 0));
+ offset += max_io_size;
+ nvme_free_request(child);
+ i++;
+ }
+
+ free(payload);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ns_cmd_flush(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ spdk_nvme_cmd_cb cb_fn = NULL;
+ void *cb_arg = NULL;
+ int rc;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false);
+
+ rc = spdk_nvme_ns_cmd_flush(&ns, &qpair, cb_fn, cb_arg);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_FLUSH);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ns_cmd_write_zeroes(void)
+{
+ struct spdk_nvme_ns ns = { 0 };
+ struct spdk_nvme_ctrlr ctrlr = { 0 };
+ struct spdk_nvme_qpair qpair;
+ spdk_nvme_cmd_cb cb_fn = NULL;
+ void *cb_arg = NULL;
+ uint64_t cmd_lba;
+ uint32_t cmd_lba_count;
+ int rc;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false);
+
+ rc = spdk_nvme_ns_cmd_write_zeroes(&ns, &qpair, 0, 2, cb_fn, cb_arg, 0);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_WRITE_ZEROES);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ nvme_cmd_interpret_rw(&g_request->cmd, &cmd_lba, &cmd_lba_count);
+ CU_ASSERT_EQUAL(cmd_lba, 0);
+ CU_ASSERT_EQUAL(cmd_lba_count, 2);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ns_cmd_dataset_management(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ spdk_nvme_cmd_cb cb_fn = NULL;
+ void *cb_arg = NULL;
+ struct spdk_nvme_dsm_range ranges[256];
+ uint16_t i;
+ int rc = 0;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false);
+
+ for (i = 0; i < 256; i++) {
+ ranges[i].starting_lba = i;
+ ranges[i].length = 1;
+ ranges[i].attributes.raw = 0;
+ }
+
+ /* TRIM one LBA */
+ rc = spdk_nvme_ns_cmd_dataset_management(&ns, &qpair, SPDK_NVME_DSM_ATTR_DEALLOCATE,
+ ranges, 1, cb_fn, cb_arg);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_DATASET_MANAGEMENT);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw10 == 0);
+ CU_ASSERT(g_request->cmd.cdw11 == SPDK_NVME_DSM_ATTR_DEALLOCATE);
+ spdk_dma_free(g_request->payload.contig_or_cb_arg);
+ nvme_free_request(g_request);
+
+ /* TRIM 256 LBAs */
+ rc = spdk_nvme_ns_cmd_dataset_management(&ns, &qpair, SPDK_NVME_DSM_ATTR_DEALLOCATE,
+ ranges, 256, cb_fn, cb_arg);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_DATASET_MANAGEMENT);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw10 == 255u);
+ CU_ASSERT(g_request->cmd.cdw11 == SPDK_NVME_DSM_ATTR_DEALLOCATE);
+ spdk_dma_free(g_request->payload.contig_or_cb_arg);
+ nvme_free_request(g_request);
+
+ rc = spdk_nvme_ns_cmd_dataset_management(&ns, &qpair, SPDK_NVME_DSM_ATTR_DEALLOCATE,
+ NULL, 0, cb_fn, cb_arg);
+ CU_ASSERT(rc != 0);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ns_cmd_readv(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ int rc = 0;
+ void *cb_arg;
+ uint32_t lba_count = 256;
+ uint32_t sector_size = 512;
+ uint64_t sge_length = lba_count * sector_size;
+
+ cb_arg = malloc(512);
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, 128 * 1024, 0, false);
+ rc = spdk_nvme_ns_cmd_readv(&ns, &qpair, 0x1000, lba_count, NULL, &sge_length, 0,
+ nvme_request_reset_sgl, nvme_request_next_sge);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_READ);
+ CU_ASSERT(nvme_payload_type(&g_request->payload) == NVME_PAYLOAD_TYPE_SGL);
+ CU_ASSERT(g_request->payload.reset_sgl_fn == nvme_request_reset_sgl);
+ CU_ASSERT(g_request->payload.next_sge_fn == nvme_request_next_sge);
+ CU_ASSERT(g_request->payload.contig_or_cb_arg == &sge_length);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+
+ rc = spdk_nvme_ns_cmd_readv(&ns, &qpair, 0x1000, 256, NULL, cb_arg, 0, nvme_request_reset_sgl,
+ NULL);
+ CU_ASSERT(rc != 0);
+
+ free(cb_arg);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ns_cmd_writev(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ int rc = 0;
+ void *cb_arg;
+ uint32_t lba_count = 256;
+ uint32_t sector_size = 512;
+ uint64_t sge_length = lba_count * sector_size;
+
+ cb_arg = malloc(512);
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, 128 * 1024, 0, false);
+ rc = spdk_nvme_ns_cmd_writev(&ns, &qpair, 0x1000, lba_count, NULL, &sge_length, 0,
+ nvme_request_reset_sgl, nvme_request_next_sge);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_WRITE);
+ CU_ASSERT(nvme_payload_type(&g_request->payload) == NVME_PAYLOAD_TYPE_SGL);
+ CU_ASSERT(g_request->payload.reset_sgl_fn == nvme_request_reset_sgl);
+ CU_ASSERT(g_request->payload.next_sge_fn == nvme_request_next_sge);
+ CU_ASSERT(g_request->payload.contig_or_cb_arg == &sge_length);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+
+ rc = spdk_nvme_ns_cmd_writev(&ns, &qpair, 0x1000, 256, NULL, cb_arg, 0,
+ NULL, nvme_request_next_sge);
+ CU_ASSERT(rc != 0);
+
+ free(cb_arg);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ns_cmd_comparev(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ int rc = 0;
+ void *cb_arg;
+ uint32_t lba_count = 256;
+ uint32_t sector_size = 512;
+ uint64_t sge_length = lba_count * sector_size;
+
+ cb_arg = malloc(512);
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, 128 * 1024, 0, false);
+ rc = spdk_nvme_ns_cmd_comparev(&ns, &qpair, 0x1000, lba_count, NULL, &sge_length, 0,
+ nvme_request_reset_sgl, nvme_request_next_sge);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_COMPARE);
+ CU_ASSERT(nvme_payload_type(&g_request->payload) == NVME_PAYLOAD_TYPE_SGL);
+ CU_ASSERT(g_request->payload.reset_sgl_fn == nvme_request_reset_sgl);
+ CU_ASSERT(g_request->payload.next_sge_fn == nvme_request_next_sge);
+ CU_ASSERT(g_request->payload.contig_or_cb_arg == &sge_length);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+
+ rc = spdk_nvme_ns_cmd_comparev(&ns, &qpair, 0x1000, 256, NULL, cb_arg, 0,
+ nvme_request_reset_sgl, NULL);
+ CU_ASSERT(rc != 0);
+
+ free(cb_arg);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_io_flags(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ void *payload;
+ uint64_t lba;
+ uint32_t lba_count;
+ int rc;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 128 * 1024, false);
+ payload = malloc(256 * 1024);
+ lba = 0;
+ lba_count = (4 * 1024) / 512;
+
+ rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL,
+ SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT((g_request->cmd.cdw12 & SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS) != 0);
+ CU_ASSERT((g_request->cmd.cdw12 & SPDK_NVME_IO_FLAGS_LIMITED_RETRY) == 0);
+ nvme_free_request(g_request);
+
+ rc = spdk_nvme_ns_cmd_read(&ns, &qpair, payload, lba, lba_count, NULL, NULL,
+ SPDK_NVME_IO_FLAGS_LIMITED_RETRY);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT((g_request->cmd.cdw12 & SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS) == 0);
+ CU_ASSERT((g_request->cmd.cdw12 & SPDK_NVME_IO_FLAGS_LIMITED_RETRY) != 0);
+ nvme_free_request(g_request);
+
+ free(payload);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ns_cmd_reservation_register(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ struct spdk_nvme_reservation_register_data *payload;
+ bool ignore_key = 1;
+ spdk_nvme_cmd_cb cb_fn = NULL;
+ void *cb_arg = NULL;
+ int rc = 0;
+ uint32_t tmp_cdw10;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false);
+ payload = malloc(sizeof(struct spdk_nvme_reservation_register_data));
+
+ rc = spdk_nvme_ns_cmd_reservation_register(&ns, &qpair, payload, ignore_key,
+ SPDK_NVME_RESERVE_REGISTER_KEY,
+ SPDK_NVME_RESERVE_PTPL_NO_CHANGES,
+ cb_fn, cb_arg);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_RESERVATION_REGISTER);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+
+ tmp_cdw10 = SPDK_NVME_RESERVE_REGISTER_KEY;
+ tmp_cdw10 |= ignore_key ? 1 << 3 : 0;
+ tmp_cdw10 |= (uint32_t)SPDK_NVME_RESERVE_PTPL_NO_CHANGES << 30;
+
+ CU_ASSERT(g_request->cmd.cdw10 == tmp_cdw10);
+
+ spdk_dma_free(g_request->payload.contig_or_cb_arg);
+ nvme_free_request(g_request);
+ free(payload);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ns_cmd_reservation_release(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ struct spdk_nvme_reservation_key_data *payload;
+ bool ignore_key = 1;
+ spdk_nvme_cmd_cb cb_fn = NULL;
+ void *cb_arg = NULL;
+ int rc = 0;
+ uint32_t tmp_cdw10;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false);
+ payload = malloc(sizeof(struct spdk_nvme_reservation_key_data));
+
+ rc = spdk_nvme_ns_cmd_reservation_release(&ns, &qpair, payload, ignore_key,
+ SPDK_NVME_RESERVE_RELEASE,
+ SPDK_NVME_RESERVE_WRITE_EXCLUSIVE,
+ cb_fn, cb_arg);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_RESERVATION_RELEASE);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+
+ tmp_cdw10 = SPDK_NVME_RESERVE_RELEASE;
+ tmp_cdw10 |= ignore_key ? 1 << 3 : 0;
+ tmp_cdw10 |= (uint32_t)SPDK_NVME_RESERVE_WRITE_EXCLUSIVE << 8;
+
+ CU_ASSERT(g_request->cmd.cdw10 == tmp_cdw10);
+
+ spdk_dma_free(g_request->payload.contig_or_cb_arg);
+ nvme_free_request(g_request);
+ free(payload);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ns_cmd_reservation_acquire(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ struct spdk_nvme_reservation_acquire_data *payload;
+ bool ignore_key = 1;
+ spdk_nvme_cmd_cb cb_fn = NULL;
+ void *cb_arg = NULL;
+ int rc = 0;
+ uint32_t tmp_cdw10;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false);
+ payload = malloc(sizeof(struct spdk_nvme_reservation_acquire_data));
+
+ rc = spdk_nvme_ns_cmd_reservation_acquire(&ns, &qpair, payload, ignore_key,
+ SPDK_NVME_RESERVE_ACQUIRE,
+ SPDK_NVME_RESERVE_WRITE_EXCLUSIVE,
+ cb_fn, cb_arg);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_RESERVATION_ACQUIRE);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+
+ tmp_cdw10 = SPDK_NVME_RESERVE_ACQUIRE;
+ tmp_cdw10 |= ignore_key ? 1 << 3 : 0;
+ tmp_cdw10 |= (uint32_t)SPDK_NVME_RESERVE_WRITE_EXCLUSIVE << 8;
+
+ CU_ASSERT(g_request->cmd.cdw10 == tmp_cdw10);
+
+ spdk_dma_free(g_request->payload.contig_or_cb_arg);
+ nvme_free_request(g_request);
+ free(payload);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ns_cmd_reservation_report(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ struct spdk_nvme_reservation_status_data *payload;
+ spdk_nvme_cmd_cb cb_fn = NULL;
+ void *cb_arg = NULL;
+ int rc = 0;
+ uint32_t size = sizeof(struct spdk_nvme_reservation_status_data);
+
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 0, 128 * 1024, 0, false);
+
+ payload = calloc(1, size);
+ SPDK_CU_ASSERT_FATAL(payload != NULL);
+
+ rc = spdk_nvme_ns_cmd_reservation_report(&ns, &qpair, payload, size, cb_fn, cb_arg);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ CU_ASSERT(g_request->cmd.opc == SPDK_NVME_OPC_RESERVATION_REPORT);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+
+ CU_ASSERT(g_request->cmd.cdw10 == (size / 4));
+
+ spdk_dma_free(g_request->payload.contig_or_cb_arg);
+ nvme_free_request(g_request);
+ free(payload);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ns_cmd_write_with_md(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ int rc = 0;
+ char *buffer = NULL;
+ char *metadata = NULL;
+ uint32_t block_size, md_size;
+ struct nvme_request *child0, *child1;
+
+ block_size = 512;
+ md_size = 128;
+
+ buffer = malloc((block_size + md_size) * 384);
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+ metadata = malloc(md_size * 384);
+ SPDK_CU_ASSERT_FATAL(metadata != NULL);
+
+ /*
+ * 512 byte data + 128 byte metadata
+ * Separate metadata buffer
+ * Max data transfer size 128 KB
+ * No stripe size
+ *
+ * 256 blocks * 512 bytes per block = single 128 KB I/O (no splitting required)
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 128, 128 * 1024, 0, false);
+
+ rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, metadata, 0x1000, 256, NULL, NULL, 0, 0,
+ 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload.md == metadata);
+ CU_ASSERT(g_request->payload_size == 256 * 512);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ /*
+ * 512 byte data + 128 byte metadata
+ * Extended LBA
+ * Max data transfer size 128 KB
+ * No stripe size
+ *
+ * 256 blocks * (512 + 128) bytes per block = two I/Os:
+ * child 0: 204 blocks - 204 * (512 + 128) = 127.5 KB
+ * child 1: 52 blocks
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 128, 128 * 1024, 0, true);
+
+ rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256, NULL, NULL, 0, 0,
+ 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 2);
+ child0 = TAILQ_FIRST(&g_request->children);
+
+ SPDK_CU_ASSERT_FATAL(child0 != NULL);
+ CU_ASSERT(child0->payload.md == NULL);
+ CU_ASSERT(child0->payload_offset == 0);
+ CU_ASSERT(child0->payload_size == 204 * (512 + 128));
+ child1 = TAILQ_NEXT(child0, child_tailq);
+
+ SPDK_CU_ASSERT_FATAL(child1 != NULL);
+ CU_ASSERT(child1->payload.md == NULL);
+ CU_ASSERT(child1->payload_offset == 204 * (512 + 128));
+ CU_ASSERT(child1->payload_size == 52 * (512 + 128));
+
+ nvme_request_free_children(g_request);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ /*
+ * 512 byte data + 8 byte metadata
+ * Extended LBA
+ * Max data transfer size 128 KB
+ * No stripe size
+ * No protection information
+ *
+ * 256 blocks * (512 + 8) bytes per block = two I/Os:
+ * child 0: 252 blocks - 252 * (512 + 8) = 127.96875 KB
+ * child 1: 4 blocks
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, true);
+
+ rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256, NULL, NULL, 0, 0,
+ 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 2);
+ child0 = TAILQ_FIRST(&g_request->children);
+
+ SPDK_CU_ASSERT_FATAL(child0 != NULL);
+ CU_ASSERT(child0->payload.md == NULL);
+ CU_ASSERT(child0->payload_offset == 0);
+ CU_ASSERT(child0->payload_size == 252 * (512 + 8));
+ child1 = TAILQ_NEXT(child0, child_tailq);
+
+ SPDK_CU_ASSERT_FATAL(child1 != NULL);
+ CU_ASSERT(child1->payload.md == NULL);
+ CU_ASSERT(child1->payload_offset == 252 * (512 + 8));
+ CU_ASSERT(child1->payload_size == 4 * (512 + 8));
+
+ nvme_request_free_children(g_request);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ /*
+ * 512 byte data + 8 byte metadata
+ * Extended LBA
+ * Max data transfer size 128 KB
+ * No stripe size
+ * Protection information enabled + PRACT
+ *
+ * Special case for 8-byte metadata + PI + PRACT: no metadata transferred
+ * In theory, 256 blocks * 512 bytes per block = one I/O (128 KB)
+ * However, the splitting code does not account for PRACT when calculating
+ * max sectors per transfer, so we actually get two I/Os:
+ * child 0: 252 blocks
+ * child 1: 4 blocks
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, true);
+ ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED;
+
+ rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256, NULL, NULL,
+ SPDK_NVME_IO_FLAGS_PRACT, 0, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 2);
+ child0 = TAILQ_FIRST(&g_request->children);
+
+ SPDK_CU_ASSERT_FATAL(child0 != NULL);
+ CU_ASSERT(child0->payload_offset == 0);
+ CU_ASSERT(child0->payload_size == 252 * 512); /* NOTE: does not include metadata! */
+ child1 = TAILQ_NEXT(child0, child_tailq);
+
+ SPDK_CU_ASSERT_FATAL(child1 != NULL);
+ CU_ASSERT(child1->payload.md == NULL);
+ CU_ASSERT(child1->payload_offset == 252 * 512);
+ CU_ASSERT(child1->payload_size == 4 * 512);
+
+ nvme_request_free_children(g_request);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ /*
+ * 512 byte data + 8 byte metadata
+ * Separate metadata buffer
+ * Max data transfer size 128 KB
+ * No stripe size
+ * Protection information enabled + PRACT
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, false);
+ ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED;
+
+ rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, metadata, 0x1000, 256, NULL, NULL,
+ SPDK_NVME_IO_FLAGS_PRACT, 0, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload.md == metadata);
+ CU_ASSERT(g_request->payload_size == 256 * 512);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ /*
+ * 512 byte data + 8 byte metadata
+ * Separate metadata buffer
+ * Max data transfer size 128 KB
+ * No stripe size
+ * Protection information enabled + PRACT
+ *
+ * 384 blocks * 512 bytes = two I/Os:
+ * child 0: 256 blocks
+ * child 1: 128 blocks
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, false);
+ ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED;
+
+ rc = spdk_nvme_ns_cmd_write_with_md(&ns, &qpair, buffer, metadata, 0x1000, 384, NULL, NULL,
+ SPDK_NVME_IO_FLAGS_PRACT, 0, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 2);
+ child0 = TAILQ_FIRST(&g_request->children);
+
+ SPDK_CU_ASSERT_FATAL(child0 != NULL);
+ CU_ASSERT(child0->payload_offset == 0);
+ CU_ASSERT(child0->payload_size == 256 * 512);
+ CU_ASSERT(child0->md_offset == 0);
+ child1 = TAILQ_NEXT(child0, child_tailq);
+
+ SPDK_CU_ASSERT_FATAL(child1 != NULL);
+ CU_ASSERT(child1->payload_offset == 256 * 512);
+ CU_ASSERT(child1->payload_size == 128 * 512);
+ CU_ASSERT(child1->md_offset == 256 * 8);
+
+ nvme_request_free_children(g_request);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ free(buffer);
+ free(metadata);
+}
+
+static void
+test_nvme_ns_cmd_read_with_md(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ int rc = 0;
+ char *buffer = NULL;
+ char *metadata = NULL;
+ uint32_t block_size, md_size;
+
+ block_size = 512;
+ md_size = 128;
+
+ buffer = malloc(block_size * 256);
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+ metadata = malloc(md_size * 256);
+ SPDK_CU_ASSERT_FATAL(metadata != NULL);
+
+ /*
+ * 512 byte data + 128 byte metadata
+ * Separate metadata buffer
+ * Max data transfer size 128 KB
+ * No stripe size
+ *
+ * 256 blocks * 512 bytes per block = single 128 KB I/O (no splitting required)
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 128, 128 * 1024, 0, false);
+
+ rc = spdk_nvme_ns_cmd_read_with_md(&ns, &qpair, buffer, metadata, 0x1000, 256, NULL, NULL, 0, 0,
+ 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload.md == metadata);
+ CU_ASSERT(g_request->payload_size == 256 * 512);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+ free(buffer);
+ free(metadata);
+}
+
+static void
+test_nvme_ns_cmd_compare_with_md(void)
+{
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+ int rc = 0;
+ char *buffer = NULL;
+ char *metadata = NULL;
+ uint32_t block_size, md_size;
+ struct nvme_request *child0, *child1;
+
+ block_size = 512;
+ md_size = 128;
+
+ buffer = malloc((block_size + md_size) * 384);
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+ metadata = malloc(md_size * 384);
+ SPDK_CU_ASSERT_FATAL(metadata != NULL);
+
+ /*
+ * 512 byte data + 128 byte metadata
+ * Separate metadata buffer
+ * Max data transfer size 128 KB
+ * No stripe size
+ *
+ * 256 blocks * 512 bytes per block = single 128 KB I/O (no splitting required)
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 128, 128 * 1024, 0, false);
+
+ rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, metadata, 0x1000, 256,
+ NULL, NULL, 0, 0, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload.md == metadata);
+ CU_ASSERT(g_request->payload_size == 256 * 512);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ /*
+ * 512 byte data + 128 byte metadata
+ * Extended LBA
+ * Max data transfer size 128 KB
+ * No stripe size
+ *
+ * 256 blocks * (512 + 128) bytes per block = two I/Os:
+ * child 0: 204 blocks - 204 * (512 + 128) = 127.5 KB
+ * child 1: 52 blocks
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 128, 128 * 1024, 0, true);
+
+ rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256,
+ NULL, NULL, 0, 0, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 2);
+ child0 = TAILQ_FIRST(&g_request->children);
+
+ SPDK_CU_ASSERT_FATAL(child0 != NULL);
+ CU_ASSERT(child0->payload.md == NULL);
+ CU_ASSERT(child0->payload_offset == 0);
+ CU_ASSERT(child0->payload_size == 204 * (512 + 128));
+ child1 = TAILQ_NEXT(child0, child_tailq);
+
+ SPDK_CU_ASSERT_FATAL(child1 != NULL);
+ CU_ASSERT(child1->payload.md == NULL);
+ CU_ASSERT(child1->payload_offset == 204 * (512 + 128));
+ CU_ASSERT(child1->payload_size == 52 * (512 + 128));
+
+ nvme_request_free_children(g_request);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ /*
+ * 512 byte data + 8 byte metadata
+ * Extended LBA
+ * Max data transfer size 128 KB
+ * No stripe size
+ * No protection information
+ *
+ * 256 blocks * (512 + 8) bytes per block = two I/Os:
+ * child 0: 252 blocks - 252 * (512 + 8) = 127.96875 KB
+ * child 1: 4 blocks
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, true);
+
+ rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256,
+ NULL, NULL, 0, 0, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 2);
+ child0 = TAILQ_FIRST(&g_request->children);
+
+ SPDK_CU_ASSERT_FATAL(child0 != NULL);
+ CU_ASSERT(child0->payload.md == NULL);
+ CU_ASSERT(child0->payload_offset == 0);
+ CU_ASSERT(child0->payload_size == 252 * (512 + 8));
+ child1 = TAILQ_NEXT(child0, child_tailq);
+
+ SPDK_CU_ASSERT_FATAL(child1 != NULL);
+ CU_ASSERT(child1->payload.md == NULL);
+ CU_ASSERT(child1->payload_offset == 252 * (512 + 8));
+ CU_ASSERT(child1->payload_size == 4 * (512 + 8));
+
+ nvme_request_free_children(g_request);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ /*
+ * 512 byte data + 8 byte metadata
+ * Extended LBA
+ * Max data transfer size 128 KB
+ * No stripe size
+ * Protection information enabled + PRACT
+ *
+ * Special case for 8-byte metadata + PI + PRACT: no metadata transferred
+ * In theory, 256 blocks * 512 bytes per block = one I/O (128 KB)
+ * However, the splitting code does not account for PRACT when calculating
+ * max sectors per transfer, so we actually get two I/Os:
+ * child 0: 252 blocks
+ * child 1: 4 blocks
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, true);
+ ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED;
+
+ rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, NULL, 0x1000, 256,
+ NULL, NULL, SPDK_NVME_IO_FLAGS_PRACT, 0, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 2);
+ child0 = TAILQ_FIRST(&g_request->children);
+
+ SPDK_CU_ASSERT_FATAL(child0 != NULL);
+ CU_ASSERT(child0->payload_offset == 0);
+ CU_ASSERT(child0->payload_size == 252 * 512); /* NOTE: does not include metadata! */
+ child1 = TAILQ_NEXT(child0, child_tailq);
+
+ SPDK_CU_ASSERT_FATAL(child1 != NULL);
+ CU_ASSERT(child1->payload.md == NULL);
+ CU_ASSERT(child1->payload_offset == 252 * 512);
+ CU_ASSERT(child1->payload_size == 4 * 512);
+
+ nvme_request_free_children(g_request);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ /*
+ * 512 byte data + 8 byte metadata
+ * Separate metadata buffer
+ * Max data transfer size 128 KB
+ * No stripe size
+ * Protection information enabled + PRACT
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, false);
+ ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED;
+
+ rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, metadata, 0x1000, 256,
+ NULL, NULL, SPDK_NVME_IO_FLAGS_PRACT, 0, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload.md == metadata);
+ CU_ASSERT(g_request->payload_size == 256 * 512);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ /*
+ * 512 byte data + 8 byte metadata
+ * Separate metadata buffer
+ * Max data transfer size 128 KB
+ * No stripe size
+ * Protection information enabled + PRACT
+ *
+ * 384 blocks * 512 bytes = two I/Os:
+ * child 0: 256 blocks
+ * child 1: 128 blocks
+ */
+ prepare_for_test(&ns, &ctrlr, &qpair, 512, 8, 128 * 1024, 0, false);
+ ns.flags |= SPDK_NVME_NS_DPS_PI_SUPPORTED;
+
+ rc = spdk_nvme_ns_cmd_compare_with_md(&ns, &qpair, buffer, metadata, 0x1000, 384,
+ NULL, NULL, SPDK_NVME_IO_FLAGS_PRACT, 0, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 2);
+ child0 = TAILQ_FIRST(&g_request->children);
+
+ SPDK_CU_ASSERT_FATAL(child0 != NULL);
+ CU_ASSERT(child0->payload_offset == 0);
+ CU_ASSERT(child0->payload_size == 256 * 512);
+ CU_ASSERT(child0->md_offset == 0);
+ child1 = TAILQ_NEXT(child0, child_tailq);
+
+ SPDK_CU_ASSERT_FATAL(child1 != NULL);
+ CU_ASSERT(child1->payload_offset == 256 * 512);
+ CU_ASSERT(child1->payload_size == 128 * 512);
+ CU_ASSERT(child1->md_offset == 256 * 8);
+
+ nvme_request_free_children(g_request);
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ free(buffer);
+ free(metadata);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvme_ns_cmd", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "split_test", split_test) == NULL
+ || CU_add_test(suite, "split_test2", split_test2) == NULL
+ || CU_add_test(suite, "split_test3", split_test3) == NULL
+ || CU_add_test(suite, "split_test4", split_test4) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_flush", test_nvme_ns_cmd_flush) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_dataset_management",
+ test_nvme_ns_cmd_dataset_management) == NULL
+ || CU_add_test(suite, "io_flags", test_io_flags) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_write_zeroes", test_nvme_ns_cmd_write_zeroes) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_reservation_register",
+ test_nvme_ns_cmd_reservation_register) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_reservation_release",
+ test_nvme_ns_cmd_reservation_release) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_reservation_acquire",
+ test_nvme_ns_cmd_reservation_acquire) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_reservation_report", test_nvme_ns_cmd_reservation_report) == NULL
+ || CU_add_test(suite, "test_cmd_child_request", test_cmd_child_request) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_readv", test_nvme_ns_cmd_readv) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_read_with_md", test_nvme_ns_cmd_read_with_md) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_writev", test_nvme_ns_cmd_writev) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_write_with_md", test_nvme_ns_cmd_write_with_md) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_comparev", test_nvme_ns_cmd_comparev) == NULL
+ || CU_add_test(suite, "nvme_ns_cmd_compare_with_md", test_nvme_ns_cmd_compare_with_md) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ g_spdk_nvme_driver = &_g_nvme_driver;
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/.gitignore
new file mode 100644
index 00000000..8f4f47a1
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/.gitignore
@@ -0,0 +1 @@
+nvme_ns_ocssd_cmd_ut
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/Makefile
new file mode 100644
index 00000000..35fdb83a
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = nvme_ns_ocssd_cmd_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/nvme_ns_ocssd_cmd_ut.c b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/nvme_ns_ocssd_cmd_ut.c
new file mode 100644
index 00000000..2d13b7a6
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_ns_ocssd_cmd.c/nvme_ns_ocssd_cmd_ut.c
@@ -0,0 +1,677 @@
+/*-
+ * 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_cunit.h"
+
+#include "nvme/nvme_ns_ocssd_cmd.c"
+#include "nvme/nvme_ns_cmd.c"
+#include "nvme/nvme.c"
+
+#include "common/lib/test_env.c"
+
+#ifndef PAGE_SIZE
+#define PAGE_SIZE 4096
+#endif
+
+DEFINE_STUB(spdk_nvme_qpair_process_completions, int32_t,
+ (struct spdk_nvme_qpair *qpair,
+ uint32_t max_completions), 0);
+
+static struct nvme_driver _g_nvme_driver = {
+ .lock = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static struct nvme_request *g_request = NULL;
+
+int
+nvme_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req)
+{
+ g_request = req;
+
+ return 0;
+}
+
+void
+nvme_ctrlr_destruct(struct spdk_nvme_ctrlr *ctrlr)
+{
+}
+
+void
+nvme_ctrlr_proc_get_ref(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return;
+}
+
+
+int
+nvme_ctrlr_process_init(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return 0;
+}
+
+void
+nvme_ctrlr_proc_put_ref(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return;
+}
+
+void
+spdk_nvme_ctrlr_get_default_ctrlr_opts(struct spdk_nvme_ctrlr_opts *opts, size_t opts_size)
+{
+ memset(opts, 0, sizeof(*opts));
+}
+
+bool
+spdk_nvme_transport_available(enum spdk_nvme_transport_type trtype)
+{
+ return true;
+}
+
+struct spdk_nvme_ctrlr *nvme_transport_ctrlr_construct(const struct spdk_nvme_transport_id *trid,
+ const struct spdk_nvme_ctrlr_opts *opts,
+ void *devhandle)
+{
+ return NULL;
+}
+
+int
+nvme_ctrlr_get_ref_count(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return 0;
+}
+
+int
+nvme_transport_ctrlr_scan(const struct spdk_nvme_transport_id *trid,
+ void *cb_ctx,
+ spdk_nvme_probe_cb probe_cb,
+ spdk_nvme_remove_cb remove_cb,
+ bool direct_connect)
+{
+ return 0;
+}
+
+uint32_t
+spdk_nvme_ns_get_max_io_xfer_size(struct spdk_nvme_ns *ns)
+{
+ return ns->ctrlr->max_xfer_size;
+}
+
+static void
+prepare_for_test(struct spdk_nvme_ns *ns, struct spdk_nvme_ctrlr *ctrlr,
+ struct spdk_nvme_qpair *qpair,
+ uint32_t sector_size, uint32_t md_size, uint32_t max_xfer_size,
+ uint32_t stripe_size, bool extended_lba)
+{
+ uint32_t num_requests = 32;
+ uint32_t i;
+
+ ctrlr->max_xfer_size = max_xfer_size;
+ /*
+ * Clear the flags field - we especially want to make sure the SGL_SUPPORTED flag is not set
+ * so that we test the SGL splitting path.
+ */
+ ctrlr->flags = 0;
+ ctrlr->min_page_size = PAGE_SIZE;
+ ctrlr->page_size = PAGE_SIZE;
+ memset(&ctrlr->opts, 0, sizeof(ctrlr->opts));
+ memset(ns, 0, sizeof(*ns));
+ ns->ctrlr = ctrlr;
+ ns->sector_size = sector_size;
+ ns->extended_lba_size = sector_size;
+ if (extended_lba) {
+ ns->flags |= SPDK_NVME_NS_EXTENDED_LBA_SUPPORTED;
+ ns->extended_lba_size += md_size;
+ }
+ ns->md_size = md_size;
+ ns->sectors_per_max_io = spdk_nvme_ns_get_max_io_xfer_size(ns) / ns->extended_lba_size;
+ ns->sectors_per_stripe = stripe_size / ns->extended_lba_size;
+
+ memset(qpair, 0, sizeof(*qpair));
+ qpair->ctrlr = ctrlr;
+ qpair->req_buf = calloc(num_requests, sizeof(struct nvme_request));
+ SPDK_CU_ASSERT_FATAL(qpair->req_buf != NULL);
+
+ for (i = 0; i < num_requests; i++) {
+ struct nvme_request *req = qpair->req_buf + i * sizeof(struct nvme_request);
+
+ STAILQ_INSERT_HEAD(&qpair->free_req, req, stailq);
+ }
+
+ g_request = NULL;
+}
+
+static void
+cleanup_after_test(struct spdk_nvme_qpair *qpair)
+{
+ free(qpair->req_buf);
+}
+
+static void
+test_nvme_ocssd_ns_cmd_vector_reset_single_entry(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false);
+ uint64_t lba_list = 0x12345678;
+ spdk_nvme_ocssd_ns_cmd_vector_reset(&ns, &qpair, &lba_list, 1,
+ NULL, NULL, NULL);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_RESET);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw10 == lba_list);
+ CU_ASSERT(g_request->cmd.cdw12 == 0);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ocssd_ns_cmd_vector_reset(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+ const uint32_t vector_size = 0x10;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false);
+ uint64_t lba_list[vector_size];
+ spdk_nvme_ocssd_ns_cmd_vector_reset(&ns, &qpair, lba_list, vector_size,
+ NULL, NULL, NULL);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_RESET);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ocssd_ns_cmd_vector_read_with_md_single_entry(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+ const uint32_t md_size = 0x80;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ char *buffer = malloc(sector_size);
+ char *metadata = malloc(md_size);
+ uint64_t lba_list = 0x12345678;
+
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+ SPDK_CU_ASSERT_FATAL(metadata != NULL);
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, md_size, max_xfer_size, 0, false);
+ rc = spdk_nvme_ocssd_ns_cmd_vector_read_with_md(&ns, &qpair, buffer, metadata,
+ &lba_list, 1, NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload.md == metadata);
+ CU_ASSERT(g_request->payload_size == PAGE_SIZE);
+ CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer);
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_READ);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw10 == lba_list);
+ CU_ASSERT(g_request->cmd.cdw12 == 0);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ free(buffer);
+ free(metadata);
+}
+
+static void
+test_nvme_ocssd_ns_cmd_vector_read_with_md(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+ const uint32_t md_size = 0x80;
+ const uint32_t vector_size = 0x10;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ char *buffer = malloc(sector_size * vector_size);
+ char *metadata = malloc(md_size * vector_size);
+ uint64_t lba_list[vector_size];
+
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+ SPDK_CU_ASSERT_FATAL(metadata != NULL);
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, md_size, max_xfer_size, 0, false);
+ rc = spdk_nvme_ocssd_ns_cmd_vector_read_with_md(&ns, &qpair, buffer, metadata,
+ lba_list, vector_size,
+ NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload.md == metadata);
+ CU_ASSERT(g_request->payload_size == max_xfer_size);
+ CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer);
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_READ);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ free(buffer);
+ free(metadata);
+}
+
+static void
+test_nvme_ocssd_ns_cmd_vector_read_single_entry(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ char *buffer = malloc(sector_size);
+ uint64_t lba_list = 0x12345678;
+
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false);
+ rc = spdk_nvme_ocssd_ns_cmd_vector_read(&ns, &qpair, buffer, &lba_list, 1,
+ NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload_size == PAGE_SIZE);
+ CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer);
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_READ);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw10 == lba_list);
+ CU_ASSERT(g_request->cmd.cdw12 == 0);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+ free(buffer);
+}
+
+static void
+test_nvme_ocssd_ns_cmd_vector_read(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+ const uint32_t vector_size = 0x10;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ char *buffer = malloc(sector_size * vector_size);
+ uint64_t lba_list[vector_size];
+
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false);
+ rc = spdk_nvme_ocssd_ns_cmd_vector_read(&ns, &qpair, buffer, lba_list, vector_size,
+ NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload_size == max_xfer_size);
+ CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer);
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_READ);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+ free(buffer);
+}
+
+static void
+test_nvme_ocssd_ns_cmd_vector_write_with_md_single_entry(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+ const uint32_t md_size = 0x80;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ char *buffer = malloc(sector_size);
+ char *metadata = malloc(md_size);
+ uint64_t lba_list = 0x12345678;
+
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+ SPDK_CU_ASSERT_FATAL(metadata != NULL);
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, md_size, max_xfer_size, 0, false);
+ spdk_nvme_ocssd_ns_cmd_vector_write_with_md(&ns, &qpair, buffer, metadata,
+ &lba_list, 1, NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload.md == metadata);
+ CU_ASSERT(g_request->payload_size == PAGE_SIZE);
+ CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer);
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_WRITE);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw10 == lba_list);
+ CU_ASSERT(g_request->cmd.cdw12 == 0);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ free(buffer);
+ free(metadata);
+}
+
+
+static void
+test_nvme_ocssd_ns_cmd_vector_write_with_md(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+ const uint32_t md_size = 0x80;
+ const uint32_t vector_size = 0x10;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ char *buffer = malloc(sector_size * vector_size);
+ char *metadata = malloc(md_size * vector_size);
+ uint64_t lba_list[vector_size];
+
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+ SPDK_CU_ASSERT_FATAL(metadata != NULL);
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, md_size, max_xfer_size, 0, false);
+ spdk_nvme_ocssd_ns_cmd_vector_write_with_md(&ns, &qpair, buffer, metadata,
+ lba_list, vector_size,
+ NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload.md == metadata);
+ CU_ASSERT(g_request->payload_size == max_xfer_size);
+ CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer);
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_WRITE);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ free(buffer);
+ free(metadata);
+}
+
+static void
+test_nvme_ocssd_ns_cmd_vector_write_single_entry(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ char *buffer = malloc(sector_size);
+ uint64_t lba_list = 0x12345678;
+
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false);
+ spdk_nvme_ocssd_ns_cmd_vector_write(&ns, &qpair, buffer,
+ &lba_list, 1, NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload_size == PAGE_SIZE);
+ CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer);
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_WRITE);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw10 == lba_list);
+ CU_ASSERT(g_request->cmd.cdw12 == 0);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ free(buffer);
+}
+
+static void
+test_nvme_ocssd_ns_cmd_vector_write(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+ const uint32_t vector_size = 0x10;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ char *buffer = malloc(sector_size * vector_size);
+ uint64_t lba_list[vector_size];
+
+ SPDK_CU_ASSERT_FATAL(buffer != NULL);
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false);
+ spdk_nvme_ocssd_ns_cmd_vector_write(&ns, &qpair, buffer,
+ lba_list, vector_size,
+ NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+
+ CU_ASSERT(g_request->payload_size == max_xfer_size);
+ CU_ASSERT(g_request->payload.contig_or_cb_arg == buffer);
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_WRITE);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+
+ free(buffer);
+}
+
+static void
+test_nvme_ocssd_ns_cmd_vector_copy_single_entry(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ uint64_t src_lba_list = 0x12345678;
+ uint64_t dst_lba_list = 0x87654321;
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false);
+ spdk_nvme_ocssd_ns_cmd_vector_copy(&ns, &qpair, &dst_lba_list, &src_lba_list, 1,
+ NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_COPY);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw10 == src_lba_list);
+ CU_ASSERT(g_request->cmd.cdw12 == 0);
+ CU_ASSERT(g_request->cmd.cdw14 == dst_lba_list);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+static void
+test_nvme_ocssd_ns_cmd_vector_copy(void)
+{
+ const uint32_t max_xfer_size = 0x10000;
+ const uint32_t sector_size = 0x1000;
+ const uint32_t vector_size = 0x10;
+
+ struct spdk_nvme_ns ns;
+ struct spdk_nvme_ctrlr ctrlr;
+ struct spdk_nvme_qpair qpair;
+
+ int rc = 0;
+
+ uint64_t src_lba_list[vector_size];
+ uint64_t dst_lba_list[vector_size];
+
+ prepare_for_test(&ns, &ctrlr, &qpair, sector_size, 0, max_xfer_size, 0, false);
+ spdk_nvme_ocssd_ns_cmd_vector_copy(&ns, &qpair,
+ dst_lba_list, src_lba_list, vector_size,
+ NULL, NULL, 0);
+
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ SPDK_CU_ASSERT_FATAL(g_request != NULL);
+ SPDK_CU_ASSERT_FATAL(g_request->num_children == 0);
+ CU_ASSERT(g_request->cmd.opc == SPDK_OCSSD_OPC_VECTOR_COPY);
+ CU_ASSERT(g_request->cmd.nsid == ns.id);
+ CU_ASSERT(g_request->cmd.cdw12 == vector_size - 1);
+
+ nvme_free_request(g_request);
+ cleanup_after_test(&qpair);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvme_ns_cmd", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "nvme_ns_ocssd_cmd_vector_reset", test_nvme_ocssd_ns_cmd_vector_reset) == NULL
+ || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_reset_single_entry",
+ test_nvme_ocssd_ns_cmd_vector_reset_single_entry) == NULL
+ || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_read_with_md",
+ test_nvme_ocssd_ns_cmd_vector_read_with_md) == NULL
+ || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_read_with_md_single_entry",
+ test_nvme_ocssd_ns_cmd_vector_read_with_md_single_entry) == NULL
+ || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_read", test_nvme_ocssd_ns_cmd_vector_read) == NULL
+ || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_read_single_entry",
+ test_nvme_ocssd_ns_cmd_vector_read_single_entry) == NULL
+ || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_write_with_md",
+ test_nvme_ocssd_ns_cmd_vector_write_with_md) == NULL
+ || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_write_with_md_single_entry",
+ test_nvme_ocssd_ns_cmd_vector_write_with_md_single_entry) == NULL
+ || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_write", test_nvme_ocssd_ns_cmd_vector_write) == NULL
+ || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_write_single_entry",
+ test_nvme_ocssd_ns_cmd_vector_write_single_entry) == NULL
+ || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_copy", test_nvme_ocssd_ns_cmd_vector_copy) == NULL
+ || CU_add_test(suite, "nvme_ocssd_ns_cmd_vector_copy_single_entry",
+ test_nvme_ocssd_ns_cmd_vector_copy_single_entry) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ g_spdk_nvme_driver = &_g_nvme_driver;
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvme/nvme_pcie.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_pcie.c/.gitignore
new file mode 100644
index 00000000..8fc29109
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_pcie.c/.gitignore
@@ -0,0 +1 @@
+nvme_pcie_ut
diff --git a/src/spdk/test/unit/lib/nvme/nvme_pcie.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_pcie.c/Makefile
new file mode 100644
index 00000000..09032a93
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_pcie.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = nvme_pcie_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvme/nvme_pcie.c/nvme_pcie_ut.c b/src/spdk/test/unit/lib/nvme/nvme_pcie.c/nvme_pcie_ut.c
new file mode 100644
index 00000000..2bec5865
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_pcie.c/nvme_pcie_ut.c
@@ -0,0 +1,861 @@
+/*-
+ * 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_cunit.h"
+
+#include "common/lib/test_env.c"
+
+#include "nvme/nvme_pcie.c"
+
+pid_t g_spdk_nvme_pid;
+
+DEFINE_STUB(spdk_mem_register, int, (void *vaddr, size_t len), 0);
+DEFINE_STUB(spdk_mem_unregister, int, (void *vaddr, size_t len), 0);
+
+DEFINE_STUB(spdk_nvme_ctrlr_get_process,
+ struct spdk_nvme_ctrlr_process *,
+ (struct spdk_nvme_ctrlr *ctrlr, pid_t pid),
+ NULL);
+
+DEFINE_STUB(spdk_nvme_ctrlr_get_current_process,
+ struct spdk_nvme_ctrlr_process *,
+ (struct spdk_nvme_ctrlr *ctrlr),
+ NULL);
+
+DEFINE_STUB(spdk_nvme_wait_for_completion, int,
+ (struct spdk_nvme_qpair *qpair,
+ struct nvme_completion_poll_status *status), 0);
+
+struct spdk_trace_flag SPDK_LOG_NVME = {
+ .name = "nvme",
+ .enabled = false,
+};
+
+static struct nvme_driver _g_nvme_driver = {
+ .lock = PTHREAD_MUTEX_INITIALIZER,
+};
+struct nvme_driver *g_spdk_nvme_driver = &_g_nvme_driver;
+
+int32_t spdk_nvme_retry_count = 1;
+
+struct nvme_request *g_request = NULL;
+
+extern bool ut_fail_vtophys;
+
+bool fail_next_sge = false;
+
+struct io_request {
+ uint64_t address_offset;
+ bool invalid_addr;
+ bool invalid_second_addr;
+};
+
+void
+nvme_ctrlr_fail(struct spdk_nvme_ctrlr *ctrlr, bool hot_remove)
+{
+ abort();
+}
+
+int
+spdk_uevent_connect(void)
+{
+ abort();
+}
+
+int
+spdk_get_uevent(int fd, struct spdk_uevent *uevent)
+{
+ abort();
+}
+
+struct spdk_pci_id
+spdk_pci_device_get_id(struct spdk_pci_device *dev)
+{
+ abort();
+}
+
+int
+nvme_qpair_init(struct spdk_nvme_qpair *qpair, uint16_t id,
+ struct spdk_nvme_ctrlr *ctrlr,
+ enum spdk_nvme_qprio qprio,
+ uint32_t num_requests)
+{
+ abort();
+}
+
+void
+nvme_qpair_deinit(struct spdk_nvme_qpair *qpair)
+{
+ abort();
+}
+
+int
+spdk_pci_nvme_enumerate(spdk_pci_enum_cb enum_cb, void *enum_ctx)
+{
+ abort();
+}
+
+int
+spdk_pci_nvme_device_attach(spdk_pci_enum_cb enum_cb, void *enum_ctx,
+ struct spdk_pci_addr *pci_address)
+{
+ abort();
+}
+
+void
+spdk_pci_device_detach(struct spdk_pci_device *device)
+{
+ abort();
+}
+
+int
+spdk_pci_device_map_bar(struct spdk_pci_device *dev, uint32_t bar,
+ void **mapped_addr, uint64_t *phys_addr, uint64_t *size)
+{
+ abort();
+}
+
+int
+spdk_pci_device_unmap_bar(struct spdk_pci_device *dev, uint32_t bar, void *addr)
+{
+ abort();
+}
+
+struct spdk_pci_addr
+spdk_pci_device_get_addr(struct spdk_pci_device *dev)
+{
+ abort();
+}
+
+int
+spdk_pci_device_cfg_read32(struct spdk_pci_device *dev, uint32_t *value, uint32_t offset)
+{
+ abort();
+}
+
+int
+spdk_pci_device_cfg_write32(struct spdk_pci_device *dev, uint32_t value, uint32_t offset)
+{
+ abort();
+}
+
+int
+spdk_pci_device_claim(const struct spdk_pci_addr *pci_addr)
+{
+ abort();
+}
+
+int
+nvme_ctrlr_construct(struct spdk_nvme_ctrlr *ctrlr)
+{
+ abort();
+}
+
+void
+nvme_ctrlr_destruct_finish(struct spdk_nvme_ctrlr *ctrlr)
+{
+ abort();
+}
+
+void
+nvme_ctrlr_destruct(struct spdk_nvme_ctrlr *ctrlr)
+{
+ abort();
+}
+
+int
+nvme_ctrlr_add_process(struct spdk_nvme_ctrlr *ctrlr, void *devhandle)
+{
+ abort();
+}
+
+void
+nvme_ctrlr_free_processes(struct spdk_nvme_ctrlr *ctrlr)
+{
+ abort();
+}
+
+struct spdk_pci_device *
+nvme_ctrlr_proc_get_devhandle(struct spdk_nvme_ctrlr *ctrlr)
+{
+ abort();
+}
+
+int
+nvme_ctrlr_probe(const struct spdk_nvme_transport_id *trid, void *devhandle,
+ spdk_nvme_probe_cb probe_cb, void *cb_ctx)
+{
+ abort();
+}
+
+int
+nvme_ctrlr_get_cap(struct spdk_nvme_ctrlr *ctrlr, union spdk_nvme_cap_register *cap)
+{
+ abort();
+}
+
+int
+nvme_ctrlr_get_vs(struct spdk_nvme_ctrlr *ctrlr, union spdk_nvme_vs_register *vs)
+{
+ abort();
+}
+
+void
+nvme_ctrlr_init_cap(struct spdk_nvme_ctrlr *ctrlr, const union spdk_nvme_cap_register *cap,
+ const union spdk_nvme_vs_register *vs)
+{
+ abort();
+}
+
+uint64_t
+nvme_get_quirks(const struct spdk_pci_id *id)
+{
+ abort();
+}
+
+bool
+nvme_completion_is_retry(const struct spdk_nvme_cpl *cpl)
+{
+ abort();
+}
+
+void
+nvme_qpair_print_command(struct spdk_nvme_qpair *qpair, struct spdk_nvme_cmd *cmd)
+{
+ abort();
+}
+
+void
+nvme_qpair_print_completion(struct spdk_nvme_qpair *qpair, struct spdk_nvme_cpl *cpl)
+{
+ abort();
+}
+
+int
+nvme_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req)
+{
+ abort();
+}
+
+int
+nvme_ctrlr_submit_admin_request(struct spdk_nvme_ctrlr *ctrlr,
+ struct nvme_request *req)
+{
+ abort();
+}
+
+void
+nvme_completion_poll_cb(void *arg, const struct spdk_nvme_cpl *cpl)
+{
+ abort();
+}
+
+int32_t
+spdk_nvme_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions)
+{
+ abort();
+}
+
+void
+nvme_qpair_enable(struct spdk_nvme_qpair *qpair)
+{
+ abort();
+}
+
+int
+nvme_request_check_timeout(struct nvme_request *req, uint16_t cid,
+ struct spdk_nvme_ctrlr_process *active_proc,
+ uint64_t now_tick)
+{
+ abort();
+}
+
+struct spdk_nvme_ctrlr *
+spdk_nvme_get_ctrlr_by_trid_unsafe(const struct spdk_nvme_transport_id *trid)
+{
+ return NULL;
+}
+
+union spdk_nvme_csts_register spdk_nvme_ctrlr_get_regs_csts(struct spdk_nvme_ctrlr *ctrlr)
+{
+ union spdk_nvme_csts_register csts = {};
+
+ return csts;
+}
+
+#if 0 /* TODO: update PCIe-specific unit test */
+static void
+nvme_request_reset_sgl(void *cb_arg, uint32_t sgl_offset)
+{
+ struct io_request *req = (struct io_request *)cb_arg;
+
+ req->address_offset = 0;
+ req->invalid_addr = false;
+ req->invalid_second_addr = false;
+ switch (sgl_offset) {
+ case 0:
+ req->invalid_addr = false;
+ break;
+ case 1:
+ req->invalid_addr = true;
+ break;
+ case 2:
+ req->invalid_addr = false;
+ req->invalid_second_addr = true;
+ break;
+ default:
+ break;
+ }
+ return;
+}
+
+static int
+nvme_request_next_sge(void *cb_arg, void **address, uint32_t *length)
+{
+ struct io_request *req = (struct io_request *)cb_arg;
+
+ if (req->address_offset == 0) {
+ if (req->invalid_addr) {
+ *address = (void *)7;
+ } else {
+ *address = (void *)(4096 * req->address_offset);
+ }
+ } else if (req->address_offset == 1) {
+ if (req->invalid_second_addr) {
+ *address = (void *)7;
+ } else {
+ *address = (void *)(4096 * req->address_offset);
+ }
+ } else {
+ *address = (void *)(4096 * req->address_offset);
+ }
+
+ req->address_offset += 1;
+ *length = 4096;
+
+ if (fail_next_sge) {
+ return - 1;
+ } else {
+ return 0;
+ }
+
+}
+
+static void
+prepare_submit_request_test(struct spdk_nvme_qpair *qpair,
+ struct spdk_nvme_ctrlr *ctrlr)
+{
+ memset(ctrlr, 0, sizeof(*ctrlr));
+ ctrlr->free_io_qids = NULL;
+ TAILQ_INIT(&ctrlr->active_io_qpairs);
+ TAILQ_INIT(&ctrlr->active_procs);
+ nvme_qpair_init(qpair, 1, ctrlr, 0);
+
+ ut_fail_vtophys = false;
+}
+
+static void
+cleanup_submit_request_test(struct spdk_nvme_qpair *qpair)
+{
+}
+
+static void
+ut_insert_cq_entry(struct spdk_nvme_qpair *qpair, uint32_t slot)
+{
+ struct nvme_request *req;
+ struct nvme_tracker *tr;
+ struct spdk_nvme_cpl *cpl;
+
+ req = calloc(1, sizeof(*req));
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+ memset(req, 0, sizeof(*req));
+
+ tr = TAILQ_FIRST(&qpair->free_tr);
+ TAILQ_REMOVE(&qpair->free_tr, tr, tq_list); /* remove tr from free_tr */
+ TAILQ_INSERT_HEAD(&qpair->outstanding_tr, tr, tq_list);
+ req->cmd.cid = tr->cid;
+ tr->req = req;
+ qpair->tr[tr->cid].active = true;
+
+ cpl = &qpair->cpl[slot];
+ cpl->status.p = qpair->phase;
+ cpl->cid = tr->cid;
+}
+
+static void
+expected_success_callback(void *arg, const struct spdk_nvme_cpl *cpl)
+{
+ CU_ASSERT(!spdk_nvme_cpl_is_error(cpl));
+}
+
+static void
+expected_failure_callback(void *arg, const struct spdk_nvme_cpl *cpl)
+{
+ CU_ASSERT(spdk_nvme_cpl_is_error(cpl));
+}
+
+static void
+test4(void)
+{
+ struct spdk_nvme_qpair qpair = {};
+ struct nvme_request *req;
+ struct spdk_nvme_ctrlr ctrlr = {};
+ char payload[4096];
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+
+ req = nvme_allocate_request_contig(payload, sizeof(payload), expected_failure_callback, NULL);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+
+ /* Force vtophys to return a failure. This should
+ * result in the nvme_qpair manually failing
+ * the request with error status to signify
+ * a bad payload buffer.
+ */
+ ut_fail_vtophys = true;
+
+ CU_ASSERT(qpair.sq_tail == 0);
+
+ CU_ASSERT(nvme_qpair_submit_request(&qpair, req) != 0);
+
+ CU_ASSERT(qpair.sq_tail == 0);
+
+ cleanup_submit_request_test(&qpair);
+}
+
+static void
+test_sgl_req(void)
+{
+ struct spdk_nvme_qpair qpair = {};
+ struct nvme_request *req;
+ struct spdk_nvme_ctrlr ctrlr = {};
+ struct nvme_payload payload = {};
+ struct nvme_tracker *sgl_tr = NULL;
+ uint64_t i;
+ struct io_request io_req = {};
+
+ payload = NVME_PAYLOAD_SGL(nvme_request_reset_sgl, nvme_request_next_sge, &io_req, NULL);
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+ req = nvme_allocate_request(&payload, 0x1000, NULL, &io_req);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+ req->cmd.opc = SPDK_NVME_OPC_WRITE;
+ req->cmd.cdw10 = 10000;
+ req->cmd.cdw12 = 7 | 0;
+ req->payload_offset = 1;
+
+ CU_ASSERT(nvme_qpair_submit_request(&qpair, req) != 0);
+ CU_ASSERT(qpair.sq_tail == 0);
+ cleanup_submit_request_test(&qpair);
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+ req = nvme_allocate_request(&payload, 0x1000, NULL, &io_req);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+ req->cmd.opc = SPDK_NVME_OPC_WRITE;
+ req->cmd.cdw10 = 10000;
+ req->cmd.cdw12 = 7 | 0;
+ spdk_nvme_retry_count = 1;
+ fail_next_sge = true;
+
+ CU_ASSERT(nvme_qpair_submit_request(&qpair, req) != 0);
+ CU_ASSERT(qpair.sq_tail == 0);
+ cleanup_submit_request_test(&qpair);
+
+ fail_next_sge = false;
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+ req = nvme_allocate_request(&payload, 2 * 0x1000, NULL, &io_req);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+ req->cmd.opc = SPDK_NVME_OPC_WRITE;
+ req->cmd.cdw10 = 10000;
+ req->cmd.cdw12 = 15 | 0;
+ req->payload_offset = 2;
+
+ CU_ASSERT(nvme_qpair_submit_request(&qpair, req) != 0);
+ CU_ASSERT(qpair.sq_tail == 0);
+ cleanup_submit_request_test(&qpair);
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+ req = nvme_allocate_request(&payload, (NVME_MAX_PRP_LIST_ENTRIES + 1) * 0x1000, NULL, &io_req);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+ req->cmd.opc = SPDK_NVME_OPC_WRITE;
+ req->cmd.cdw10 = 10000;
+ req->cmd.cdw12 = 4095 | 0;
+
+ CU_ASSERT(nvme_qpair_submit_request(&qpair, req) == 0);
+
+ CU_ASSERT(req->cmd.dptr.prp.prp1 == 0);
+ CU_ASSERT(qpair.sq_tail == 1);
+ sgl_tr = TAILQ_FIRST(&qpair.outstanding_tr);
+ if (sgl_tr != NULL) {
+ for (i = 0; i < NVME_MAX_PRP_LIST_ENTRIES; i++) {
+ CU_ASSERT(sgl_tr->u.prp[i] == (0x1000 * (i + 1)));
+ }
+
+ TAILQ_REMOVE(&qpair.outstanding_tr, sgl_tr, tq_list);
+ }
+ cleanup_submit_request_test(&qpair);
+ nvme_free_request(req);
+}
+
+static void
+test_hw_sgl_req(void)
+{
+ struct spdk_nvme_qpair qpair = {};
+ struct nvme_request *req;
+ struct spdk_nvme_ctrlr ctrlr = {};
+ struct nvme_payload payload = {};
+ struct nvme_tracker *sgl_tr = NULL;
+ uint64_t i;
+ struct io_request io_req = {};
+
+ payload = NVME_PAYLOAD_SGL(nvme_request_reset_sgl, nvme_request_next_sge, &io_req, NULL);
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+ req = nvme_allocate_request(&payload, 0x1000, NULL, &io_req);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+ req->cmd.opc = SPDK_NVME_OPC_WRITE;
+ req->cmd.cdw10 = 10000;
+ req->cmd.cdw12 = 7 | 0;
+ req->payload_offset = 0;
+ ctrlr.flags |= SPDK_NVME_CTRLR_SGL_SUPPORTED;
+
+ nvme_qpair_submit_request(&qpair, req);
+
+ sgl_tr = TAILQ_FIRST(&qpair.outstanding_tr);
+ CU_ASSERT(sgl_tr != NULL);
+ CU_ASSERT(sgl_tr->u.sgl[0].generic.type == SPDK_NVME_SGL_TYPE_DATA_BLOCK);
+ CU_ASSERT(sgl_tr->u.sgl[0].generic.subtype == 0);
+ CU_ASSERT(sgl_tr->u.sgl[0].unkeyed.length == 4096);
+ CU_ASSERT(sgl_tr->u.sgl[0].address == 0);
+ CU_ASSERT(req->cmd.dptr.sgl1.generic.type == SPDK_NVME_SGL_TYPE_DATA_BLOCK);
+ TAILQ_REMOVE(&qpair.outstanding_tr, sgl_tr, tq_list);
+ cleanup_submit_request_test(&qpair);
+ nvme_free_request(req);
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+ req = nvme_allocate_request(&payload, NVME_MAX_SGL_DESCRIPTORS * 0x1000, NULL, &io_req);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+ req->cmd.opc = SPDK_NVME_OPC_WRITE;
+ req->cmd.cdw10 = 10000;
+ req->cmd.cdw12 = 2023 | 0;
+ req->payload_offset = 0;
+ ctrlr.flags |= SPDK_NVME_CTRLR_SGL_SUPPORTED;
+
+ nvme_qpair_submit_request(&qpair, req);
+
+ sgl_tr = TAILQ_FIRST(&qpair.outstanding_tr);
+ CU_ASSERT(sgl_tr != NULL);
+ for (i = 0; i < NVME_MAX_SGL_DESCRIPTORS; i++) {
+ CU_ASSERT(sgl_tr->u.sgl[i].generic.type == SPDK_NVME_SGL_TYPE_DATA_BLOCK);
+ CU_ASSERT(sgl_tr->u.sgl[i].generic.subtype == 0);
+ CU_ASSERT(sgl_tr->u.sgl[i].unkeyed.length == 4096);
+ CU_ASSERT(sgl_tr->u.sgl[i].address == i * 4096);
+ }
+ CU_ASSERT(req->cmd.dptr.sgl1.generic.type == SPDK_NVME_SGL_TYPE_LAST_SEGMENT);
+ TAILQ_REMOVE(&qpair.outstanding_tr, sgl_tr, tq_list);
+ cleanup_submit_request_test(&qpair);
+ nvme_free_request(req);
+}
+
+static void test_nvme_qpair_fail(void)
+{
+ struct spdk_nvme_qpair qpair = {};
+ struct nvme_request *req = NULL;
+ struct spdk_nvme_ctrlr ctrlr = {};
+ struct nvme_tracker *tr_temp;
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+
+ tr_temp = TAILQ_FIRST(&qpair.free_tr);
+ SPDK_CU_ASSERT_FATAL(tr_temp != NULL);
+ TAILQ_REMOVE(&qpair.free_tr, tr_temp, tq_list);
+ tr_temp->req = nvme_allocate_request_null(expected_failure_callback, NULL);
+ SPDK_CU_ASSERT_FATAL(tr_temp->req != NULL);
+ tr_temp->req->cmd.cid = tr_temp->cid;
+
+ TAILQ_INSERT_HEAD(&qpair.outstanding_tr, tr_temp, tq_list);
+ nvme_qpair_fail(&qpair);
+ CU_ASSERT_TRUE(TAILQ_EMPTY(&qpair.outstanding_tr));
+
+ req = nvme_allocate_request_null(expected_failure_callback, NULL);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+
+ STAILQ_INSERT_HEAD(&qpair.queued_req, req, stailq);
+ nvme_qpair_fail(&qpair);
+ CU_ASSERT_TRUE(STAILQ_EMPTY(&qpair.queued_req));
+
+ cleanup_submit_request_test(&qpair);
+}
+
+static void
+test_nvme_qpair_process_completions_limit(void)
+{
+ struct spdk_nvme_qpair qpair = {};
+ struct spdk_nvme_ctrlr ctrlr = {};
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+ qpair.is_enabled = true;
+
+ /* Insert 4 entries into the completion queue */
+ CU_ASSERT(qpair.cq_head == 0);
+ ut_insert_cq_entry(&qpair, 0);
+ ut_insert_cq_entry(&qpair, 1);
+ ut_insert_cq_entry(&qpair, 2);
+ ut_insert_cq_entry(&qpair, 3);
+
+ /* This should only process 2 completions, and 2 should be left in the queue */
+ spdk_nvme_qpair_process_completions(&qpair, 2);
+ CU_ASSERT(qpair.cq_head == 2);
+
+ /* This should only process 1 completion, and 1 should be left in the queue */
+ spdk_nvme_qpair_process_completions(&qpair, 1);
+ CU_ASSERT(qpair.cq_head == 3);
+
+ /* This should process the remaining completion */
+ spdk_nvme_qpair_process_completions(&qpair, 5);
+ CU_ASSERT(qpair.cq_head == 4);
+
+ cleanup_submit_request_test(&qpair);
+}
+
+static void test_nvme_qpair_destroy(void)
+{
+ struct spdk_nvme_qpair qpair = {};
+ struct spdk_nvme_ctrlr ctrlr = {};
+ struct nvme_tracker *tr_temp;
+
+ memset(&ctrlr, 0, sizeof(ctrlr));
+ TAILQ_INIT(&ctrlr.free_io_qpairs);
+ TAILQ_INIT(&ctrlr.active_io_qpairs);
+ TAILQ_INIT(&ctrlr.active_procs);
+
+ nvme_qpair_init(&qpair, 1, 128, &ctrlr);
+ nvme_qpair_destroy(&qpair);
+
+
+ nvme_qpair_init(&qpair, 0, 128, &ctrlr);
+ tr_temp = TAILQ_FIRST(&qpair.free_tr);
+ SPDK_CU_ASSERT_FATAL(tr_temp != NULL);
+ TAILQ_REMOVE(&qpair.free_tr, tr_temp, tq_list);
+ tr_temp->req = nvme_allocate_request_null(expected_failure_callback, NULL);
+ SPDK_CU_ASSERT_FATAL(tr_temp->req != NULL);
+
+ tr_temp->req->cmd.opc = SPDK_NVME_OPC_ASYNC_EVENT_REQUEST;
+ tr_temp->req->cmd.cid = tr_temp->cid;
+ TAILQ_INSERT_HEAD(&qpair.outstanding_tr, tr_temp, tq_list);
+
+ nvme_qpair_destroy(&qpair);
+ CU_ASSERT(TAILQ_EMPTY(&qpair.outstanding_tr));
+}
+#endif
+
+static void
+prp_list_prep(struct nvme_tracker *tr, struct nvme_request *req, uint32_t *prp_index)
+{
+ memset(req, 0, sizeof(*req));
+ memset(tr, 0, sizeof(*tr));
+ tr->req = req;
+ tr->prp_sgl_bus_addr = 0xDEADBEEF;
+ *prp_index = 0;
+}
+
+static void
+test_prp_list_append(void)
+{
+ struct nvme_request req;
+ struct nvme_tracker tr;
+ uint32_t prp_index;
+
+ /* Non-DWORD-aligned buffer (invalid) */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100001, 0x1000, 0x1000) == -EINVAL);
+
+ /* 512-byte buffer, 4K aligned */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x200, 0x1000) == 0);
+ CU_ASSERT(prp_index == 1);
+ CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100000);
+
+ /* 512-byte buffer, non-4K-aligned */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x108000, 0x200, 0x1000) == 0);
+ CU_ASSERT(prp_index == 1);
+ CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x108000);
+
+ /* 4K buffer, 4K aligned */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x1000, 0x1000) == 0);
+ CU_ASSERT(prp_index == 1);
+ CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100000);
+
+ /* 4K buffer, non-4K aligned */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, 0x1000, 0x1000) == 0);
+ CU_ASSERT(prp_index == 2);
+ CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100800);
+ CU_ASSERT(req.cmd.dptr.prp.prp2 == 0x101000);
+
+ /* 8K buffer, 4K aligned */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x2000, 0x1000) == 0);
+ CU_ASSERT(prp_index == 2);
+ CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100000);
+ CU_ASSERT(req.cmd.dptr.prp.prp2 == 0x101000);
+
+ /* 8K buffer, non-4K aligned */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, 0x2000, 0x1000) == 0);
+ CU_ASSERT(prp_index == 3);
+ CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100800);
+ CU_ASSERT(req.cmd.dptr.prp.prp2 == tr.prp_sgl_bus_addr);
+ CU_ASSERT(tr.u.prp[0] == 0x101000);
+ CU_ASSERT(tr.u.prp[1] == 0x102000);
+
+ /* 12K buffer, 4K aligned */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x3000, 0x1000) == 0);
+ CU_ASSERT(prp_index == 3);
+ CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100000);
+ CU_ASSERT(req.cmd.dptr.prp.prp2 == tr.prp_sgl_bus_addr);
+ CU_ASSERT(tr.u.prp[0] == 0x101000);
+ CU_ASSERT(tr.u.prp[1] == 0x102000);
+
+ /* 12K buffer, non-4K aligned */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, 0x3000, 0x1000) == 0);
+ CU_ASSERT(prp_index == 4);
+ CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100800);
+ CU_ASSERT(req.cmd.dptr.prp.prp2 == tr.prp_sgl_bus_addr);
+ CU_ASSERT(tr.u.prp[0] == 0x101000);
+ CU_ASSERT(tr.u.prp[1] == 0x102000);
+ CU_ASSERT(tr.u.prp[2] == 0x103000);
+
+ /* Two 4K buffers, both 4K aligned */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x1000, 0x1000) == 0);
+ CU_ASSERT(prp_index == 1);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x900000, 0x1000, 0x1000) == 0);
+ CU_ASSERT(prp_index == 2);
+ CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100000);
+ CU_ASSERT(req.cmd.dptr.prp.prp2 == 0x900000);
+
+ /* Two 4K buffers, first non-4K aligned, second 4K aligned */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, 0x1000, 0x1000) == 0);
+ CU_ASSERT(prp_index == 2);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x900000, 0x1000, 0x1000) == 0);
+ CU_ASSERT(prp_index == 3);
+ CU_ASSERT(req.cmd.dptr.prp.prp1 == 0x100800);
+ CU_ASSERT(req.cmd.dptr.prp.prp2 == tr.prp_sgl_bus_addr);
+ CU_ASSERT(tr.u.prp[0] == 0x101000);
+ CU_ASSERT(tr.u.prp[1] == 0x900000);
+
+ /* Two 4K buffers, both non-4K aligned (invalid) */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800, 0x1000, 0x1000) == 0);
+ CU_ASSERT(prp_index == 2);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x900800, 0x1000, 0x1000) == -EINVAL);
+ CU_ASSERT(prp_index == 2);
+
+ /* 4K buffer, 4K aligned, but vtophys fails */
+ MOCK_SET(spdk_vtophys, SPDK_VTOPHYS_ERROR);
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000, 0x1000, 0x1000) == -EINVAL);
+ MOCK_CLEAR(spdk_vtophys);
+
+ /* Largest aligned buffer that can be described in NVME_MAX_PRP_LIST_ENTRIES (plus PRP1) */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000,
+ (NVME_MAX_PRP_LIST_ENTRIES + 1) * 0x1000, 0x1000) == 0);
+ CU_ASSERT(prp_index == NVME_MAX_PRP_LIST_ENTRIES + 1);
+
+ /* Largest non-4K-aligned buffer that can be described in NVME_MAX_PRP_LIST_ENTRIES (plus PRP1) */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800,
+ NVME_MAX_PRP_LIST_ENTRIES * 0x1000, 0x1000) == 0);
+ CU_ASSERT(prp_index == NVME_MAX_PRP_LIST_ENTRIES + 1);
+
+ /* Buffer too large to be described in NVME_MAX_PRP_LIST_ENTRIES */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100000,
+ (NVME_MAX_PRP_LIST_ENTRIES + 2) * 0x1000, 0x1000) == -EINVAL);
+
+ /* Non-4K-aligned buffer too large to be described in NVME_MAX_PRP_LIST_ENTRIES */
+ prp_list_prep(&tr, &req, &prp_index);
+ CU_ASSERT(nvme_pcie_prp_list_append(&tr, &prp_index, (void *)0x100800,
+ (NVME_MAX_PRP_LIST_ENTRIES + 1) * 0x1000, 0x1000) == -EINVAL);
+}
+
+static void test_shadow_doorbell_update(void)
+{
+ bool ret;
+
+ /* nvme_pcie_qpair_need_event(uint16_t event_idx, uint16_t new_idx, uint16_t old) */
+ ret = nvme_pcie_qpair_need_event(10, 15, 14);
+ CU_ASSERT(ret == false);
+
+ ret = nvme_pcie_qpair_need_event(14, 15, 14);
+ CU_ASSERT(ret == true);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvme_pcie", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (CU_add_test(suite, "prp_list_append", test_prp_list_append) == NULL
+ || CU_add_test(suite, "shadow_doorbell_update",
+ test_shadow_doorbell_update) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvme/nvme_qpair.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_qpair.c/.gitignore
new file mode 100644
index 00000000..1bb18e99
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_qpair.c/.gitignore
@@ -0,0 +1 @@
+nvme_qpair_ut
diff --git a/src/spdk/test/unit/lib/nvme/nvme_qpair.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_qpair.c/Makefile
new file mode 100644
index 00000000..d7762a38
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_qpair.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = nvme_qpair_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvme/nvme_qpair.c/nvme_qpair_ut.c b/src/spdk/test/unit/lib/nvme/nvme_qpair.c/nvme_qpair_ut.c
new file mode 100644
index 00000000..11fea8c7
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_qpair.c/nvme_qpair_ut.c
@@ -0,0 +1,418 @@
+/*-
+ * 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_cunit.h"
+
+#include "common/lib/test_env.c"
+
+pid_t g_spdk_nvme_pid;
+
+bool trace_flag = false;
+#define SPDK_LOG_NVME trace_flag
+
+#include "nvme/nvme_qpair.c"
+
+struct nvme_driver _g_nvme_driver = {
+ .lock = PTHREAD_MUTEX_INITIALIZER,
+};
+
+void
+nvme_request_remove_child(struct nvme_request *parent,
+ struct nvme_request *child)
+{
+ parent->num_children--;
+ TAILQ_REMOVE(&parent->children, child, child_tailq);
+}
+
+int
+nvme_transport_qpair_enable(struct spdk_nvme_qpair *qpair)
+{
+ return 0;
+}
+
+int
+nvme_transport_qpair_disable(struct spdk_nvme_qpair *qpair)
+{
+ return 0;
+}
+
+int
+nvme_transport_qpair_fail(struct spdk_nvme_qpair *qpair)
+{
+ return 0;
+}
+
+int
+nvme_transport_qpair_submit_request(struct spdk_nvme_qpair *qpair, struct nvme_request *req)
+{
+ // TODO
+ return 0;
+}
+
+int32_t
+nvme_transport_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions)
+{
+ // TODO
+ return 0;
+}
+
+int
+spdk_nvme_ctrlr_free_io_qpair(struct spdk_nvme_qpair *qpair)
+{
+ return 0;
+}
+
+static void
+prepare_submit_request_test(struct spdk_nvme_qpair *qpair,
+ struct spdk_nvme_ctrlr *ctrlr)
+{
+ memset(ctrlr, 0, sizeof(*ctrlr));
+ ctrlr->free_io_qids = NULL;
+ TAILQ_INIT(&ctrlr->active_io_qpairs);
+ TAILQ_INIT(&ctrlr->active_procs);
+ nvme_qpair_init(qpair, 1, ctrlr, 0, 32);
+}
+
+static void
+cleanup_submit_request_test(struct spdk_nvme_qpair *qpair)
+{
+ free(qpair->req_buf);
+}
+
+static void
+expected_success_callback(void *arg, const struct spdk_nvme_cpl *cpl)
+{
+ CU_ASSERT(!spdk_nvme_cpl_is_error(cpl));
+}
+
+static void
+expected_failure_callback(void *arg, const struct spdk_nvme_cpl *cpl)
+{
+ CU_ASSERT(spdk_nvme_cpl_is_error(cpl));
+}
+
+static void
+test3(void)
+{
+ struct spdk_nvme_qpair qpair = {};
+ struct nvme_request *req;
+ struct spdk_nvme_ctrlr ctrlr = {};
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+
+ req = nvme_allocate_request_null(&qpair, expected_success_callback, NULL);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+
+ CU_ASSERT(nvme_qpair_submit_request(&qpair, req) == 0);
+
+ nvme_free_request(req);
+
+ cleanup_submit_request_test(&qpair);
+}
+
+static void
+test_ctrlr_failed(void)
+{
+ struct spdk_nvme_qpair qpair = {};
+ struct nvme_request *req;
+ struct spdk_nvme_ctrlr ctrlr = {};
+ char payload[4096];
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+
+ req = nvme_allocate_request_contig(&qpair, payload, sizeof(payload), expected_failure_callback,
+ NULL);
+ SPDK_CU_ASSERT_FATAL(req != NULL);
+
+ /* Set the controller to failed.
+ * Set the controller to resetting so that the qpair won't get re-enabled.
+ */
+ ctrlr.is_failed = true;
+ ctrlr.is_resetting = true;
+
+ CU_ASSERT(nvme_qpair_submit_request(&qpair, req) != 0);
+
+ cleanup_submit_request_test(&qpair);
+}
+
+static void struct_packing(void)
+{
+ /* ctrlr is the first field in nvme_qpair after the fields
+ * that are used in the I/O path. Make sure the I/O path fields
+ * all fit into two cache lines.
+ */
+ CU_ASSERT(offsetof(struct spdk_nvme_qpair, ctrlr) <= 128);
+}
+
+static void test_nvme_qpair_process_completions(void)
+{
+ struct spdk_nvme_qpair qpair = {};
+ struct spdk_nvme_ctrlr ctrlr = {};
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+ qpair.ctrlr->is_resetting = true;
+
+ spdk_nvme_qpair_process_completions(&qpair, 0);
+ cleanup_submit_request_test(&qpair);
+}
+
+static void test_nvme_completion_is_retry(void)
+{
+ struct spdk_nvme_cpl cpl = {};
+
+ cpl.status.sct = SPDK_NVME_SCT_GENERIC;
+ cpl.status.sc = SPDK_NVME_SC_NAMESPACE_NOT_READY;
+ cpl.status.dnr = 0;
+ CU_ASSERT_TRUE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_FORMAT_IN_PROGRESS;
+ cpl.status.dnr = 1;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+ cpl.status.dnr = 0;
+ CU_ASSERT_TRUE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_INVALID_OPCODE;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_INVALID_FIELD;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_COMMAND_ID_CONFLICT;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_DATA_TRANSFER_ERROR;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_ABORTED_POWER_LOSS;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_INTERNAL_DEVICE_ERROR;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_ABORTED_BY_REQUEST;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_ABORTED_FAILED_FUSED;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_ABORTED_MISSING_FUSED;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_COMMAND_SEQUENCE_ERROR;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_INVALID_SGL_SEG_DESCRIPTOR;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_INVALID_NUM_SGL_DESCIRPTORS;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_DATA_SGL_LENGTH_INVALID;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_METADATA_SGL_LENGTH_INVALID;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_SGL_DESCRIPTOR_TYPE_INVALID;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_INVALID_CONTROLLER_MEM_BUF;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_INVALID_PRP_OFFSET;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_ATOMIC_WRITE_UNIT_EXCEEDED;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_LBA_OUT_OF_RANGE;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_CAPACITY_EXCEEDED;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = SPDK_NVME_SC_RESERVATION_CONFLICT;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sc = 0x70;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sct = SPDK_NVME_SCT_MEDIA_ERROR;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sct = SPDK_NVME_SCT_PATH;
+ cpl.status.sc = SPDK_NVME_SC_INTERNAL_PATH_ERROR;
+ cpl.status.dnr = 0;
+ CU_ASSERT_TRUE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sct = SPDK_NVME_SCT_PATH;
+ cpl.status.sc = SPDK_NVME_SC_INTERNAL_PATH_ERROR;
+ cpl.status.dnr = 1;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sct = SPDK_NVME_SCT_VENDOR_SPECIFIC;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+
+ cpl.status.sct = 0x4;
+ CU_ASSERT_FALSE(nvme_completion_is_retry(&cpl));
+}
+
+#ifdef DEBUG
+static void
+test_get_status_string(void)
+{
+ const char *status_string;
+
+ status_string = get_status_string(SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_SUCCESS);
+ CU_ASSERT(strcmp(status_string, "SUCCESS") == 0);
+
+ status_string = get_status_string(SPDK_NVME_SCT_COMMAND_SPECIFIC,
+ SPDK_NVME_SC_COMPLETION_QUEUE_INVALID);
+ CU_ASSERT(strcmp(status_string, "INVALID COMPLETION QUEUE") == 0);
+
+ status_string = get_status_string(SPDK_NVME_SCT_MEDIA_ERROR, SPDK_NVME_SC_UNRECOVERED_READ_ERROR);
+ CU_ASSERT(strcmp(status_string, "UNRECOVERED READ ERROR") == 0);
+
+ status_string = get_status_string(SPDK_NVME_SCT_VENDOR_SPECIFIC, 0);
+ CU_ASSERT(strcmp(status_string, "VENDOR SPECIFIC") == 0);
+
+ status_string = get_status_string(100, 0);
+ CU_ASSERT(strcmp(status_string, "RESERVED") == 0);
+}
+#endif
+
+static void
+test_nvme_qpair_add_cmd_error_injection(void)
+{
+ struct spdk_nvme_qpair qpair = {};
+ struct spdk_nvme_ctrlr ctrlr = {};
+ int rc;
+
+ prepare_submit_request_test(&qpair, &ctrlr);
+ ctrlr.adminq = &qpair;
+
+ /* Admin error injection at submission path */
+ rc = spdk_nvme_qpair_add_cmd_error_injection(&ctrlr, NULL,
+ SPDK_NVME_OPC_GET_FEATURES, true, 5000, 1,
+ SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_INVALID_FIELD);
+
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(!TAILQ_EMPTY(&qpair.err_cmd_head));
+
+ /* Remove cmd error injection */
+ spdk_nvme_qpair_remove_cmd_error_injection(&ctrlr, NULL, SPDK_NVME_OPC_GET_FEATURES);
+
+ CU_ASSERT(TAILQ_EMPTY(&qpair.err_cmd_head));
+
+ /* IO error injection at completion path */
+ rc = spdk_nvme_qpair_add_cmd_error_injection(&ctrlr, &qpair,
+ SPDK_NVME_OPC_READ, false, 0, 1,
+ SPDK_NVME_SCT_MEDIA_ERROR, SPDK_NVME_SC_UNRECOVERED_READ_ERROR);
+
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(!TAILQ_EMPTY(&qpair.err_cmd_head));
+
+ /* Provide the same opc, and check whether allocate a new entry */
+ rc = spdk_nvme_qpair_add_cmd_error_injection(&ctrlr, &qpair,
+ SPDK_NVME_OPC_READ, false, 0, 1,
+ SPDK_NVME_SCT_MEDIA_ERROR, SPDK_NVME_SC_UNRECOVERED_READ_ERROR);
+
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&qpair.err_cmd_head));
+ CU_ASSERT(TAILQ_NEXT(TAILQ_FIRST(&qpair.err_cmd_head), link) == NULL);
+
+ /* Remove cmd error injection */
+ spdk_nvme_qpair_remove_cmd_error_injection(&ctrlr, &qpair, SPDK_NVME_OPC_READ);
+
+ CU_ASSERT(TAILQ_EMPTY(&qpair.err_cmd_head));
+
+ rc = spdk_nvme_qpair_add_cmd_error_injection(&ctrlr, &qpair,
+ SPDK_NVME_OPC_COMPARE, true, 0, 5,
+ SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_COMPARE_FAILURE);
+
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(!TAILQ_EMPTY(&qpair.err_cmd_head));
+
+ /* Remove cmd error injection */
+ spdk_nvme_qpair_remove_cmd_error_injection(&ctrlr, &qpair, SPDK_NVME_OPC_COMPARE);
+
+ CU_ASSERT(TAILQ_EMPTY(&qpair.err_cmd_head));
+
+ cleanup_submit_request_test(&qpair);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvme_qpair", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (CU_add_test(suite, "test3", test3) == NULL
+ || CU_add_test(suite, "ctrlr_failed", test_ctrlr_failed) == NULL
+ || CU_add_test(suite, "struct_packing", struct_packing) == NULL
+ || CU_add_test(suite, "spdk_nvme_qpair_process_completions",
+ test_nvme_qpair_process_completions) == NULL
+ || CU_add_test(suite, "nvme_completion_is_retry", test_nvme_completion_is_retry) == NULL
+#ifdef DEBUG
+ || CU_add_test(suite, "get_status_string", test_get_status_string) == NULL
+#endif
+ || CU_add_test(suite, "spdk_nvme_qpair_add_cmd_error_injection",
+ test_nvme_qpair_add_cmd_error_injection) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvme/nvme_quirks.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_quirks.c/.gitignore
new file mode 100644
index 00000000..eca86651
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_quirks.c/.gitignore
@@ -0,0 +1 @@
+nvme_quirks_ut
diff --git a/src/spdk/test/unit/lib/nvme/nvme_quirks.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_quirks.c/Makefile
new file mode 100644
index 00000000..d86887f0
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_quirks.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = nvme_quirks_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvme/nvme_quirks.c/nvme_quirks_ut.c b/src/spdk/test/unit/lib/nvme/nvme_quirks.c/nvme_quirks_ut.c
new file mode 100644
index 00000000..95fdd143
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_quirks.c/nvme_quirks_ut.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_cunit.h"
+
+#include "nvme/nvme_quirks.c"
+
+SPDK_LOG_REGISTER_COMPONENT("nvme", SPDK_LOG_NVME)
+
+static void
+test_nvme_quirks_striping(void)
+{
+ struct spdk_pci_id pci_id = {};
+ uint64_t quirks = 0;
+
+ /* Non-Intel device should not have striping enabled */
+ quirks = nvme_get_quirks(&pci_id);
+ CU_ASSERT((quirks & NVME_INTEL_QUIRK_STRIPING) == 0);
+
+ /* Set the vendor id to Intel, but no device id. No striping. */
+ pci_id.vendor_id = SPDK_PCI_VID_INTEL;
+ quirks = nvme_get_quirks(&pci_id);
+ CU_ASSERT((quirks & NVME_INTEL_QUIRK_STRIPING) == 0);
+
+ /* Device ID 0x0953 should have striping enabled */
+ pci_id.device_id = 0x0953;
+ quirks = nvme_get_quirks(&pci_id);
+ CU_ASSERT((quirks & NVME_INTEL_QUIRK_STRIPING) != 0);
+
+ /* Even if specific subvendor/subdevice ids are set,
+ * striping should be enabled.
+ */
+ pci_id.subvendor_id = SPDK_PCI_VID_INTEL;
+ pci_id.subdevice_id = 0x3704;
+ quirks = nvme_get_quirks(&pci_id);
+ CU_ASSERT((quirks & NVME_INTEL_QUIRK_STRIPING) != 0);
+
+ pci_id.subvendor_id = 1234;
+ pci_id.subdevice_id = 42;
+ quirks = nvme_get_quirks(&pci_id);
+ CU_ASSERT((quirks & NVME_INTEL_QUIRK_STRIPING) != 0);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvme_quirks", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test nvme_quirks striping",
+ test_nvme_quirks_striping) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvme/nvme_rdma.c/.gitignore b/src/spdk/test/unit/lib/nvme/nvme_rdma.c/.gitignore
new file mode 100644
index 00000000..66265b95
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_rdma.c/.gitignore
@@ -0,0 +1 @@
+nvme_rdma_ut
diff --git a/src/spdk/test/unit/lib/nvme/nvme_rdma.c/Makefile b/src/spdk/test/unit/lib/nvme/nvme_rdma.c/Makefile
new file mode 100644
index 00000000..7ea42632
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_rdma.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = nvme_rdma_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvme/nvme_rdma.c/nvme_rdma_ut.c b/src/spdk/test/unit/lib/nvme/nvme_rdma.c/nvme_rdma_ut.c
new file mode 100644
index 00000000..87835ab6
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvme/nvme_rdma.c/nvme_rdma_ut.c
@@ -0,0 +1,298 @@
+/*-
+ * 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_cunit.h"
+#include "common/lib/test_env.c"
+#include "nvme/nvme_rdma.c"
+
+SPDK_LOG_REGISTER_COMPONENT("nvme", SPDK_LOG_NVME)
+
+DEFINE_STUB(nvme_qpair_submit_request, int, (struct spdk_nvme_qpair *qpair,
+ struct nvme_request *req), 0);
+
+DEFINE_STUB(nvme_qpair_init, int, (struct spdk_nvme_qpair *qpair, uint16_t id,
+ struct spdk_nvme_ctrlr *ctrlr, enum spdk_nvme_qprio qprio, uint32_t num_requests), 0);
+
+DEFINE_STUB_V(nvme_qpair_deinit, (struct spdk_nvme_qpair *qpair));
+
+DEFINE_STUB(nvme_ctrlr_probe, int, (const struct spdk_nvme_transport_id *trid, void *devhandle,
+ spdk_nvme_probe_cb probe_cb, void *cb_ctx), 0);
+
+DEFINE_STUB(nvme_ctrlr_get_cap, int, (struct spdk_nvme_ctrlr *ctrlr,
+ union spdk_nvme_cap_register *cap), 0);
+
+DEFINE_STUB(nvme_ctrlr_get_vs, int, (struct spdk_nvme_ctrlr *ctrlr,
+ union spdk_nvme_vs_register *vs), 0);
+
+DEFINE_STUB_V(nvme_ctrlr_init_cap, (struct spdk_nvme_ctrlr *ctrlr,
+ const union spdk_nvme_cap_register *cap, const union spdk_nvme_vs_register *vs));
+
+DEFINE_STUB(nvme_ctrlr_construct, int, (struct spdk_nvme_ctrlr *ctrlr), 0);
+
+DEFINE_STUB_V(nvme_ctrlr_destruct, (struct spdk_nvme_ctrlr *ctrlr));
+
+DEFINE_STUB(nvme_ctrlr_add_process, int, (struct spdk_nvme_ctrlr *ctrlr, void *devhandle), 0);
+
+DEFINE_STUB_V(nvme_ctrlr_connected, (struct spdk_nvme_ctrlr *ctrlr));
+
+DEFINE_STUB(nvme_ctrlr_cmd_identify, int, (struct spdk_nvme_ctrlr *ctrlr, uint8_t cns,
+ uint16_t cntid, uint32_t nsid, void *payload, size_t payload_size, spdk_nvme_cmd_cb cb_fn,
+ void *cb_arg), 0);
+
+DEFINE_STUB_V(spdk_nvme_ctrlr_get_default_ctrlr_opts, (struct spdk_nvme_ctrlr_opts *opts,
+ size_t opts_size));
+
+DEFINE_STUB_V(nvme_completion_poll_cb, (void *arg, const struct spdk_nvme_cpl *cpl));
+
+DEFINE_STUB(spdk_nvme_ctrlr_get_current_process, struct spdk_nvme_ctrlr_process *,
+ (struct spdk_nvme_ctrlr *ctrlr), NULL);
+
+DEFINE_STUB(spdk_nvme_wait_for_completion, int, (struct spdk_nvme_qpair *qpair,
+ struct nvme_completion_poll_status *status), 0);
+
+DEFINE_STUB(spdk_nvme_wait_for_completion_robust_lock, int, (struct spdk_nvme_qpair *qpair,
+ struct nvme_completion_poll_status *status, pthread_mutex_t *robust_mutex), 0);
+
+DEFINE_STUB(spdk_mem_map_set_translation, int, (struct spdk_mem_map *map, uint64_t vaddr,
+ uint64_t size, uint64_t translation), 0);
+
+DEFINE_STUB(spdk_mem_map_clear_translation, int, (struct spdk_mem_map *map, uint64_t vaddr,
+ uint64_t size), 0);
+
+DEFINE_STUB(spdk_mem_map_alloc, struct spdk_mem_map *, (uint64_t default_translation,
+ const struct spdk_mem_map_ops *ops, void *cb_ctx), NULL);
+
+DEFINE_STUB_V(spdk_mem_map_free, (struct spdk_mem_map **pmap));
+
+DEFINE_STUB(nvme_fabric_qpair_connect, int, (struct spdk_nvme_qpair *qpair, uint32_t num_entries),
+ 0);
+
+DEFINE_STUB(nvme_transport_ctrlr_set_reg_4, int, (struct spdk_nvme_ctrlr *ctrlr, uint32_t offset,
+ uint32_t value), 0);
+
+DEFINE_STUB(nvme_fabric_ctrlr_set_reg_4, int, (struct spdk_nvme_ctrlr *ctrlr, uint32_t offset,
+ uint32_t value), 0);
+
+DEFINE_STUB(nvme_fabric_ctrlr_set_reg_8, int, (struct spdk_nvme_ctrlr *ctrlr, uint32_t offset,
+ uint64_t value), 0);
+
+DEFINE_STUB(nvme_fabric_ctrlr_get_reg_4, int, (struct spdk_nvme_ctrlr *ctrlr, uint32_t offset,
+ uint32_t *value), 0);
+
+DEFINE_STUB(nvme_fabric_ctrlr_get_reg_8, int, (struct spdk_nvme_ctrlr *ctrlr, uint32_t offset,
+ uint64_t *value), 0);
+
+DEFINE_STUB_V(nvme_ctrlr_destruct_finish, (struct spdk_nvme_ctrlr *ctrlr));
+
+DEFINE_STUB(nvme_request_check_timeout, int, (struct nvme_request *req, uint16_t cid,
+ struct spdk_nvme_ctrlr_process *active_proc, uint64_t now_tick), 0);
+
+DEFINE_STUB(nvme_fabric_ctrlr_discover, int, (struct spdk_nvme_ctrlr *ctrlr, void *cb_ctx,
+ spdk_nvme_probe_cb probe_cb), 0);
+
+/* used to mock out having to split an SGL over a memory region */
+uint64_t g_mr_size;
+struct ibv_mr g_nvme_rdma_mr;
+
+uint64_t
+spdk_mem_map_translate(const struct spdk_mem_map *map, uint64_t vaddr, uint64_t *size)
+{
+ if (g_mr_size != 0) {
+ *(uint32_t *)size = g_mr_size;
+ }
+
+ return (uint64_t)&g_nvme_rdma_mr;
+}
+
+struct nvme_rdma_ut_bdev_io {
+ struct iovec iovs[NVME_RDMA_MAX_SGL_DESCRIPTORS];
+ int iovpos;
+};
+
+/* essentially a simplification of bdev_nvme_next_sge and bdev_nvme_reset_sgl */
+static void nvme_rdma_ut_reset_sgl(void *cb_arg, uint32_t offset)
+{
+ struct nvme_rdma_ut_bdev_io *bio = cb_arg;
+ struct iovec *iov;
+
+ for (bio->iovpos = 0; bio->iovpos < NVME_RDMA_MAX_SGL_DESCRIPTORS; bio->iovpos++) {
+ iov = &bio->iovs[bio->iovpos];
+ /* Only provide offsets at the beginning of an iov */
+ if (offset == 0) {
+ break;
+ }
+
+ offset -= iov->iov_len;
+ }
+
+ SPDK_CU_ASSERT_FATAL(bio->iovpos < NVME_RDMA_MAX_SGL_DESCRIPTORS);
+}
+
+static int nvme_rdma_ut_next_sge(void *cb_arg, void **address, uint32_t *length)
+{
+ struct nvme_rdma_ut_bdev_io *bio = cb_arg;
+ struct iovec *iov;
+
+ SPDK_CU_ASSERT_FATAL(bio->iovpos < NVME_RDMA_MAX_SGL_DESCRIPTORS);
+
+ iov = &bio->iovs[bio->iovpos];
+
+ *address = iov->iov_base;
+ *length = iov->iov_len;
+ bio->iovpos++;
+
+ return 0;
+}
+
+static void
+test_nvme_rdma_build_sgl_request(void)
+{
+ struct nvme_rdma_qpair rqpair;
+ struct spdk_nvme_ctrlr ctrlr = {0};
+ struct spdk_nvmf_cmd cmd = {{0}};
+ struct spdk_nvme_rdma_req rdma_req = {0};
+ struct nvme_request req = {{0}};
+ struct nvme_rdma_ut_bdev_io bio;
+ struct spdk_nvme_rdma_mr_map rmap = {0};
+ struct spdk_mem_map *map = NULL;
+ uint64_t i;
+ int rc;
+
+ rmap.map = map;
+
+ ctrlr.max_sges = NVME_RDMA_MAX_SGL_DESCRIPTORS;
+
+ rqpair.mr_map = &rmap;
+ rqpair.qpair.ctrlr = &ctrlr;
+ rqpair.cmds = &cmd;
+ cmd.sgl[0].address = 0x1111;
+
+ rdma_req.id = 0;
+ rdma_req.req = &req;
+
+ req.payload.reset_sgl_fn = nvme_rdma_ut_reset_sgl;
+ req.payload.next_sge_fn = nvme_rdma_ut_next_sge;
+ req.payload.contig_or_cb_arg = &bio;
+ req.qpair = &rqpair.qpair;
+
+ g_nvme_rdma_mr.rkey = 1;
+
+ for (i = 0; i < NVME_RDMA_MAX_SGL_DESCRIPTORS; i++) {
+ bio.iovs[i].iov_base = (void *)i;
+ bio.iovs[i].iov_len = 0;
+ }
+
+ /* Test case 1: single SGL. Expected: PASS */
+ bio.iovpos = 0;
+ req.payload_offset = 0;
+ req.payload_size = 0x1000;
+ bio.iovs[0].iov_len = 0x1000;
+ rc = nvme_rdma_build_sgl_request(&rqpair, &rdma_req);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ CU_ASSERT(bio.iovpos == 1);
+ CU_ASSERT(req.cmd.dptr.sgl1.keyed.type == SPDK_NVME_SGL_TYPE_KEYED_DATA_BLOCK);
+ CU_ASSERT(req.cmd.dptr.sgl1.keyed.subtype == SPDK_NVME_SGL_SUBTYPE_ADDRESS);
+ CU_ASSERT(req.cmd.dptr.sgl1.keyed.length == req.payload_size);
+ CU_ASSERT(req.cmd.dptr.sgl1.keyed.key == g_nvme_rdma_mr.rkey);
+ CU_ASSERT(req.cmd.dptr.sgl1.address == (uint64_t)bio.iovs[0].iov_base);
+ CU_ASSERT(rdma_req.send_sgl[0].length == sizeof(struct spdk_nvme_cmd));
+
+ /* Test case 2: multiple SGL. Expected: PASS */
+ bio.iovpos = 0;
+ req.payload_offset = 0;
+ req.payload_size = 0x4000;
+ for (i = 0; i < 4; i++) {
+ bio.iovs[i].iov_len = 0x1000;
+ }
+ rc = nvme_rdma_build_sgl_request(&rqpair, &rdma_req);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+ CU_ASSERT(bio.iovpos == 4);
+ CU_ASSERT(req.cmd.dptr.sgl1.unkeyed.type == SPDK_NVME_SGL_TYPE_LAST_SEGMENT);
+ CU_ASSERT(req.cmd.dptr.sgl1.unkeyed.subtype == SPDK_NVME_SGL_SUBTYPE_OFFSET);
+ CU_ASSERT(req.cmd.dptr.sgl1.unkeyed.length == 4 * sizeof(struct spdk_nvme_sgl_descriptor));
+ CU_ASSERT(req.cmd.dptr.sgl1.address == (uint64_t)0);
+ CU_ASSERT(rdma_req.send_sgl[0].length == 4 * sizeof(struct spdk_nvme_sgl_descriptor) + sizeof(
+ struct spdk_nvme_cmd))
+ for (i = 0; i < 4; i++) {
+ CU_ASSERT(cmd.sgl[i].keyed.type == SPDK_NVME_SGL_TYPE_KEYED_DATA_BLOCK);
+ CU_ASSERT(cmd.sgl[i].keyed.subtype == SPDK_NVME_SGL_SUBTYPE_ADDRESS);
+ CU_ASSERT(cmd.sgl[i].keyed.length == bio.iovs[i].iov_len);
+ CU_ASSERT(cmd.sgl[i].keyed.key == g_nvme_rdma_mr.rkey);
+ CU_ASSERT(cmd.sgl[i].address == (uint64_t)bio.iovs[i].iov_base);
+ }
+
+ /* Test case 3: Multiple SGL, SGL larger than mr size. Expected: FAIL */
+ bio.iovpos = 0;
+ req.payload_offset = 0;
+ g_mr_size = 0x500;
+ rc = nvme_rdma_build_sgl_request(&rqpair, &rdma_req);
+ SPDK_CU_ASSERT_FATAL(rc != 0);
+ CU_ASSERT(bio.iovpos == 1);
+
+ /* Test case 4: Multiple SGL, SGL size smaller than I/O size */
+ bio.iovpos = 0;
+ req.payload_offset = 0;
+ req.payload_size = 0x6000;
+ g_mr_size = 0x0;
+ rc = nvme_rdma_build_sgl_request(&rqpair, &rdma_req);
+ SPDK_CU_ASSERT_FATAL(rc != 0);
+ CU_ASSERT(bio.iovpos == NVME_RDMA_MAX_SGL_DESCRIPTORS);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvme_rdma", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (CU_add_test(suite, "build_sgl_request", test_nvme_rdma_build_sgl_request) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvmf/Makefile b/src/spdk/test/unit/lib/nvmf/Makefile
new file mode 100644
index 00000000..0b02f8ba
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/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 = request.c ctrlr.c subsystem.c ctrlr_discovery.c ctrlr_bdev.c
+
+.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/unit/lib/nvmf/ctrlr.c/.gitignore b/src/spdk/test/unit/lib/nvmf/ctrlr.c/.gitignore
new file mode 100644
index 00000000..65e84943
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/ctrlr.c/.gitignore
@@ -0,0 +1 @@
+ctrlr_ut
diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr.c/Makefile b/src/spdk/test/unit/lib/nvmf/ctrlr.c/Makefile
new file mode 100644
index 00000000..c68c589a
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/ctrlr.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = ctrlr_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr.c/ctrlr_ut.c b/src/spdk/test/unit/lib/nvmf/ctrlr.c/ctrlr_ut.c
new file mode 100644
index 00000000..71555e32
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/ctrlr.c/ctrlr_ut.c
@@ -0,0 +1,797 @@
+/*-
+ * 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_cunit.h"
+#include "spdk_internal/mock.h"
+
+#include "common/lib/test_env.c"
+#include "nvmf/ctrlr.c"
+
+SPDK_LOG_REGISTER_COMPONENT("nvmf", SPDK_LOG_NVMF)
+
+struct spdk_bdev {
+ int ut_mock;
+ uint64_t blockcnt;
+};
+
+DEFINE_STUB(spdk_nvmf_tgt_find_subsystem,
+ struct spdk_nvmf_subsystem *,
+ (struct spdk_nvmf_tgt *tgt, const char *subnqn),
+ NULL);
+
+DEFINE_STUB(spdk_nvmf_poll_group_create,
+ struct spdk_nvmf_poll_group *,
+ (struct spdk_nvmf_tgt *tgt),
+ NULL);
+
+DEFINE_STUB_V(spdk_nvmf_poll_group_destroy,
+ (struct spdk_nvmf_poll_group *group));
+
+DEFINE_STUB_V(spdk_nvmf_transport_qpair_fini,
+ (struct spdk_nvmf_qpair *qpair));
+
+DEFINE_STUB(spdk_nvmf_poll_group_add,
+ int,
+ (struct spdk_nvmf_poll_group *group, struct spdk_nvmf_qpair *qpair),
+ 0);
+
+DEFINE_STUB(spdk_nvmf_subsystem_get_sn,
+ const char *,
+ (const struct spdk_nvmf_subsystem *subsystem),
+ NULL);
+
+DEFINE_STUB(spdk_nvmf_subsystem_get_ns,
+ struct spdk_nvmf_ns *,
+ (struct spdk_nvmf_subsystem *subsystem, uint32_t nsid),
+ NULL);
+
+DEFINE_STUB(spdk_nvmf_subsystem_get_first_ns,
+ struct spdk_nvmf_ns *,
+ (struct spdk_nvmf_subsystem *subsystem),
+ NULL);
+
+DEFINE_STUB(spdk_nvmf_subsystem_get_next_ns,
+ struct spdk_nvmf_ns *,
+ (struct spdk_nvmf_subsystem *subsystem, struct spdk_nvmf_ns *prev_ns),
+ NULL);
+
+DEFINE_STUB(spdk_nvmf_subsystem_host_allowed,
+ bool,
+ (struct spdk_nvmf_subsystem *subsystem, const char *hostnqn),
+ true);
+
+DEFINE_STUB(spdk_nvmf_subsystem_add_ctrlr,
+ int,
+ (struct spdk_nvmf_subsystem *subsystem, struct spdk_nvmf_ctrlr *ctrlr),
+ 0);
+
+DEFINE_STUB_V(spdk_nvmf_subsystem_remove_ctrlr,
+ (struct spdk_nvmf_subsystem *subsystem, struct spdk_nvmf_ctrlr *ctrlr));
+
+DEFINE_STUB(spdk_nvmf_subsystem_get_ctrlr,
+ struct spdk_nvmf_ctrlr *,
+ (struct spdk_nvmf_subsystem *subsystem, uint16_t cntlid),
+ NULL);
+
+DEFINE_STUB(spdk_nvmf_ctrlr_dsm_supported,
+ bool,
+ (struct spdk_nvmf_ctrlr *ctrlr),
+ false);
+
+DEFINE_STUB(spdk_nvmf_ctrlr_write_zeroes_supported,
+ bool,
+ (struct spdk_nvmf_ctrlr *ctrlr),
+ false);
+
+DEFINE_STUB_V(spdk_nvmf_get_discovery_log_page,
+ (struct spdk_nvmf_tgt *tgt, void *buffer, uint64_t offset, uint32_t length));
+
+DEFINE_STUB(spdk_nvmf_request_complete,
+ int,
+ (struct spdk_nvmf_request *req),
+ -1);
+
+DEFINE_STUB(spdk_nvmf_request_free,
+ int,
+ (struct spdk_nvmf_request *req),
+ -1);
+
+DEFINE_STUB(spdk_nvmf_qpair_get_listen_trid,
+ int,
+ (struct spdk_nvmf_qpair *qpair, struct spdk_nvme_transport_id *trid),
+ 0);
+
+DEFINE_STUB(spdk_nvmf_subsystem_listener_allowed,
+ bool,
+ (struct spdk_nvmf_subsystem *subsystem, struct spdk_nvme_transport_id *trid),
+ true);
+
+static void
+ctrlr_ut_pass_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx)
+{
+ fn(ctx);
+}
+
+void
+spdk_nvmf_bdev_ctrlr_identify_ns(struct spdk_nvmf_ns *ns, struct spdk_nvme_ns_data *nsdata)
+{
+ uint64_t num_blocks;
+
+ SPDK_CU_ASSERT_FATAL(ns->bdev != NULL);
+ num_blocks = ns->bdev->blockcnt;
+ nsdata->nsze = num_blocks;
+ nsdata->ncap = num_blocks;
+ nsdata->nuse = num_blocks;
+ nsdata->nlbaf = 0;
+ nsdata->flbas.format = 0;
+ nsdata->lbaf[0].lbads = spdk_u32log2(512);
+}
+
+static void
+test_get_log_page(void)
+{
+ struct spdk_nvmf_subsystem subsystem = {};
+ struct spdk_nvmf_request req = {};
+ struct spdk_nvmf_qpair qpair = {};
+ struct spdk_nvmf_ctrlr ctrlr = {};
+ union nvmf_h2c_msg cmd = {};
+ union nvmf_c2h_msg rsp = {};
+ char data[4096];
+
+ subsystem.subtype = SPDK_NVMF_SUBTYPE_NVME;
+
+ ctrlr.subsys = &subsystem;
+
+ qpair.ctrlr = &ctrlr;
+
+ req.qpair = &qpair;
+ req.cmd = &cmd;
+ req.rsp = &rsp;
+ req.data = &data;
+ req.length = sizeof(data);
+
+ /* Get Log Page - all valid */
+ memset(&cmd, 0, sizeof(cmd));
+ memset(&rsp, 0, sizeof(rsp));
+ cmd.nvme_cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE;
+ cmd.nvme_cmd.cdw10 = SPDK_NVME_LOG_ERROR | (req.length / 4 - 1) << 16;
+ CU_ASSERT(spdk_nvmf_ctrlr_get_log_page(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(req.rsp->nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(req.rsp->nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS);
+
+ /* Get Log Page with invalid log ID */
+ memset(&cmd, 0, sizeof(cmd));
+ memset(&rsp, 0, sizeof(rsp));
+ cmd.nvme_cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE;
+ cmd.nvme_cmd.cdw10 = 0;
+ CU_ASSERT(spdk_nvmf_ctrlr_get_log_page(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(req.rsp->nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(req.rsp->nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_FIELD);
+
+ /* Get Log Page with invalid offset (not dword aligned) */
+ memset(&cmd, 0, sizeof(cmd));
+ memset(&rsp, 0, sizeof(rsp));
+ cmd.nvme_cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE;
+ cmd.nvme_cmd.cdw10 = SPDK_NVME_LOG_ERROR | (req.length / 4 - 1) << 16;
+ cmd.nvme_cmd.cdw12 = 2;
+ CU_ASSERT(spdk_nvmf_ctrlr_get_log_page(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(req.rsp->nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(req.rsp->nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_FIELD);
+
+ /* Get Log Page without data buffer */
+ memset(&cmd, 0, sizeof(cmd));
+ memset(&rsp, 0, sizeof(rsp));
+ req.data = NULL;
+ cmd.nvme_cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE;
+ cmd.nvme_cmd.cdw10 = SPDK_NVME_LOG_ERROR | (req.length / 4 - 1) << 16;
+ CU_ASSERT(spdk_nvmf_ctrlr_get_log_page(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(req.rsp->nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(req.rsp->nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_FIELD);
+ req.data = data;
+}
+
+static void
+test_process_fabrics_cmd(void)
+{
+ struct spdk_nvmf_request req = {};
+ int ret;
+ struct spdk_nvmf_qpair req_qpair = {};
+ union nvmf_h2c_msg req_cmd = {};
+ union nvmf_c2h_msg req_rsp = {};
+
+ req.qpair = &req_qpair;
+ req.cmd = &req_cmd;
+ req.rsp = &req_rsp;
+ req.qpair->ctrlr = NULL;
+
+ /* No ctrlr and invalid command check */
+ req.cmd->nvmf_cmd.fctype = SPDK_NVMF_FABRIC_COMMAND_PROPERTY_GET;
+ ret = spdk_nvmf_ctrlr_process_fabrics_cmd(&req);
+ CU_ASSERT_EQUAL(req.rsp->nvme_cpl.status.sc, SPDK_NVME_SC_COMMAND_SEQUENCE_ERROR);
+ CU_ASSERT_EQUAL(ret, SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+}
+
+static bool
+nvme_status_success(const struct spdk_nvme_status *status)
+{
+ return status->sct == SPDK_NVME_SCT_GENERIC && status->sc == SPDK_NVME_SC_SUCCESS;
+}
+
+static void
+test_connect(void)
+{
+ struct spdk_nvmf_fabric_connect_data connect_data;
+ struct spdk_thread *thread;
+ struct spdk_nvmf_poll_group group;
+ struct spdk_nvmf_transport transport;
+ struct spdk_nvmf_subsystem subsystem;
+ struct spdk_nvmf_request req;
+ struct spdk_nvmf_qpair admin_qpair;
+ struct spdk_nvmf_qpair qpair;
+ struct spdk_nvmf_qpair qpair2;
+ struct spdk_nvmf_ctrlr ctrlr;
+ struct spdk_nvmf_tgt tgt;
+ union nvmf_h2c_msg cmd;
+ union nvmf_c2h_msg rsp;
+ const uint8_t hostid[16] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
+ };
+ const char subnqn[] = "nqn.2016-06.io.spdk:subsystem1";
+ const char hostnqn[] = "nqn.2016-06.io.spdk:host1";
+ int rc;
+
+ thread = spdk_allocate_thread(ctrlr_ut_pass_msg, NULL, NULL, NULL, "ctrlr_ut");
+ SPDK_CU_ASSERT_FATAL(thread != NULL);
+
+ memset(&group, 0, sizeof(group));
+ group.thread = thread;
+
+ memset(&ctrlr, 0, sizeof(ctrlr));
+ ctrlr.subsys = &subsystem;
+ ctrlr.qpair_mask = spdk_bit_array_create(3);
+ SPDK_CU_ASSERT_FATAL(ctrlr.qpair_mask != NULL);
+ ctrlr.vcprop.cc.bits.en = 1;
+ ctrlr.vcprop.cc.bits.iosqes = 6;
+ ctrlr.vcprop.cc.bits.iocqes = 4;
+
+ memset(&admin_qpair, 0, sizeof(admin_qpair));
+ admin_qpair.group = &group;
+
+ memset(&tgt, 0, sizeof(tgt));
+ memset(&transport, 0, sizeof(transport));
+ transport.opts.max_queue_depth = 64;
+ transport.opts.max_qpairs_per_ctrlr = 3;
+ transport.tgt = &tgt;
+
+ memset(&qpair, 0, sizeof(qpair));
+ qpair.transport = &transport;
+ qpair.group = &group;
+
+ memset(&connect_data, 0, sizeof(connect_data));
+ memcpy(connect_data.hostid, hostid, sizeof(hostid));
+ connect_data.cntlid = 0xFFFF;
+ snprintf(connect_data.subnqn, sizeof(connect_data.subnqn), "%s", subnqn);
+ snprintf(connect_data.hostnqn, sizeof(connect_data.hostnqn), "%s", hostnqn);
+
+ memset(&subsystem, 0, sizeof(subsystem));
+ subsystem.thread = thread;
+ subsystem.id = 1;
+ TAILQ_INIT(&subsystem.ctrlrs);
+ subsystem.tgt = &tgt;
+ subsystem.subtype = SPDK_NVMF_SUBTYPE_NVME;
+ snprintf(subsystem.subnqn, sizeof(subsystem.subnqn), "%s", subnqn);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.connect_cmd.opcode = SPDK_NVME_OPC_FABRIC;
+ cmd.connect_cmd.cid = 1;
+ cmd.connect_cmd.fctype = SPDK_NVMF_FABRIC_COMMAND_CONNECT;
+ cmd.connect_cmd.recfmt = 0;
+ cmd.connect_cmd.qid = 0;
+ cmd.connect_cmd.sqsize = 31;
+ cmd.connect_cmd.cattr = 0;
+ cmd.connect_cmd.kato = 120000;
+
+ memset(&req, 0, sizeof(req));
+ req.qpair = &qpair;
+ req.length = sizeof(connect_data);
+ req.xfer = SPDK_NVME_DATA_HOST_TO_CONTROLLER;
+ req.data = &connect_data;
+ req.cmd = &cmd;
+ req.rsp = &rsp;
+
+ MOCK_SET(spdk_nvmf_tgt_find_subsystem, &subsystem);
+ MOCK_SET(spdk_nvmf_poll_group_create, &group);
+
+ /* Valid admin connect command */
+ memset(&rsp, 0, sizeof(rsp));
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS);
+ CU_ASSERT(nvme_status_success(&rsp.nvme_cpl.status));
+ CU_ASSERT(qpair.ctrlr != NULL);
+ spdk_bit_array_free(&qpair.ctrlr->qpair_mask);
+ free(qpair.ctrlr);
+ qpair.ctrlr = NULL;
+
+ /* Invalid data length */
+ memset(&rsp, 0, sizeof(rsp));
+ req.length = sizeof(connect_data) - 1;
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_FIELD);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ req.length = sizeof(connect_data);
+
+ /* Invalid recfmt */
+ memset(&rsp, 0, sizeof(rsp));
+ cmd.connect_cmd.recfmt = 1234;
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INCOMPATIBLE_FORMAT);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ cmd.connect_cmd.recfmt = 0;
+
+ /* Unterminated subnqn */
+ memset(&rsp, 0, sizeof(rsp));
+ memset(connect_data.subnqn, 'a', sizeof(connect_data.subnqn));
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 1);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 256);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ snprintf(connect_data.subnqn, sizeof(connect_data.subnqn), "%s", subnqn);
+
+ /* Subsystem not found */
+ memset(&rsp, 0, sizeof(rsp));
+ MOCK_SET(spdk_nvmf_tgt_find_subsystem, NULL);
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 1);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 256);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ MOCK_SET(spdk_nvmf_tgt_find_subsystem, &subsystem);
+
+ /* Unterminated hostnqn */
+ memset(&rsp, 0, sizeof(rsp));
+ memset(connect_data.hostnqn, 'b', sizeof(connect_data.hostnqn));
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 1);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 512);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ snprintf(connect_data.hostnqn, sizeof(connect_data.hostnqn), "%s", hostnqn);
+
+ /* Host not allowed */
+ memset(&rsp, 0, sizeof(rsp));
+ MOCK_SET(spdk_nvmf_subsystem_host_allowed, false);
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_HOST);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ MOCK_SET(spdk_nvmf_subsystem_host_allowed, true);
+
+ /* Invalid sqsize == 0 */
+ memset(&rsp, 0, sizeof(rsp));
+ cmd.connect_cmd.sqsize = 0;
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 44);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ cmd.connect_cmd.sqsize = 31;
+
+ /* Invalid sqsize > max_queue_depth */
+ memset(&rsp, 0, sizeof(rsp));
+ cmd.connect_cmd.sqsize = 64;
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 44);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ cmd.connect_cmd.sqsize = 31;
+
+ /* Invalid cntlid for admin queue */
+ memset(&rsp, 0, sizeof(rsp));
+ connect_data.cntlid = 0x1234;
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 1);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 16);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ connect_data.cntlid = 0xFFFF;
+
+ ctrlr.admin_qpair = &admin_qpair;
+ ctrlr.subsys = &subsystem;
+
+ /* Valid I/O queue connect command */
+ memset(&rsp, 0, sizeof(rsp));
+ MOCK_SET(spdk_nvmf_subsystem_get_ctrlr, &ctrlr);
+ cmd.connect_cmd.qid = 1;
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS);
+ CU_ASSERT(nvme_status_success(&rsp.nvme_cpl.status));
+ CU_ASSERT(qpair.ctrlr == &ctrlr);
+ qpair.ctrlr = NULL;
+
+ /* Non-existent controller */
+ memset(&rsp, 0, sizeof(rsp));
+ MOCK_SET(spdk_nvmf_subsystem_get_ctrlr, NULL);
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 1);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 16);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ MOCK_SET(spdk_nvmf_subsystem_get_ctrlr, &ctrlr);
+
+ /* I/O connect to discovery controller */
+ memset(&rsp, 0, sizeof(rsp));
+ subsystem.subtype = SPDK_NVMF_SUBTYPE_DISCOVERY;
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 42);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ subsystem.subtype = SPDK_NVMF_SUBTYPE_NVME;
+
+ /* I/O connect to disabled controller */
+ memset(&rsp, 0, sizeof(rsp));
+ ctrlr.vcprop.cc.bits.en = 0;
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 42);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ ctrlr.vcprop.cc.bits.en = 1;
+
+ /* I/O connect with invalid IOSQES */
+ memset(&rsp, 0, sizeof(rsp));
+ ctrlr.vcprop.cc.bits.iosqes = 3;
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 42);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ ctrlr.vcprop.cc.bits.iosqes = 6;
+
+ /* I/O connect with invalid IOCQES */
+ memset(&rsp, 0, sizeof(rsp));
+ ctrlr.vcprop.cc.bits.iocqes = 3;
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVMF_FABRIC_SC_INVALID_PARAM);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.iattr == 0);
+ CU_ASSERT(rsp.connect_rsp.status_code_specific.invalid.ipo == 42);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ ctrlr.vcprop.cc.bits.iocqes = 4;
+
+ /* I/O connect with too many existing qpairs */
+ memset(&rsp, 0, sizeof(rsp));
+ spdk_bit_array_set(ctrlr.qpair_mask, 0);
+ spdk_bit_array_set(ctrlr.qpair_mask, 1);
+ spdk_bit_array_set(ctrlr.qpair_mask, 2);
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_QUEUE_IDENTIFIER);
+ CU_ASSERT(qpair.ctrlr == NULL);
+ spdk_bit_array_clear(ctrlr.qpair_mask, 0);
+ spdk_bit_array_clear(ctrlr.qpair_mask, 1);
+ spdk_bit_array_clear(ctrlr.qpair_mask, 2);
+
+ /* I/O connect with duplicate queue ID */
+ memset(&rsp, 0, sizeof(rsp));
+ memset(&qpair2, 0, sizeof(qpair2));
+ qpair2.group = &group;
+ qpair2.qid = 1;
+ spdk_bit_array_set(ctrlr.qpair_mask, 1);
+ cmd.connect_cmd.qid = 1;
+ rc = spdk_nvmf_ctrlr_connect(&req);
+ CU_ASSERT(rc == SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_COMMAND_SPECIFIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_QUEUE_IDENTIFIER);
+ CU_ASSERT(qpair.ctrlr == NULL);
+
+ /* Clean up globals */
+ MOCK_CLEAR(spdk_nvmf_tgt_find_subsystem);
+ MOCK_CLEAR(spdk_nvmf_poll_group_create);
+
+ spdk_bit_array_free(&ctrlr.qpair_mask);
+ spdk_free_thread();
+}
+
+static void
+test_get_ns_id_desc_list(void)
+{
+ struct spdk_nvmf_subsystem subsystem;
+ struct spdk_nvmf_qpair qpair;
+ struct spdk_nvmf_ctrlr ctrlr;
+ struct spdk_nvmf_request req;
+ struct spdk_nvmf_ns *ns_ptrs[1];
+ struct spdk_nvmf_ns ns;
+ union nvmf_h2c_msg cmd;
+ union nvmf_c2h_msg rsp;
+ struct spdk_bdev bdev;
+ uint8_t buf[4096];
+
+ memset(&subsystem, 0, sizeof(subsystem));
+ ns_ptrs[0] = &ns;
+ subsystem.ns = ns_ptrs;
+ subsystem.max_nsid = 1;
+ subsystem.subtype = SPDK_NVMF_SUBTYPE_NVME;
+
+ memset(&ns, 0, sizeof(ns));
+ ns.opts.nsid = 1;
+ ns.bdev = &bdev;
+
+ memset(&qpair, 0, sizeof(qpair));
+ qpair.ctrlr = &ctrlr;
+
+ memset(&ctrlr, 0, sizeof(ctrlr));
+ ctrlr.subsys = &subsystem;
+ ctrlr.vcprop.cc.bits.en = 1;
+
+ memset(&req, 0, sizeof(req));
+ req.qpair = &qpair;
+ req.cmd = &cmd;
+ req.rsp = &rsp;
+ req.xfer = SPDK_NVME_DATA_CONTROLLER_TO_HOST;
+ req.data = buf;
+ req.length = sizeof(buf);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.nvme_cmd.opc = SPDK_NVME_OPC_IDENTIFY;
+ cmd.nvme_cmd.cdw10 = SPDK_NVME_IDENTIFY_NS_ID_DESCRIPTOR_LIST;
+
+ /* Invalid NSID */
+ cmd.nvme_cmd.nsid = 0;
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT);
+
+ /* Valid NSID, but ns has no IDs defined */
+ cmd.nvme_cmd.nsid = 1;
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS);
+ CU_ASSERT(spdk_mem_all_zero(buf, sizeof(buf)));
+
+ /* Valid NSID, only EUI64 defined */
+ ns.opts.eui64[0] = 0x11;
+ ns.opts.eui64[7] = 0xFF;
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS);
+ CU_ASSERT(buf[0] == SPDK_NVME_NIDT_EUI64);
+ CU_ASSERT(buf[1] == 8);
+ CU_ASSERT(buf[4] == 0x11);
+ CU_ASSERT(buf[11] == 0xFF);
+ CU_ASSERT(buf[13] == 0);
+
+ /* Valid NSID, only NGUID defined */
+ memset(ns.opts.eui64, 0, sizeof(ns.opts.eui64));
+ ns.opts.nguid[0] = 0x22;
+ ns.opts.nguid[15] = 0xEE;
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS);
+ CU_ASSERT(buf[0] == SPDK_NVME_NIDT_NGUID);
+ CU_ASSERT(buf[1] == 16);
+ CU_ASSERT(buf[4] == 0x22);
+ CU_ASSERT(buf[19] == 0xEE);
+ CU_ASSERT(buf[21] == 0);
+
+ /* Valid NSID, both EUI64 and NGUID defined */
+ ns.opts.eui64[0] = 0x11;
+ ns.opts.eui64[7] = 0xFF;
+ ns.opts.nguid[0] = 0x22;
+ ns.opts.nguid[15] = 0xEE;
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS);
+ CU_ASSERT(buf[0] == SPDK_NVME_NIDT_EUI64);
+ CU_ASSERT(buf[1] == 8);
+ CU_ASSERT(buf[4] == 0x11);
+ CU_ASSERT(buf[11] == 0xFF);
+ CU_ASSERT(buf[12] == SPDK_NVME_NIDT_NGUID);
+ CU_ASSERT(buf[13] == 16);
+ CU_ASSERT(buf[16] == 0x22);
+ CU_ASSERT(buf[31] == 0xEE);
+ CU_ASSERT(buf[33] == 0);
+
+ /* Valid NSID, EUI64, NGUID, and UUID defined */
+ ns.opts.eui64[0] = 0x11;
+ ns.opts.eui64[7] = 0xFF;
+ ns.opts.nguid[0] = 0x22;
+ ns.opts.nguid[15] = 0xEE;
+ ns.opts.uuid.u.raw[0] = 0x33;
+ ns.opts.uuid.u.raw[15] = 0xDD;
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_process_admin_cmd(&req) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.nvme_cpl.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.nvme_cpl.status.sc == SPDK_NVME_SC_SUCCESS);
+ CU_ASSERT(buf[0] == SPDK_NVME_NIDT_EUI64);
+ CU_ASSERT(buf[1] == 8);
+ CU_ASSERT(buf[4] == 0x11);
+ CU_ASSERT(buf[11] == 0xFF);
+ CU_ASSERT(buf[12] == SPDK_NVME_NIDT_NGUID);
+ CU_ASSERT(buf[13] == 16);
+ CU_ASSERT(buf[16] == 0x22);
+ CU_ASSERT(buf[31] == 0xEE);
+ CU_ASSERT(buf[32] == SPDK_NVME_NIDT_UUID);
+ CU_ASSERT(buf[33] == 16);
+ CU_ASSERT(buf[36] == 0x33);
+ CU_ASSERT(buf[51] == 0xDD);
+ CU_ASSERT(buf[53] == 0);
+}
+
+static void
+test_identify_ns(void)
+{
+ struct spdk_nvmf_subsystem subsystem = {};
+ struct spdk_nvmf_transport transport = {};
+ struct spdk_nvmf_qpair admin_qpair = { .transport = &transport};
+ struct spdk_nvmf_ctrlr ctrlr = { .subsys = &subsystem, .admin_qpair = &admin_qpair };
+ struct spdk_nvme_cmd cmd = {};
+ struct spdk_nvme_cpl rsp = {};
+ struct spdk_nvme_ns_data nsdata = {};
+ struct spdk_bdev bdev[3] = {{.blockcnt = 1234}, {.blockcnt = 0}, {.blockcnt = 5678}};
+ struct spdk_nvmf_ns ns[3] = {{.bdev = &bdev[0]}, {.bdev = NULL}, {.bdev = &bdev[2]}};
+ struct spdk_nvmf_ns *ns_arr[3] = {&ns[0], NULL, &ns[2]};
+
+ subsystem.ns = ns_arr;
+ subsystem.max_nsid = SPDK_COUNTOF(ns_arr);
+
+ /* Invalid NSID 0 */
+ cmd.nsid = 0;
+ memset(&nsdata, 0, sizeof(nsdata));
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp,
+ &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT);
+ CU_ASSERT(spdk_mem_all_zero(&nsdata, sizeof(nsdata)));
+
+ /* Valid NSID 1 */
+ cmd.nsid = 1;
+ memset(&nsdata, 0, sizeof(nsdata));
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp,
+ &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_SUCCESS);
+ CU_ASSERT(nsdata.nsze == 1234);
+
+ /* Valid but inactive NSID 2 */
+ cmd.nsid = 2;
+ memset(&nsdata, 0, sizeof(nsdata));
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp,
+ &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_SUCCESS);
+ CU_ASSERT(spdk_mem_all_zero(&nsdata, sizeof(nsdata)));
+
+ /* Valid NSID 3 */
+ cmd.nsid = 3;
+ memset(&nsdata, 0, sizeof(nsdata));
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp,
+ &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_SUCCESS);
+ CU_ASSERT(nsdata.nsze == 5678);
+
+ /* Invalid NSID 4 */
+ cmd.nsid = 4;
+ memset(&nsdata, 0, sizeof(nsdata));
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp,
+ &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT);
+ CU_ASSERT(spdk_mem_all_zero(&nsdata, sizeof(nsdata)));
+
+ /* Invalid NSID 0xFFFFFFFF (NS management not supported) */
+ cmd.nsid = 0xFFFFFFFF;
+ memset(&nsdata, 0, sizeof(nsdata));
+ memset(&rsp, 0, sizeof(rsp));
+ CU_ASSERT(spdk_nvmf_ctrlr_identify_ns(&ctrlr, &cmd, &rsp,
+ &nsdata) == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE);
+ CU_ASSERT(rsp.status.sct == SPDK_NVME_SCT_GENERIC);
+ CU_ASSERT(rsp.status.sc == SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT);
+ CU_ASSERT(spdk_mem_all_zero(&nsdata, sizeof(nsdata)));
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvmf", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "get_log_page", test_get_log_page) == NULL ||
+ CU_add_test(suite, "process_fabrics_cmd", test_process_fabrics_cmd) == NULL ||
+ CU_add_test(suite, "connect", test_connect) == NULL ||
+ CU_add_test(suite, "get_ns_id_desc_list", test_get_ns_id_desc_list) == NULL ||
+ CU_add_test(suite, "identify_ns", test_identify_ns) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/.gitignore b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/.gitignore
new file mode 100644
index 00000000..78fca101
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/.gitignore
@@ -0,0 +1 @@
+ctrlr_bdev_ut
diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/Makefile b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/Makefile
new file mode 100644
index 00000000..1d22f14b
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = ctrlr_bdev_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/ctrlr_bdev_ut.c b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/ctrlr_bdev_ut.c
new file mode 100644
index 00000000..1085e4d7
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/ctrlr_bdev.c/ctrlr_bdev_ut.c
@@ -0,0 +1,260 @@
+/*-
+ * 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_cunit.h"
+
+#include "nvmf/ctrlr_bdev.c"
+
+
+SPDK_LOG_REGISTER_COMPONENT("nvmf", SPDK_LOG_NVMF)
+
+int
+spdk_nvmf_request_complete(struct spdk_nvmf_request *req)
+{
+ return -1;
+}
+
+const char *
+spdk_bdev_get_name(const struct spdk_bdev *bdev)
+{
+ return "test";
+}
+
+uint32_t
+spdk_bdev_get_block_size(const struct spdk_bdev *bdev)
+{
+ abort();
+ return 0;
+}
+
+uint64_t
+spdk_bdev_get_num_blocks(const struct spdk_bdev *bdev)
+{
+ abort();
+ return 0;
+}
+
+uint32_t
+spdk_bdev_get_optimal_io_boundary(const struct spdk_bdev *bdev)
+{
+ abort();
+ return 0;
+}
+
+struct spdk_io_channel *
+spdk_bdev_get_io_channel(struct spdk_bdev_desc *desc)
+{
+ return NULL;
+}
+
+int
+spdk_bdev_flush_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return 0;
+}
+
+int
+spdk_bdev_unmap_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return 0;
+}
+
+bool
+spdk_bdev_io_type_supported(struct spdk_bdev *bdev, enum spdk_bdev_io_type io_type)
+{
+ return false;
+}
+
+int
+spdk_bdev_queue_io_wait(struct spdk_bdev *bdev, struct spdk_io_channel *ch,
+ struct spdk_bdev_io_wait_entry *entry)
+{
+ return 0;
+}
+
+int
+spdk_bdev_write_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, void *buf,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return 0;
+}
+
+int
+spdk_bdev_writev_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ struct iovec *iov, int iovcnt,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return 0;
+}
+
+int
+spdk_bdev_read_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, void *buf,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return 0;
+}
+
+int spdk_bdev_readv_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ struct iovec *iov, int iovcnt,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return 0;
+}
+
+int
+spdk_bdev_write_zeroes_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return 0;
+}
+
+int
+spdk_bdev_nvme_io_passthru(struct spdk_bdev_desc *desc,
+ struct spdk_io_channel *ch,
+ const struct spdk_nvme_cmd *cmd,
+ void *buf, size_t nbytes,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return 0;
+}
+
+void spdk_bdev_free_io(struct spdk_bdev_io *bdev_io)
+{
+}
+
+const char *spdk_nvmf_subsystem_get_nqn(struct spdk_nvmf_subsystem *subsystem)
+{
+ return NULL;
+}
+
+struct spdk_nvmf_ns *
+spdk_nvmf_subsystem_get_ns(struct spdk_nvmf_subsystem *subsystem, uint32_t nsid)
+{
+ abort();
+ return NULL;
+}
+
+struct spdk_nvmf_ns *
+spdk_nvmf_subsystem_get_first_ns(struct spdk_nvmf_subsystem *subsystem)
+{
+ abort();
+ return NULL;
+}
+
+struct spdk_nvmf_ns *
+spdk_nvmf_subsystem_get_next_ns(struct spdk_nvmf_subsystem *subsystem, struct spdk_nvmf_ns *prev_ns)
+{
+ abort();
+ return NULL;
+}
+
+void spdk_bdev_io_get_nvme_status(const struct spdk_bdev_io *bdev_io, int *sct, int *sc)
+{
+}
+
+static void
+test_get_rw_params(void)
+{
+ struct spdk_nvme_cmd cmd = {0};
+ uint64_t lba;
+ uint64_t count;
+
+ lba = 0;
+ count = 0;
+ to_le64(&cmd.cdw10, 0x1234567890ABCDEF);
+ to_le32(&cmd.cdw12, 0x9875 | SPDK_NVME_IO_FLAGS_FORCE_UNIT_ACCESS);
+ nvmf_bdev_ctrlr_get_rw_params(&cmd, &lba, &count);
+ CU_ASSERT(lba == 0x1234567890ABCDEF);
+ CU_ASSERT(count == 0x9875 + 1); /* NOTE: this field is 0's based, hence the +1 */
+}
+
+static void
+test_lba_in_range(void)
+{
+ /* Trivial cases (no overflow) */
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 0, 1) == true);
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 0, 1000) == true);
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 0, 1001) == false);
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 1, 999) == true);
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 1, 1000) == false);
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 999, 1) == true);
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 1000, 1) == false);
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(1000, 1001, 1) == false);
+
+ /* Overflow edge cases */
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(UINT64_MAX, 0, UINT64_MAX) == true);
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(UINT64_MAX, 1, UINT64_MAX) == false)
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(UINT64_MAX, UINT64_MAX - 1, 1) == true);
+ CU_ASSERT(nvmf_bdev_ctrlr_lba_in_range(UINT64_MAX, UINT64_MAX, 1) == false);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvmf", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "get_rw_params", test_get_rw_params) == NULL ||
+ CU_add_test(suite, "lba_in_range", test_lba_in_range) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/.gitignore b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/.gitignore
new file mode 100644
index 00000000..a975a97e
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/.gitignore
@@ -0,0 +1 @@
+ctrlr_discovery_ut
diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/Makefile b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/Makefile
new file mode 100644
index 00000000..e56238d2
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = ctrlr_discovery_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/ctrlr_discovery_ut.c b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/ctrlr_discovery_ut.c
new file mode 100644
index 00000000..f86bdf2b
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/ctrlr_discovery.c/ctrlr_discovery_ut.c
@@ -0,0 +1,306 @@
+/*-
+ * 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_cunit.h"
+#include "spdk_internal/mock.h"
+
+#include "common/lib/test_env.c"
+#include "nvmf/ctrlr_discovery.c"
+#include "nvmf/subsystem.c"
+
+SPDK_LOG_REGISTER_COMPONENT("nvmf", SPDK_LOG_NVMF)
+
+DEFINE_STUB(spdk_bdev_module_claim_bdev,
+ int,
+ (struct spdk_bdev *bdev, struct spdk_bdev_desc *desc,
+ struct spdk_bdev_module *module), 0);
+
+DEFINE_STUB_V(spdk_bdev_module_release_bdev,
+ (struct spdk_bdev *bdev));
+
+uint32_t
+spdk_env_get_current_core(void)
+{
+ return 0;
+}
+
+struct spdk_event *
+spdk_event_allocate(uint32_t core, spdk_event_fn fn, void *arg1, void *arg2)
+{
+ return NULL;
+}
+
+void
+spdk_event_call(struct spdk_event *event)
+{
+
+}
+
+int
+spdk_bdev_open(struct spdk_bdev *bdev, bool write, spdk_bdev_remove_cb_t remove_cb,
+ void *remove_ctx, struct spdk_bdev_desc **desc)
+{
+ return 0;
+}
+
+void
+spdk_bdev_close(struct spdk_bdev_desc *desc)
+{
+}
+
+const char *
+spdk_bdev_get_name(const struct spdk_bdev *bdev)
+{
+ return "test";
+}
+
+const struct spdk_uuid *
+spdk_bdev_get_uuid(const struct spdk_bdev *bdev)
+{
+ return &bdev->uuid;
+}
+
+int
+spdk_nvmf_transport_listen(struct spdk_nvmf_transport *transport,
+ const struct spdk_nvme_transport_id *trid)
+{
+ return 0;
+}
+
+void
+spdk_nvmf_transport_listener_discover(struct spdk_nvmf_transport *transport,
+ struct spdk_nvme_transport_id *trid,
+ struct spdk_nvmf_discovery_log_page_entry *entry)
+{
+ entry->trtype = 42;
+}
+
+static struct spdk_nvmf_transport g_transport = {};
+
+struct spdk_nvmf_transport *
+spdk_nvmf_transport_create(enum spdk_nvme_transport_type type,
+ struct spdk_nvmf_transport_opts *tprt_opts)
+{
+ if (type == SPDK_NVME_TRANSPORT_RDMA) {
+ return &g_transport;
+ }
+
+ return NULL;
+}
+
+struct spdk_nvmf_subsystem *
+spdk_nvmf_tgt_find_subsystem(struct spdk_nvmf_tgt *tgt, const char *subnqn)
+{
+ return NULL;
+}
+
+struct spdk_nvmf_transport *
+spdk_nvmf_tgt_get_transport(struct spdk_nvmf_tgt *tgt, enum spdk_nvme_transport_type trtype)
+{
+ return &g_transport;
+}
+
+bool
+spdk_nvmf_transport_qpair_is_idle(struct spdk_nvmf_qpair *qpair)
+{
+ return false;
+}
+
+int
+spdk_nvme_transport_id_parse_trtype(enum spdk_nvme_transport_type *trtype, const char *str)
+{
+ if (trtype == NULL || str == NULL) {
+ return -EINVAL;
+ }
+
+ if (strcasecmp(str, "PCIe") == 0) {
+ *trtype = SPDK_NVME_TRANSPORT_PCIE;
+ } else if (strcasecmp(str, "RDMA") == 0) {
+ *trtype = SPDK_NVME_TRANSPORT_RDMA;
+ } else {
+ return -ENOENT;
+ }
+ return 0;
+}
+
+int
+spdk_nvme_transport_id_compare(const struct spdk_nvme_transport_id *trid1,
+ const struct spdk_nvme_transport_id *trid2)
+{
+ return 0;
+}
+
+void
+spdk_nvmf_ctrlr_ns_changed(struct spdk_nvmf_ctrlr *ctrlr, uint32_t nsid)
+{
+}
+
+void
+spdk_nvmf_ctrlr_destruct(struct spdk_nvmf_ctrlr *ctrlr)
+{
+}
+
+int
+spdk_nvmf_poll_group_update_subsystem(struct spdk_nvmf_poll_group *group,
+ struct spdk_nvmf_subsystem *subsystem)
+{
+ return 0;
+}
+
+int
+spdk_nvmf_poll_group_add_subsystem(struct spdk_nvmf_poll_group *group,
+ struct spdk_nvmf_subsystem *subsystem,
+ spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg)
+{
+ return 0;
+}
+
+void
+spdk_nvmf_poll_group_remove_subsystem(struct spdk_nvmf_poll_group *group,
+ struct spdk_nvmf_subsystem *subsystem,
+ spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg)
+{
+}
+
+void
+spdk_nvmf_poll_group_pause_subsystem(struct spdk_nvmf_poll_group *group,
+ struct spdk_nvmf_subsystem *subsystem,
+ spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg)
+{
+}
+
+void
+spdk_nvmf_poll_group_resume_subsystem(struct spdk_nvmf_poll_group *group,
+ struct spdk_nvmf_subsystem *subsystem,
+ spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg)
+{
+}
+
+static void
+test_discovery_log(void)
+{
+ struct spdk_nvmf_tgt tgt = {};
+ struct spdk_nvmf_subsystem *subsystem;
+ uint8_t buffer[8192];
+ struct spdk_nvmf_discovery_log_page *disc_log;
+ struct spdk_nvmf_discovery_log_page_entry *entry;
+ struct spdk_nvme_transport_id trid = {};
+
+ tgt.opts.max_subsystems = 1024;
+ tgt.subsystems = calloc(tgt.opts.max_subsystems, sizeof(struct spdk_nvmf_subsystem *));
+ SPDK_CU_ASSERT_FATAL(tgt.subsystems != NULL);
+
+ /* Add one subsystem and verify that the discovery log contains it */
+ subsystem = spdk_nvmf_subsystem_create(&tgt, "nqn.2016-06.io.spdk:subsystem1",
+ SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem != NULL);
+
+ trid.trtype = SPDK_NVME_TRANSPORT_RDMA;
+ trid.adrfam = SPDK_NVMF_ADRFAM_IPV4;
+ snprintf(trid.traddr, sizeof(trid.traddr), "1234");
+ snprintf(trid.trsvcid, sizeof(trid.trsvcid), "5678");
+ SPDK_CU_ASSERT_FATAL(spdk_nvmf_subsystem_add_listener(subsystem, &trid) == 0);
+
+ /* Get only genctr (first field in the header) */
+ memset(buffer, 0xCC, sizeof(buffer));
+ disc_log = (struct spdk_nvmf_discovery_log_page *)buffer;
+ spdk_nvmf_get_discovery_log_page(&tgt, buffer, 0, sizeof(disc_log->genctr));
+ CU_ASSERT(disc_log->genctr == 1); /* one added subsystem */
+
+ /* Get only the header, no entries */
+ memset(buffer, 0xCC, sizeof(buffer));
+ disc_log = (struct spdk_nvmf_discovery_log_page *)buffer;
+ spdk_nvmf_get_discovery_log_page(&tgt, buffer, 0, sizeof(*disc_log));
+ CU_ASSERT(disc_log->genctr == 1);
+ CU_ASSERT(disc_log->numrec == 1);
+
+ /* Offset 0, exact size match */
+ memset(buffer, 0xCC, sizeof(buffer));
+ disc_log = (struct spdk_nvmf_discovery_log_page *)buffer;
+ spdk_nvmf_get_discovery_log_page(&tgt, buffer, 0,
+ sizeof(*disc_log) + sizeof(disc_log->entries[0]));
+ CU_ASSERT(disc_log->genctr != 0);
+ CU_ASSERT(disc_log->numrec == 1);
+ CU_ASSERT(disc_log->entries[0].trtype == 42);
+
+ /* Offset 0, oversize buffer */
+ memset(buffer, 0xCC, sizeof(buffer));
+ disc_log = (struct spdk_nvmf_discovery_log_page *)buffer;
+ spdk_nvmf_get_discovery_log_page(&tgt, buffer, 0, sizeof(buffer));
+ CU_ASSERT(disc_log->genctr != 0);
+ CU_ASSERT(disc_log->numrec == 1);
+ CU_ASSERT(disc_log->entries[0].trtype == 42);
+ CU_ASSERT(spdk_mem_all_zero(buffer + sizeof(*disc_log) + sizeof(disc_log->entries[0]),
+ sizeof(buffer) - (sizeof(*disc_log) + sizeof(disc_log->entries[0]))));
+
+ /* Get just the first entry, no header */
+ memset(buffer, 0xCC, sizeof(buffer));
+ entry = (struct spdk_nvmf_discovery_log_page_entry *)buffer;
+ spdk_nvmf_get_discovery_log_page(&tgt, buffer,
+ offsetof(struct spdk_nvmf_discovery_log_page, entries[0]),
+ sizeof(*entry));
+ CU_ASSERT(entry->trtype == 42);
+ spdk_nvmf_subsystem_destroy(subsystem);
+ free(tgt.subsystems);
+ free(tgt.discovery_log_page);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvmf", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "discovery_log", test_discovery_log) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvmf/request.c/.gitignore b/src/spdk/test/unit/lib/nvmf/request.c/.gitignore
new file mode 100644
index 00000000..7f06e410
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/request.c/.gitignore
@@ -0,0 +1 @@
+request_ut
diff --git a/src/spdk/test/unit/lib/nvmf/request.c/Makefile b/src/spdk/test/unit/lib/nvmf/request.c/Makefile
new file mode 100644
index 00000000..0c683cff
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/request.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = request_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvmf/request.c/request_ut.c b/src/spdk/test/unit/lib/nvmf/request.c/request_ut.c
new file mode 100644
index 00000000..bd21fa63
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/request.c/request_ut.c
@@ -0,0 +1,153 @@
+/*-
+ * 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_cunit.h"
+
+#include "nvmf/request.c"
+
+SPDK_LOG_REGISTER_COMPONENT("nvmf", SPDK_LOG_NVMF)
+
+int
+spdk_nvmf_transport_req_free(struct spdk_nvmf_request *req)
+{
+ return 0;
+}
+
+int
+spdk_nvmf_transport_req_complete(struct spdk_nvmf_request *req)
+{
+ return 0;
+}
+
+int
+spdk_nvmf_ctrlr_process_fabrics_cmd(struct spdk_nvmf_request *req)
+{
+ return -1;
+}
+
+int
+spdk_nvmf_ctrlr_process_admin_cmd(struct spdk_nvmf_request *req)
+{
+ return -1;
+}
+
+int
+spdk_nvmf_ctrlr_process_io_cmd(struct spdk_nvmf_request *req)
+{
+ return -1;
+}
+
+int
+spdk_nvme_ctrlr_cmd_admin_raw(struct spdk_nvme_ctrlr *ctrlr,
+ struct spdk_nvme_cmd *cmd,
+ void *buf, uint32_t len,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ return -1;
+}
+
+int
+spdk_nvme_ctrlr_cmd_io_raw(struct spdk_nvme_ctrlr *ctrlr,
+ struct spdk_nvme_qpair *qpair,
+ struct spdk_nvme_cmd *cmd,
+ void *buf, uint32_t len,
+ spdk_nvme_cmd_cb cb_fn, void *cb_arg)
+{
+ return -1;
+}
+
+uint32_t
+spdk_nvme_ctrlr_get_num_ns(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return 0;
+}
+
+union spdk_nvme_vs_register spdk_nvme_ctrlr_get_regs_vs(struct spdk_nvme_ctrlr *ctrlr)
+{
+ union spdk_nvme_vs_register vs;
+
+ vs.raw = 0;
+ return vs;
+}
+
+bool
+spdk_nvme_ns_is_active(struct spdk_nvme_ns *ns)
+{
+ return false;
+}
+
+struct spdk_nvme_ns *spdk_nvme_ctrlr_get_ns(struct spdk_nvme_ctrlr *ctrlr, uint32_t ns_id)
+{
+ return NULL;
+}
+
+int
+spdk_nvmf_qpair_disconnect(struct spdk_nvmf_qpair *qpair, nvmf_qpair_disconnect_cb cb_fn, void *ctx)
+{
+ return 0;
+}
+
+static void
+test_placeholder(void)
+{
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvmf", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "placeholder", test_placeholder) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/nvmf/subsystem.c/.gitignore b/src/spdk/test/unit/lib/nvmf/subsystem.c/.gitignore
new file mode 100644
index 00000000..76ca0d33
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/subsystem.c/.gitignore
@@ -0,0 +1 @@
+subsystem_ut
diff --git a/src/spdk/test/unit/lib/nvmf/subsystem.c/Makefile b/src/spdk/test/unit/lib/nvmf/subsystem.c/Makefile
new file mode 100644
index 00000000..b62f1ee1
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/subsystem.c/Makefile
@@ -0,0 +1,38 @@
+#
+# 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)/../../../../..)
+
+TEST_FILE = subsystem_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/nvmf/subsystem.c/subsystem_ut.c b/src/spdk/test/unit/lib/nvmf/subsystem.c/subsystem_ut.c
new file mode 100644
index 00000000..1b92efd2
--- /dev/null
+++ b/src/spdk/test/unit/lib/nvmf/subsystem.c/subsystem_ut.c
@@ -0,0 +1,477 @@
+/*-
+ * 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 "common/lib/test_env.c"
+#include "spdk_cunit.h"
+#include "spdk_internal/mock.h"
+
+#include "nvmf/subsystem.c"
+
+SPDK_LOG_REGISTER_COMPONENT("nvmf", SPDK_LOG_NVMF)
+
+DEFINE_STUB(spdk_bdev_module_claim_bdev,
+ int,
+ (struct spdk_bdev *bdev, struct spdk_bdev_desc *desc,
+ struct spdk_bdev_module *module), 0);
+
+DEFINE_STUB_V(spdk_bdev_module_release_bdev,
+ (struct spdk_bdev *bdev));
+
+static void
+_subsystem_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx)
+{
+ fn(ctx);
+}
+
+static void
+subsystem_ns_remove_cb(struct spdk_nvmf_subsystem *subsystem, void *cb_arg, int status)
+{
+}
+
+uint32_t
+spdk_env_get_current_core(void)
+{
+ return 0;
+}
+
+struct spdk_event *
+spdk_event_allocate(uint32_t core, spdk_event_fn fn, void *arg1, void *arg2)
+{
+ return NULL;
+}
+
+void
+spdk_event_call(struct spdk_event *event)
+{
+
+}
+
+int
+spdk_nvmf_transport_listen(struct spdk_nvmf_transport *transport,
+ const struct spdk_nvme_transport_id *trid)
+{
+ return 0;
+}
+
+void
+spdk_nvmf_transport_listener_discover(struct spdk_nvmf_transport *transport,
+ struct spdk_nvme_transport_id *trid,
+ struct spdk_nvmf_discovery_log_page_entry *entry)
+{
+ entry->trtype = 42;
+}
+
+bool
+spdk_nvmf_transport_qpair_is_idle(struct spdk_nvmf_qpair *qpair)
+{
+ return false;
+}
+
+static struct spdk_nvmf_transport g_transport = {};
+
+struct spdk_nvmf_transport *
+spdk_nvmf_transport_create(enum spdk_nvme_transport_type type,
+ struct spdk_nvmf_transport_opts *tprt_opts)
+{
+ if (type == SPDK_NVME_TRANSPORT_RDMA) {
+ return &g_transport;
+ }
+
+ return NULL;
+}
+
+struct spdk_nvmf_subsystem *
+spdk_nvmf_tgt_find_subsystem(struct spdk_nvmf_tgt *tgt, const char *subnqn)
+{
+ return NULL;
+}
+
+struct spdk_nvmf_transport *
+spdk_nvmf_tgt_get_transport(struct spdk_nvmf_tgt *tgt, enum spdk_nvme_transport_type trtype)
+{
+ if (trtype == SPDK_NVME_TRANSPORT_RDMA) {
+ return &g_transport;
+ }
+
+ return NULL;
+}
+
+int
+spdk_nvmf_poll_group_update_subsystem(struct spdk_nvmf_poll_group *group,
+ struct spdk_nvmf_subsystem *subsystem)
+{
+ return 0;
+}
+
+int
+spdk_nvmf_poll_group_add_subsystem(struct spdk_nvmf_poll_group *group,
+ struct spdk_nvmf_subsystem *subsystem,
+ spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg)
+{
+ return 0;
+}
+
+void
+spdk_nvmf_poll_group_remove_subsystem(struct spdk_nvmf_poll_group *group,
+ struct spdk_nvmf_subsystem *subsystem,
+ spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg)
+{
+}
+
+void
+spdk_nvmf_poll_group_pause_subsystem(struct spdk_nvmf_poll_group *group,
+ struct spdk_nvmf_subsystem *subsystem,
+ spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg)
+{
+}
+
+void
+spdk_nvmf_poll_group_resume_subsystem(struct spdk_nvmf_poll_group *group,
+ struct spdk_nvmf_subsystem *subsystem,
+ spdk_nvmf_poll_group_mod_done cb_fn, void *cb_arg)
+{
+}
+
+int
+spdk_nvme_transport_id_parse_trtype(enum spdk_nvme_transport_type *trtype, const char *str)
+{
+ if (trtype == NULL || str == NULL) {
+ return -EINVAL;
+ }
+
+ if (strcasecmp(str, "PCIe") == 0) {
+ *trtype = SPDK_NVME_TRANSPORT_PCIE;
+ } else if (strcasecmp(str, "RDMA") == 0) {
+ *trtype = SPDK_NVME_TRANSPORT_RDMA;
+ } else {
+ return -ENOENT;
+ }
+ return 0;
+}
+
+int
+spdk_nvme_transport_id_compare(const struct spdk_nvme_transport_id *trid1,
+ const struct spdk_nvme_transport_id *trid2)
+{
+ return 0;
+}
+
+int32_t
+spdk_nvme_ctrlr_process_admin_completions(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return -1;
+}
+
+int32_t
+spdk_nvme_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions)
+{
+ return -1;
+}
+
+int
+spdk_nvme_detach(struct spdk_nvme_ctrlr *ctrlr)
+{
+ return -1;
+}
+
+void
+spdk_nvmf_ctrlr_destruct(struct spdk_nvmf_ctrlr *ctrlr)
+{
+}
+
+void
+spdk_nvmf_ctrlr_ns_changed(struct spdk_nvmf_ctrlr *ctrlr, uint32_t nsid)
+{
+}
+
+int
+spdk_bdev_open(struct spdk_bdev *bdev, bool write, spdk_bdev_remove_cb_t remove_cb,
+ void *remove_ctx, struct spdk_bdev_desc **desc)
+{
+ return 0;
+}
+
+void
+spdk_bdev_close(struct spdk_bdev_desc *desc)
+{
+}
+
+const char *
+spdk_bdev_get_name(const struct spdk_bdev *bdev)
+{
+ return "test";
+}
+
+const struct spdk_uuid *
+spdk_bdev_get_uuid(const struct spdk_bdev *bdev)
+{
+ return &bdev->uuid;
+}
+
+static void
+test_spdk_nvmf_subsystem_add_ns(void)
+{
+ struct spdk_nvmf_tgt tgt = {};
+ struct spdk_nvmf_subsystem subsystem = {
+ .max_nsid = 0,
+ .ns = NULL,
+ .tgt = &tgt
+ };
+ struct spdk_bdev bdev1 = {}, bdev2 = {};
+ struct spdk_nvmf_ns_opts ns_opts;
+ uint32_t nsid;
+
+ tgt.opts.max_subsystems = 1024;
+ tgt.subsystems = calloc(tgt.opts.max_subsystems, sizeof(struct spdk_nvmf_subsystem *));
+ SPDK_CU_ASSERT_FATAL(tgt.subsystems != NULL);
+
+ /* Allow NSID to be assigned automatically */
+ spdk_nvmf_ns_opts_get_defaults(&ns_opts, sizeof(ns_opts));
+ nsid = spdk_nvmf_subsystem_add_ns(&subsystem, &bdev1, &ns_opts, sizeof(ns_opts));
+ /* NSID 1 is the first unused ID */
+ CU_ASSERT(nsid == 1);
+ CU_ASSERT(subsystem.max_nsid == 1);
+ SPDK_CU_ASSERT_FATAL(subsystem.ns != NULL);
+ SPDK_CU_ASSERT_FATAL(subsystem.ns[nsid - 1] != NULL);
+ CU_ASSERT(subsystem.ns[nsid - 1]->bdev == &bdev1);
+
+ /* Request a specific NSID */
+ spdk_nvmf_ns_opts_get_defaults(&ns_opts, sizeof(ns_opts));
+ ns_opts.nsid = 5;
+ nsid = spdk_nvmf_subsystem_add_ns(&subsystem, &bdev2, &ns_opts, sizeof(ns_opts));
+ CU_ASSERT(nsid == 5);
+ CU_ASSERT(subsystem.max_nsid == 5);
+ SPDK_CU_ASSERT_FATAL(subsystem.ns[nsid - 1] != NULL);
+ CU_ASSERT(subsystem.ns[nsid - 1]->bdev == &bdev2);
+
+ /* Request an NSID that is already in use */
+ spdk_nvmf_ns_opts_get_defaults(&ns_opts, sizeof(ns_opts));
+ ns_opts.nsid = 5;
+ nsid = spdk_nvmf_subsystem_add_ns(&subsystem, &bdev2, &ns_opts, sizeof(ns_opts));
+ CU_ASSERT(nsid == 0);
+ CU_ASSERT(subsystem.max_nsid == 5);
+
+ /* Request 0xFFFFFFFF (invalid NSID, reserved for broadcast) */
+ spdk_nvmf_ns_opts_get_defaults(&ns_opts, sizeof(ns_opts));
+ ns_opts.nsid = 0xFFFFFFFF;
+ nsid = spdk_nvmf_subsystem_add_ns(&subsystem, &bdev2, &ns_opts, sizeof(ns_opts));
+ CU_ASSERT(nsid == 0);
+ CU_ASSERT(subsystem.max_nsid == 5);
+
+ spdk_nvmf_subsystem_remove_ns(&subsystem, 1, subsystem_ns_remove_cb, NULL);
+ spdk_nvmf_subsystem_remove_ns(&subsystem, 5, subsystem_ns_remove_cb, NULL);
+
+ free(subsystem.ns);
+ free(tgt.subsystems);
+}
+
+static void
+nvmf_test_create_subsystem(void)
+{
+ struct spdk_nvmf_tgt tgt = {};
+ char nqn[256];
+ struct spdk_nvmf_subsystem *subsystem;
+
+ tgt.opts.max_subsystems = 1024;
+ tgt.subsystems = calloc(tgt.opts.max_subsystems, sizeof(struct spdk_nvmf_subsystem *));
+ SPDK_CU_ASSERT_FATAL(tgt.subsystems != NULL);
+
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:subsystem1");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem != NULL);
+ CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn);
+ spdk_nvmf_subsystem_destroy(subsystem);
+
+ /* valid name with complex reverse domain */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk-full--rev-domain.name:subsystem1");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem != NULL);
+ CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn);
+ spdk_nvmf_subsystem_destroy(subsystem);
+
+ /* Valid name discovery controller */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:subsystem1");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem != NULL);
+ CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn);
+ spdk_nvmf_subsystem_destroy(subsystem);
+
+
+ /* Invalid name, no user supplied string */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem == NULL);
+
+ /* Valid name, only contains top-level domain name */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:subsystem1");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem != NULL);
+ CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn);
+ spdk_nvmf_subsystem_destroy(subsystem);
+
+ /* Invalid name, domain label > 63 characters */
+ snprintf(nqn, sizeof(nqn),
+ "nqn.2016-06.io.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz:sub");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem == NULL);
+
+ /* Invalid name, domain label starts with digit */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.3spdk:sub");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem == NULL);
+
+ /* Invalid name, domain label starts with - */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.-spdk:subsystem1");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem == NULL);
+
+ /* Invalid name, domain label ends with - */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk-:subsystem1");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem == NULL);
+
+ /* Invalid name, domain label with multiple consecutive periods */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io..spdk:subsystem1");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem == NULL);
+
+ /* Longest valid name */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:");
+ memset(nqn + strlen(nqn), 'a', 223 - strlen(nqn));
+ nqn[223] = '\0';
+ CU_ASSERT(strlen(nqn) == 223);
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem != NULL);
+ CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn);
+ spdk_nvmf_subsystem_destroy(subsystem);
+
+ /* Invalid name, too long */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:");
+ memset(nqn + strlen(nqn), 'a', 224 - strlen(nqn));
+ nqn[224] = '\0';
+ CU_ASSERT(strlen(nqn) == 224);
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ CU_ASSERT(subsystem == NULL);
+
+ /* Valid name using uuid format */
+ snprintf(nqn, sizeof(nqn), "nqn.2014-08.org.nvmexpress:uuid:11111111-aaaa-bbdd-FFEE-123456789abc");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem != NULL);
+ CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn);
+ spdk_nvmf_subsystem_destroy(subsystem);
+
+ /* Invalid name user string contains an invalid utf-8 character */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:\xFFsubsystem1");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem == NULL);
+
+ /* Valid name with non-ascii but valid utf-8 characters */
+ snprintf(nqn, sizeof(nqn), "nqn.2016-06.io.spdk:\xe1\x8a\x88subsystem1\xca\x80");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem != NULL);
+ CU_ASSERT_STRING_EQUAL(subsystem->subnqn, nqn);
+ spdk_nvmf_subsystem_destroy(subsystem);
+
+ /* Invalid uuid (too long) */
+ snprintf(nqn, sizeof(nqn),
+ "nqn.2014-08.org.nvmexpress:uuid:11111111-aaaa-bbdd-FFEE-123456789abcdef");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem == NULL);
+
+ /* Invalid uuid (dashes placed incorrectly) */
+ snprintf(nqn, sizeof(nqn), "nqn.2014-08.org.nvmexpress:uuid:111111-11aaaa-bbdd-FFEE-123456789abc");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem == NULL);
+
+ /* Invalid uuid (invalid characters in uuid) */
+ snprintf(nqn, sizeof(nqn), "nqn.2014-08.org.nvmexpress:uuid:111hg111-aaaa-bbdd-FFEE-123456789abc");
+ subsystem = spdk_nvmf_subsystem_create(&tgt, nqn, SPDK_NVMF_SUBTYPE_NVME, 0);
+ SPDK_CU_ASSERT_FATAL(subsystem == NULL);
+
+ free(tgt.subsystems);
+}
+
+static void
+test_spdk_nvmf_subsystem_set_sn(void)
+{
+ struct spdk_nvmf_subsystem subsystem = {};
+
+ /* Basic valid serial number */
+ CU_ASSERT(spdk_nvmf_subsystem_set_sn(&subsystem, "abcd xyz") == 0);
+ CU_ASSERT(strcmp(subsystem.sn, "abcd xyz") == 0);
+
+ /* Exactly 20 characters (valid) */
+ CU_ASSERT(spdk_nvmf_subsystem_set_sn(&subsystem, "12345678901234567890") == 0);
+ CU_ASSERT(strcmp(subsystem.sn, "12345678901234567890") == 0);
+
+ /* 21 characters (too long, invalid) */
+ CU_ASSERT(spdk_nvmf_subsystem_set_sn(&subsystem, "123456789012345678901") < 0);
+
+ /* Non-ASCII characters (invalid) */
+ CU_ASSERT(spdk_nvmf_subsystem_set_sn(&subsystem, "abcd\txyz") < 0);
+}
+
+int main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("nvmf", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "create_subsystem", nvmf_test_create_subsystem) == NULL ||
+ CU_add_test(suite, "nvmf_subsystem_add_ns", test_spdk_nvmf_subsystem_add_ns) == NULL ||
+ CU_add_test(suite, "nvmf_subsystem_set_sn", test_spdk_nvmf_subsystem_set_sn) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ spdk_allocate_thread(_subsystem_send_msg, NULL, NULL, NULL, "thread0");
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ spdk_free_thread();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/scsi/Makefile b/src/spdk/test/unit/lib/scsi/Makefile
new file mode 100644
index 00000000..9e413897
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/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 = dev.c lun.c scsi.c scsi_bdev.c
+
+.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/unit/lib/scsi/dev.c/.gitignore b/src/spdk/test/unit/lib/scsi/dev.c/.gitignore
new file mode 100644
index 00000000..e325086b
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/dev.c/.gitignore
@@ -0,0 +1 @@
+dev_ut
diff --git a/src/spdk/test/unit/lib/scsi/dev.c/Makefile b/src/spdk/test/unit/lib/scsi/dev.c/Makefile
new file mode 100644
index 00000000..4e7a5fa9
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/dev.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = dev_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/scsi/dev.c/dev_ut.c b/src/spdk/test/unit/lib/scsi/dev.c/dev_ut.c
new file mode 100644
index 00000000..c10a7f0a
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/dev.c/dev_ut.c
@@ -0,0 +1,681 @@
+/*-
+ * 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 "CUnit/Basic.h"
+#include "spdk_cunit.h"
+
+#include "spdk/util.h"
+
+#include "scsi/dev.c"
+#include "scsi/port.c"
+
+/* Unit test bdev mockup */
+struct spdk_bdev {
+ char name[100];
+};
+
+static struct spdk_bdev g_bdevs[] = {
+ {"malloc0"},
+ {"malloc1"},
+};
+
+const char *
+spdk_bdev_get_name(const struct spdk_bdev *bdev)
+{
+ return bdev->name;
+}
+
+static struct spdk_scsi_task *
+spdk_get_task(uint32_t *owner_task_ctr)
+{
+ struct spdk_scsi_task *task;
+
+ task = calloc(1, sizeof(*task));
+ if (!task) {
+ return NULL;
+ }
+
+ return task;
+}
+
+void
+spdk_scsi_task_put(struct spdk_scsi_task *task)
+{
+ free(task);
+}
+
+_spdk_scsi_lun *
+spdk_scsi_lun_construct(struct spdk_bdev *bdev,
+ void (*hotremove_cb)(const struct spdk_scsi_lun *, void *),
+ void *hotremove_ctx)
+{
+ struct spdk_scsi_lun *lun;
+
+ lun = calloc(1, sizeof(struct spdk_scsi_lun));
+ SPDK_CU_ASSERT_FATAL(lun != NULL);
+
+ lun->bdev = bdev;
+
+ return lun;
+}
+
+void
+spdk_scsi_lun_destruct(struct spdk_scsi_lun *lun)
+{
+ free(lun);
+}
+
+struct spdk_bdev *
+spdk_bdev_get_by_name(const char *bdev_name)
+{
+ size_t i;
+
+ for (i = 0; i < SPDK_COUNTOF(g_bdevs); i++) {
+ if (strcmp(bdev_name, g_bdevs[i].name) == 0) {
+ return &g_bdevs[i];
+ }
+ }
+
+ return NULL;
+}
+
+int
+spdk_scsi_lun_task_mgmt_execute(struct spdk_scsi_task *task, enum spdk_scsi_task_func func)
+{
+ return 0;
+}
+
+void
+spdk_scsi_lun_execute_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task)
+{
+}
+
+int
+_spdk_scsi_lun_allocate_io_channel(struct spdk_scsi_lun *lun)
+{
+ return 0;
+}
+
+void
+_spdk_scsi_lun_free_io_channel(struct spdk_scsi_lun *lun)
+{
+}
+
+bool
+spdk_scsi_lun_has_pending_tasks(const struct spdk_scsi_lun *lun)
+{
+ return false;
+}
+
+static void
+dev_destruct_null_dev(void)
+{
+ /* pass null for the dev */
+ spdk_scsi_dev_destruct(NULL);
+}
+
+static void
+dev_destruct_zero_luns(void)
+{
+ struct spdk_scsi_dev dev = { .is_allocated = 1 };
+
+ /* No luns attached to the dev */
+
+ /* free the dev */
+ spdk_scsi_dev_destruct(&dev);
+}
+
+static void
+dev_destruct_null_lun(void)
+{
+ struct spdk_scsi_dev dev = { .is_allocated = 1 };
+
+ /* pass null for the lun */
+ dev.lun[0] = NULL;
+
+ /* free the dev */
+ spdk_scsi_dev_destruct(&dev);
+}
+
+static void
+dev_destruct_success(void)
+{
+ struct spdk_scsi_dev dev = { .is_allocated = 1 };
+ int rc;
+
+ /* dev with a single lun */
+ rc = spdk_scsi_dev_add_lun(&dev, "malloc0", 0, NULL, NULL);
+
+ CU_ASSERT(rc == 0);
+
+ /* free the dev */
+ spdk_scsi_dev_destruct(&dev);
+
+}
+
+static void
+dev_construct_num_luns_zero(void)
+{
+ struct spdk_scsi_dev *dev;
+ const char *bdev_name_list[1] = {};
+ int lun_id_list[1] = { 0 };
+
+ dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 0,
+ SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL);
+
+ /* dev should be null since we passed num_luns = 0 */
+ CU_ASSERT_TRUE(dev == NULL);
+}
+
+static void
+dev_construct_no_lun_zero(void)
+{
+ struct spdk_scsi_dev *dev;
+ const char *bdev_name_list[1] = {};
+ int lun_id_list[1] = { 0 };
+
+ lun_id_list[0] = 1;
+
+ dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1,
+ SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL);
+
+ /* dev should be null since no LUN0 was specified (lun_id_list[0] = 1) */
+ CU_ASSERT_TRUE(dev == NULL);
+}
+
+static void
+dev_construct_null_lun(void)
+{
+ struct spdk_scsi_dev *dev;
+ const char *bdev_name_list[1] = {};
+ int lun_id_list[1] = { 0 };
+
+ dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1,
+ SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL);
+
+ /* dev should be null since no LUN0 was specified (lun_list[0] = NULL) */
+ CU_ASSERT_TRUE(dev == NULL);
+}
+
+static void
+dev_construct_name_too_long(void)
+{
+ struct spdk_scsi_dev *dev;
+ const char *bdev_name_list[1] = {"malloc0"};
+ int lun_id_list[1] = { 0 };
+ char name[SPDK_SCSI_DEV_MAX_NAME + 1 + 1];
+
+ /* Try to construct a dev with a name that is one byte longer than allowed. */
+ memset(name, 'x', sizeof(name) - 1);
+ name[sizeof(name) - 1] = '\0';
+
+ dev = spdk_scsi_dev_construct(name, bdev_name_list, lun_id_list, 1,
+ SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL);
+
+ CU_ASSERT(dev == NULL);
+}
+
+static void
+dev_construct_success(void)
+{
+ struct spdk_scsi_dev *dev;
+ const char *bdev_name_list[1] = {"malloc0"};
+ int lun_id_list[1] = { 0 };
+
+ dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1,
+ SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL);
+
+ /* Successfully constructs and returns a dev */
+ CU_ASSERT_TRUE(dev != NULL);
+
+ /* free the dev */
+ spdk_scsi_dev_destruct(dev);
+}
+
+static void
+dev_construct_success_lun_zero_not_first(void)
+{
+ struct spdk_scsi_dev *dev;
+ const char *bdev_name_list[2] = {"malloc1", "malloc0"};
+ int lun_id_list[2] = { 1, 0 };
+
+ dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 2,
+ SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL);
+
+ /* Successfully constructs and returns a dev */
+ CU_ASSERT_TRUE(dev != NULL);
+
+ /* free the dev */
+ spdk_scsi_dev_destruct(dev);
+}
+
+static void
+dev_queue_mgmt_task_success(void)
+{
+ struct spdk_scsi_dev *dev;
+ const char *bdev_name_list[1] = {"malloc0"};
+ int lun_id_list[1] = { 0 };
+ struct spdk_scsi_task *task;
+
+ dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1,
+ SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL);
+
+ /* Successfully constructs and returns a dev */
+ CU_ASSERT_TRUE(dev != NULL);
+
+ task = spdk_get_task(NULL);
+
+ spdk_scsi_dev_queue_mgmt_task(dev, task, SPDK_SCSI_TASK_FUNC_LUN_RESET);
+
+ spdk_scsi_task_put(task);
+
+ spdk_scsi_dev_destruct(dev);
+}
+
+static void
+dev_queue_task_success(void)
+{
+ struct spdk_scsi_dev *dev;
+ const char *bdev_name_list[1] = {"malloc0"};
+ int lun_id_list[1] = { 0 };
+ struct spdk_scsi_task *task;
+
+ dev = spdk_scsi_dev_construct("Name", bdev_name_list, lun_id_list, 1,
+ SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL);
+
+ /* Successfully constructs and returns a dev */
+ CU_ASSERT_TRUE(dev != NULL);
+
+ task = spdk_get_task(NULL);
+
+ spdk_scsi_dev_queue_task(dev, task);
+
+ spdk_scsi_task_put(task);
+
+ spdk_scsi_dev_destruct(dev);
+}
+
+static void
+dev_stop_success(void)
+{
+ struct spdk_scsi_dev dev = { 0 };
+ struct spdk_scsi_task *task;
+ struct spdk_scsi_task *task_mgmt;
+
+ task = spdk_get_task(NULL);
+
+ spdk_scsi_dev_queue_task(&dev, task);
+
+ task_mgmt = spdk_get_task(NULL);
+
+ /* Enqueue the tasks into dev->task_mgmt_submit_queue */
+ spdk_scsi_dev_queue_mgmt_task(&dev, task_mgmt, SPDK_SCSI_TASK_FUNC_LUN_RESET);
+
+ spdk_scsi_task_put(task);
+ spdk_scsi_task_put(task_mgmt);
+}
+
+static void
+dev_add_port_max_ports(void)
+{
+ struct spdk_scsi_dev dev = { 0 };
+ const char *name;
+ int id, rc;
+
+ /* dev is set to SPDK_SCSI_DEV_MAX_PORTS */
+ dev.num_ports = SPDK_SCSI_DEV_MAX_PORTS;
+ name = "Name of Port";
+ id = 1;
+
+ rc = spdk_scsi_dev_add_port(&dev, id, name);
+
+ /* returns -1; since the dev already has maximum
+ * number of ports (SPDK_SCSI_DEV_MAX_PORTS) */
+ CU_ASSERT_TRUE(rc < 0);
+}
+
+static void
+dev_add_port_construct_failure1(void)
+{
+ struct spdk_scsi_dev dev = { 0 };
+ const int port_name_length = SPDK_SCSI_PORT_MAX_NAME_LENGTH + 2;
+ char name[port_name_length];
+ uint64_t id;
+ int rc;
+
+ dev.num_ports = 1;
+ /* Set the name such that the length exceeds SPDK_SCSI_PORT_MAX_NAME_LENGTH
+ * SPDK_SCSI_PORT_MAX_NAME_LENGTH = 256 */
+ memset(name, 'a', port_name_length - 1);
+ name[port_name_length - 1] = '\0';
+ id = 1;
+
+ rc = spdk_scsi_dev_add_port(&dev, id, name);
+
+ /* returns -1; since the length of the name exceeds
+ * SPDK_SCSI_PORT_MAX_NAME_LENGTH */
+ CU_ASSERT_TRUE(rc < 0);
+}
+
+static void
+dev_add_port_construct_failure2(void)
+{
+ struct spdk_scsi_dev dev = { 0 };
+ const char *name;
+ uint64_t id;
+ int rc;
+
+ dev.num_ports = 1;
+ name = "Name of Port";
+ id = 1;
+
+ /* Initialize port[0] to be valid and its index is set to 1 */
+ dev.port[0].id = id;
+ dev.port[0].is_used = 1;
+
+ rc = spdk_scsi_dev_add_port(&dev, id, name);
+
+ /* returns -1; since the dev already has a port whose index to be 1 */
+ CU_ASSERT_TRUE(rc < 0);
+}
+
+static void
+dev_add_port_success1(void)
+{
+ struct spdk_scsi_dev dev = { 0 };
+ const char *name;
+ int id, rc;
+
+ dev.num_ports = 1;
+ name = "Name of Port";
+ id = 1;
+
+ rc = spdk_scsi_dev_add_port(&dev, id, name);
+
+ /* successfully adds a port */
+ CU_ASSERT_EQUAL(rc, 0);
+ /* Assert num_ports has been incremented to 2 */
+ CU_ASSERT_EQUAL(dev.num_ports, 2);
+}
+
+static void
+dev_add_port_success2(void)
+{
+ struct spdk_scsi_dev dev = { 0 };
+ const char *name;
+ uint64_t id;
+ int rc;
+
+ dev.num_ports = 1;
+ name = "Name of Port";
+ id = 1;
+ /* set id of invalid port[0] to 1. This must be ignored */
+ dev.port[0].id = id;
+ dev.port[0].is_used = 0;
+
+ rc = spdk_scsi_dev_add_port(&dev, id, name);
+
+ /* successfully adds a port */
+ CU_ASSERT_EQUAL(rc, 0);
+ /* Assert num_ports has been incremented to 1 */
+ CU_ASSERT_EQUAL(dev.num_ports, 2);
+}
+
+static void
+dev_add_port_success3(void)
+{
+ struct spdk_scsi_dev dev = { 0 };
+ const char *name;
+ uint64_t add_id;
+ int rc;
+
+ dev.num_ports = 1;
+ name = "Name of Port";
+ dev.port[0].id = 1;
+ dev.port[0].is_used = 1;
+ add_id = 2;
+
+ /* Add a port with id = 2 */
+ rc = spdk_scsi_dev_add_port(&dev, add_id, name);
+
+ /* successfully adds a port */
+ CU_ASSERT_EQUAL(rc, 0);
+ /* Assert num_ports has been incremented to 2 */
+ CU_ASSERT_EQUAL(dev.num_ports, 2);
+}
+
+static void
+dev_find_port_by_id_num_ports_zero(void)
+{
+ struct spdk_scsi_dev dev = { 0 };
+ struct spdk_scsi_port *rp_port;
+ uint64_t id;
+
+ dev.num_ports = 0;
+ id = 1;
+
+ rp_port = spdk_scsi_dev_find_port_by_id(&dev, id);
+
+ /* returns null; since dev's num_ports is 0 */
+ CU_ASSERT_TRUE(rp_port == NULL);
+}
+
+static void
+dev_find_port_by_id_id_not_found_failure(void)
+{
+ struct spdk_scsi_dev dev = { 0 };
+ struct spdk_scsi_port *rp_port;
+ const char *name;
+ int rc;
+ uint64_t id, find_id;
+
+ id = 1;
+ dev.num_ports = 1;
+ name = "Name of Port";
+ find_id = 2;
+
+ /* Add a port with id = 1 */
+ rc = spdk_scsi_dev_add_port(&dev, id, name);
+
+ CU_ASSERT_EQUAL(rc, 0);
+
+ /* Find port with id = 2 */
+ rp_port = spdk_scsi_dev_find_port_by_id(&dev, find_id);
+
+ /* returns null; failed to find port specified by id = 2 */
+ CU_ASSERT_TRUE(rp_port == NULL);
+}
+
+static void
+dev_find_port_by_id_success(void)
+{
+ struct spdk_scsi_dev dev = { 0 };
+ struct spdk_scsi_port *rp_port;
+ const char *name;
+ int rc;
+ uint64_t id;
+
+ id = 1;
+ dev.num_ports = 1;
+ name = "Name of Port";
+
+ /* Add a port */
+ rc = spdk_scsi_dev_add_port(&dev, id, name);
+
+ CU_ASSERT_EQUAL(rc, 0);
+
+ /* Find port by the same id as the one added above */
+ rp_port = spdk_scsi_dev_find_port_by_id(&dev, id);
+
+ /* Successfully found port specified by id */
+ CU_ASSERT_TRUE(rp_port != NULL);
+ if (rp_port != NULL) {
+ /* Assert the found port's id and name are same as
+ * the port added. */
+ CU_ASSERT_EQUAL(rp_port->id, 1);
+ CU_ASSERT_STRING_EQUAL(rp_port->name, "Name of Port");
+ }
+}
+
+static void
+dev_add_lun_bdev_not_found(void)
+{
+ int rc;
+ struct spdk_scsi_dev dev = {0};
+
+ rc = spdk_scsi_dev_add_lun(&dev, "malloc2", 0, NULL, NULL);
+
+ SPDK_CU_ASSERT_FATAL(dev.lun[0] == NULL);
+ CU_ASSERT_NOT_EQUAL(rc, 0);
+}
+
+static void
+dev_add_lun_no_free_lun_id(void)
+{
+ int rc;
+ int i;
+ struct spdk_scsi_dev dev = {0};
+ struct spdk_scsi_lun lun;
+
+ for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) {
+ dev.lun[i] = &lun;
+ }
+
+ rc = spdk_scsi_dev_add_lun(&dev, "malloc0", -1, NULL, NULL);
+
+ CU_ASSERT_NOT_EQUAL(rc, 0);
+}
+
+static void
+dev_add_lun_success1(void)
+{
+ int rc;
+ struct spdk_scsi_dev dev = {0};
+
+ rc = spdk_scsi_dev_add_lun(&dev, "malloc0", -1, NULL, NULL);
+
+ CU_ASSERT_EQUAL(rc, 0);
+
+ spdk_scsi_dev_destruct(&dev);
+}
+
+static void
+dev_add_lun_success2(void)
+{
+ int rc;
+ struct spdk_scsi_dev dev = {0};
+
+ rc = spdk_scsi_dev_add_lun(&dev, "malloc0", 0, NULL, NULL);
+
+ CU_ASSERT_EQUAL(rc, 0);
+
+ spdk_scsi_dev_destruct(&dev);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("dev_suite", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "destruct - null dev",
+ dev_destruct_null_dev) == NULL
+ || CU_add_test(suite, "destruct - zero luns", dev_destruct_zero_luns) == NULL
+ || CU_add_test(suite, "destruct - null lun", dev_destruct_null_lun) == NULL
+ || CU_add_test(suite, "destruct - success", dev_destruct_success) == NULL
+ || CU_add_test(suite, "construct - queue depth gt max depth",
+ dev_construct_num_luns_zero) == NULL
+ || CU_add_test(suite, "construct - no lun0",
+ dev_construct_no_lun_zero) == NULL
+ || CU_add_test(suite, "construct - null lun",
+ dev_construct_null_lun) == NULL
+ || CU_add_test(suite, "construct - name too long", dev_construct_name_too_long) == NULL
+ || CU_add_test(suite, "construct - success", dev_construct_success) == NULL
+ || CU_add_test(suite, "construct - success - LUN zero not first",
+ dev_construct_success_lun_zero_not_first) == NULL
+ || CU_add_test(suite, "dev queue task mgmt - success",
+ dev_queue_mgmt_task_success) == NULL
+ || CU_add_test(suite, "dev queue task - success",
+ dev_queue_task_success) == NULL
+ || CU_add_test(suite, "dev stop - success", dev_stop_success) == NULL
+ || CU_add_test(suite, "dev add port - max ports",
+ dev_add_port_max_ports) == NULL
+ || CU_add_test(suite, "dev add port - construct port failure 1",
+ dev_add_port_construct_failure1) == NULL
+ || CU_add_test(suite, "dev add port - construct port failure 2",
+ dev_add_port_construct_failure2) == NULL
+ || CU_add_test(suite, "dev add port - success 1",
+ dev_add_port_success1) == NULL
+ || CU_add_test(suite, "dev add port - success 2",
+ dev_add_port_success2) == NULL
+ || CU_add_test(suite, "dev add port - success 3",
+ dev_add_port_success3) == NULL
+ || CU_add_test(suite, "dev find port by id - num ports zero",
+ dev_find_port_by_id_num_ports_zero) == NULL
+ || CU_add_test(suite, "dev find port by id - different port id failure",
+ dev_find_port_by_id_id_not_found_failure) == NULL
+ || CU_add_test(suite, "dev find port by id - success",
+ dev_find_port_by_id_success) == NULL
+ || CU_add_test(suite, "dev add lun - bdev not found",
+ dev_add_lun_bdev_not_found) == NULL
+ || CU_add_test(suite, "dev add lun - no free lun id",
+ dev_add_lun_no_free_lun_id) == NULL
+ || CU_add_test(suite, "dev add lun - success 1",
+ dev_add_lun_success1) == NULL
+ || CU_add_test(suite, "dev add lun - success 2",
+ dev_add_lun_success2) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/scsi/lun.c/.gitignore b/src/spdk/test/unit/lib/scsi/lun.c/.gitignore
new file mode 100644
index 00000000..89bd2aaf
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/lun.c/.gitignore
@@ -0,0 +1 @@
+lun_ut
diff --git a/src/spdk/test/unit/lib/scsi/lun.c/Makefile b/src/spdk/test/unit/lib/scsi/lun.c/Makefile
new file mode 100644
index 00000000..22841b0d
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/lun.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = lun_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/scsi/lun.c/lun_ut.c b/src/spdk/test/unit/lib/scsi/lun.c/lun_ut.c
new file mode 100644
index 00000000..2237e8ed
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/lun.c/lun_ut.c
@@ -0,0 +1,654 @@
+/*-
+ * 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_cunit.h"
+
+#include "scsi/task.c"
+#include "scsi/lun.c"
+
+#include "spdk_internal/mock.h"
+
+/* Unit test bdev mockup */
+struct spdk_bdev {
+ int x;
+};
+
+SPDK_LOG_REGISTER_COMPONENT("scsi", SPDK_LOG_SCSI)
+
+struct spdk_scsi_globals g_spdk_scsi;
+
+static bool g_lun_execute_fail = false;
+static int g_lun_execute_status = SPDK_SCSI_TASK_PENDING;
+static uint32_t g_task_count = 0;
+
+struct spdk_poller *
+spdk_poller_register(spdk_poller_fn fn,
+ void *arg,
+ uint64_t period_microseconds)
+{
+ return NULL;
+}
+
+void
+spdk_poller_unregister(struct spdk_poller **ppoller)
+{
+}
+
+void
+spdk_thread_send_msg(const struct spdk_thread *thread, spdk_thread_fn fn, void *ctx)
+{
+}
+
+struct spdk_trace_histories *g_trace_histories;
+void _spdk_trace_record(uint64_t tsc, uint16_t tpoint_id, uint16_t poller_id,
+ uint32_t size, uint64_t object_id, uint64_t arg1)
+{
+}
+
+static void
+spdk_lun_ut_cpl_task(struct spdk_scsi_task *task)
+{
+ SPDK_CU_ASSERT_FATAL(g_task_count > 0);
+ g_task_count--;
+}
+
+static void
+spdk_lun_ut_free_task(struct spdk_scsi_task *task)
+{
+}
+
+static void
+ut_init_task(struct spdk_scsi_task *task)
+{
+ memset(task, 0, sizeof(*task));
+ spdk_scsi_task_construct(task, spdk_lun_ut_cpl_task,
+ spdk_lun_ut_free_task);
+ g_task_count++;
+}
+
+void *
+spdk_dma_malloc(size_t size, size_t align, uint64_t *phys_addr)
+{
+ void *buf = malloc(size);
+ if (phys_addr) {
+ *phys_addr = (uint64_t)buf;
+ }
+ return buf;
+}
+
+void *
+spdk_dma_zmalloc(size_t size, size_t align, uint64_t *phys_addr)
+{
+ void *buf = calloc(size, 1);
+ if (phys_addr) {
+ *phys_addr = (uint64_t)buf;
+ }
+ return buf;
+}
+
+void
+spdk_dma_free(void *buf)
+{
+ free(buf);
+}
+
+void
+spdk_bdev_free_io(struct spdk_bdev_io *bdev_io)
+{
+ CU_ASSERT(0);
+}
+
+int
+spdk_bdev_open(struct spdk_bdev *bdev, bool write, spdk_bdev_remove_cb_t remove_cb,
+ void *remove_ctx, struct spdk_bdev_desc **desc)
+{
+ return 0;
+}
+
+void
+spdk_bdev_close(struct spdk_bdev_desc *desc)
+{
+}
+
+const char *
+spdk_bdev_get_name(const struct spdk_bdev *bdev)
+{
+ return "test";
+}
+
+void spdk_scsi_dev_queue_mgmt_task(struct spdk_scsi_dev *dev,
+ struct spdk_scsi_task *task,
+ enum spdk_scsi_task_func func)
+{
+}
+
+void spdk_scsi_dev_delete_lun(struct spdk_scsi_dev *dev,
+ struct spdk_scsi_lun *lun)
+{
+ return;
+}
+
+void
+spdk_bdev_scsi_reset(struct spdk_scsi_task *task)
+{
+ return;
+}
+
+int
+spdk_bdev_scsi_execute(struct spdk_scsi_task *task)
+{
+ if (g_lun_execute_fail) {
+ return -EINVAL;
+ } else {
+ task->status = SPDK_SCSI_STATUS_GOOD;
+
+ if (g_lun_execute_status == SPDK_SCSI_TASK_PENDING) {
+ return g_lun_execute_status;
+ } else if (g_lun_execute_status == SPDK_SCSI_TASK_COMPLETE) {
+ return g_lun_execute_status;
+ } else {
+ return 0;
+ }
+ }
+}
+
+struct spdk_io_channel *
+spdk_bdev_get_io_channel(struct spdk_bdev_desc *desc)
+{
+ return NULL;
+}
+
+void
+spdk_put_io_channel(struct spdk_io_channel *ch)
+{
+}
+
+DEFINE_STUB(spdk_io_channel_get_thread, struct spdk_thread *, (struct spdk_io_channel *ch), NULL)
+DEFINE_STUB(spdk_get_thread, struct spdk_thread *, (void), NULL)
+
+static _spdk_scsi_lun *
+lun_construct(void)
+{
+ struct spdk_scsi_lun *lun;
+ struct spdk_bdev bdev;
+
+ lun = spdk_scsi_lun_construct(&bdev, NULL, NULL);
+
+ SPDK_CU_ASSERT_FATAL(lun != NULL);
+ return lun;
+}
+
+static void
+lun_destruct(struct spdk_scsi_lun *lun)
+{
+ /* LUN will defer its removal if there are any unfinished tasks */
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&lun->tasks));
+
+ spdk_scsi_lun_destruct(lun);
+}
+
+static void
+lun_task_mgmt_execute_null_task(void)
+{
+ int rc;
+
+ rc = spdk_scsi_lun_task_mgmt_execute(NULL, SPDK_SCSI_TASK_FUNC_ABORT_TASK);
+
+ /* returns -1 since we passed NULL for the task */
+ CU_ASSERT_TRUE(rc < 0);
+ CU_ASSERT_EQUAL(g_task_count, 0);
+}
+
+static void
+lun_task_mgmt_execute_abort_task_null_lun_failure(void)
+{
+ struct spdk_scsi_task mgmt_task = { 0 };
+ struct spdk_scsi_port initiator_port = { 0 };
+ int rc;
+
+ ut_init_task(&mgmt_task);
+ mgmt_task.lun = NULL;
+ mgmt_task.initiator_port = &initiator_port;
+
+ rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_ABORT_TASK);
+
+ /* returns -1 since we passed NULL for LUN */
+ CU_ASSERT_TRUE(rc < 0);
+ CU_ASSERT_EQUAL(g_task_count, 0);
+}
+
+static void
+lun_task_mgmt_execute_abort_task_not_supported(void)
+{
+ struct spdk_scsi_lun *lun;
+ struct spdk_scsi_task task = { 0 };
+ struct spdk_scsi_task mgmt_task = { 0 };
+ struct spdk_scsi_port initiator_port = { 0 };
+ struct spdk_scsi_dev dev = { 0 };
+ uint8_t cdb[6] = { 0 };
+ int rc;
+
+ lun = lun_construct();
+ lun->dev = &dev;
+
+ ut_init_task(&mgmt_task);
+ mgmt_task.lun = lun;
+ mgmt_task.initiator_port = &initiator_port;
+
+ /* Params to add regular task to the lun->tasks */
+ ut_init_task(&task);
+ task.lun = lun;
+ task.cdb = cdb;
+
+ spdk_scsi_lun_execute_task(lun, &task);
+
+ /* task should now be on the tasks list */
+ CU_ASSERT(!TAILQ_EMPTY(&lun->tasks));
+
+ rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_ABORT_TASK);
+
+ /* returns -1 since task abort is not supported */
+ CU_ASSERT_TRUE(rc < 0);
+ CU_ASSERT(mgmt_task.response == SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED);
+
+ /* task is still on the tasks list */
+ CU_ASSERT_EQUAL(g_task_count, 1);
+
+ spdk_scsi_lun_complete_task(lun, &task);
+ CU_ASSERT_EQUAL(g_task_count, 0);
+
+ lun_destruct(lun);
+}
+
+static void
+lun_task_mgmt_execute_abort_task_all_null_lun_failure(void)
+{
+ struct spdk_scsi_task mgmt_task = { 0 };
+ struct spdk_scsi_port initiator_port = { 0 };
+ int rc;
+
+ ut_init_task(&mgmt_task);
+ mgmt_task.lun = NULL;
+ mgmt_task.initiator_port = &initiator_port;
+
+ rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_ABORT_TASK_SET);
+
+ /* Returns -1 since we passed NULL for lun */
+ CU_ASSERT_TRUE(rc < 0);
+
+ CU_ASSERT_EQUAL(g_task_count, 0);
+}
+
+static void
+lun_task_mgmt_execute_abort_task_all_not_supported(void)
+{
+ struct spdk_scsi_lun *lun;
+ struct spdk_scsi_task task = { 0 };
+ struct spdk_scsi_task mgmt_task = { 0 };
+ struct spdk_scsi_port initiator_port = { 0 };
+ struct spdk_scsi_dev dev = { 0 };
+ int rc;
+ uint8_t cdb[6] = { 0 };
+
+ lun = lun_construct();
+ lun->dev = &dev;
+
+ ut_init_task(&mgmt_task);
+ mgmt_task.lun = lun;
+ mgmt_task.initiator_port = &initiator_port;
+
+ /* Params to add regular task to the lun->tasks */
+ ut_init_task(&task);
+ task.initiator_port = &initiator_port;
+ task.lun = lun;
+ task.cdb = cdb;
+
+ spdk_scsi_lun_execute_task(lun, &task);
+
+ /* task should now be on the tasks list */
+ CU_ASSERT(!TAILQ_EMPTY(&lun->tasks));
+
+ rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_ABORT_TASK_SET);
+
+ /* returns -1 since task abort is not supported */
+ CU_ASSERT_TRUE(rc < 0);
+ CU_ASSERT(mgmt_task.response == SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED);
+
+ /* task is still on the tasks list */
+ CU_ASSERT_EQUAL(g_task_count, 1);
+
+ spdk_scsi_lun_complete_task(lun, &task);
+
+ CU_ASSERT_EQUAL(g_task_count, 0);
+
+ lun_destruct(lun);
+}
+
+static void
+lun_task_mgmt_execute_lun_reset_failure(void)
+{
+ struct spdk_scsi_task mgmt_task = { 0 };
+ int rc;
+
+ ut_init_task(&mgmt_task);
+ mgmt_task.lun = NULL;
+
+ rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_LUN_RESET);
+
+ /* Returns -1 since we passed NULL for lun */
+ CU_ASSERT_TRUE(rc < 0);
+
+ CU_ASSERT_EQUAL(g_task_count, 0);
+}
+
+static void
+lun_task_mgmt_execute_lun_reset(void)
+{
+ struct spdk_scsi_lun *lun;
+ struct spdk_scsi_task mgmt_task = { 0 };
+ struct spdk_scsi_dev dev = { 0 };
+ int rc;
+
+ lun = lun_construct();
+ lun->dev = &dev;
+
+ ut_init_task(&mgmt_task);
+ mgmt_task.lun = lun;
+
+ rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, SPDK_SCSI_TASK_FUNC_LUN_RESET);
+
+ /* Returns success */
+ CU_ASSERT_EQUAL(rc, 0);
+
+ lun_destruct(lun);
+
+ /* task is still on the tasks list */
+ CU_ASSERT_EQUAL(g_task_count, 1);
+ g_task_count = 0;
+}
+
+static void
+lun_task_mgmt_execute_invalid_case(void)
+{
+ struct spdk_scsi_lun *lun;
+ struct spdk_scsi_task mgmt_task = { 0 };
+ struct spdk_scsi_dev dev = { 0 };
+ int rc;
+
+ lun = lun_construct();
+ lun->dev = &dev;
+
+ ut_init_task(&mgmt_task);
+ /* Pass an invalid value to the switch statement */
+ rc = spdk_scsi_lun_task_mgmt_execute(&mgmt_task, 5);
+
+ /* Returns -1 on passing an invalid value to the switch case */
+ CU_ASSERT_TRUE(rc < 0);
+
+ lun_destruct(lun);
+
+ CU_ASSERT_EQUAL(g_task_count, 0);
+}
+
+static void
+lun_append_task_null_lun_task_cdb_spc_inquiry(void)
+{
+ struct spdk_scsi_task task = { 0 };
+ uint8_t cdb[6] = { 0 };
+
+ ut_init_task(&task);
+ task.cdb = cdb;
+ task.cdb[0] = SPDK_SPC_INQUIRY;
+ /* alloc_len >= 4096 */
+ task.cdb[3] = 0xFF;
+ task.cdb[4] = 0xFF;
+ task.lun = NULL;
+
+ spdk_scsi_task_process_null_lun(&task);
+
+ CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_GOOD);
+
+ spdk_scsi_task_put(&task);
+
+ /* spdk_scsi_task_process_null_lun() does not call cpl_fn */
+ CU_ASSERT_EQUAL(g_task_count, 1);
+ g_task_count = 0;
+}
+
+static void
+lun_append_task_null_lun_alloc_len_lt_4096(void)
+{
+ struct spdk_scsi_task task = { 0 };
+ uint8_t cdb[6] = { 0 };
+
+ ut_init_task(&task);
+ task.cdb = cdb;
+ task.cdb[0] = SPDK_SPC_INQUIRY;
+ /* alloc_len < 4096 */
+ task.cdb[3] = 0;
+ task.cdb[4] = 0;
+ /* alloc_len is set to a minimal value of 4096
+ * Hence, buf of size 4096 is allocated */
+ spdk_scsi_task_process_null_lun(&task);
+
+ CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_GOOD);
+
+ spdk_scsi_task_put(&task);
+
+ /* spdk_scsi_task_process_null_lun() does not call cpl_fn */
+ CU_ASSERT_EQUAL(g_task_count, 1);
+ g_task_count = 0;
+}
+
+static void
+lun_append_task_null_lun_not_supported(void)
+{
+ struct spdk_scsi_task task = { 0 };
+ uint8_t cdb[6] = { 0 };
+
+ ut_init_task(&task);
+ task.cdb = cdb;
+ task.lun = NULL;
+
+ spdk_scsi_task_process_null_lun(&task);
+
+ CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_CHECK_CONDITION);
+ /* LUN not supported; task's data transferred should be 0 */
+ CU_ASSERT_EQUAL(task.data_transferred, 0);
+
+ /* spdk_scsi_task_process_null_lun() does not call cpl_fn */
+ CU_ASSERT_EQUAL(g_task_count, 1);
+ g_task_count = 0;
+}
+
+static void
+lun_execute_scsi_task_pending(void)
+{
+ struct spdk_scsi_lun *lun;
+ struct spdk_scsi_task task = { 0 };
+ struct spdk_scsi_dev dev = { 0 };
+
+ lun = lun_construct();
+
+ ut_init_task(&task);
+ task.lun = lun;
+ lun->dev = &dev;
+
+ g_lun_execute_fail = false;
+ g_lun_execute_status = SPDK_SCSI_TASK_PENDING;
+
+ /* the tasks list should still be empty since it has not been
+ executed yet
+ */
+ CU_ASSERT(TAILQ_EMPTY(&lun->tasks));
+
+ spdk_scsi_lun_execute_task(lun, &task);
+
+ /* Assert the task has been successfully added to the tasks queue */
+ CU_ASSERT(!TAILQ_EMPTY(&lun->tasks));
+
+ /* task is still on the tasks list */
+ CU_ASSERT_EQUAL(g_task_count, 1);
+
+ /* Need to complete task so LUN might be removed right now */
+ spdk_scsi_lun_complete_task(lun, &task);
+
+ CU_ASSERT_EQUAL(g_task_count, 0);
+
+ lun_destruct(lun);
+}
+
+static void
+lun_execute_scsi_task_complete(void)
+{
+ struct spdk_scsi_lun *lun;
+ struct spdk_scsi_task task = { 0 };
+ struct spdk_scsi_dev dev = { 0 };
+
+ lun = lun_construct();
+
+ ut_init_task(&task);
+ task.lun = lun;
+ lun->dev = &dev;
+
+ g_lun_execute_fail = false;
+ g_lun_execute_status = SPDK_SCSI_TASK_COMPLETE;
+
+ /* the tasks list should still be empty since it has not been
+ executed yet
+ */
+ CU_ASSERT(TAILQ_EMPTY(&lun->tasks));
+
+ spdk_scsi_lun_execute_task(lun, &task);
+
+ /* Assert the task has not been added to the tasks queue */
+ CU_ASSERT(TAILQ_EMPTY(&lun->tasks));
+
+ lun_destruct(lun);
+
+ CU_ASSERT_EQUAL(g_task_count, 0);
+}
+
+static void
+lun_destruct_success(void)
+{
+ struct spdk_scsi_lun *lun;
+
+ lun = lun_construct();
+
+ spdk_scsi_lun_destruct(lun);
+
+ CU_ASSERT_EQUAL(g_task_count, 0);
+}
+
+static void
+lun_construct_null_ctx(void)
+{
+ struct spdk_scsi_lun *lun;
+
+ lun = spdk_scsi_lun_construct(NULL, NULL, NULL);
+
+ /* lun should be NULL since we passed NULL for the ctx pointer. */
+ CU_ASSERT(lun == NULL);
+ CU_ASSERT_EQUAL(g_task_count, 0);
+}
+
+static void
+lun_construct_success(void)
+{
+ struct spdk_scsi_lun *lun = lun_construct();
+
+ lun_destruct(lun);
+
+ CU_ASSERT_EQUAL(g_task_count, 0);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("lun_suite", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "task management - null task failure",
+ lun_task_mgmt_execute_null_task) == NULL
+ || CU_add_test(suite, "task management abort task - null lun failure",
+ lun_task_mgmt_execute_abort_task_null_lun_failure) == NULL
+ || CU_add_test(suite, "task management abort task - not supported",
+ lun_task_mgmt_execute_abort_task_not_supported) == NULL
+ || CU_add_test(suite, "task management abort task set - null lun failure",
+ lun_task_mgmt_execute_abort_task_all_null_lun_failure) == NULL
+ || CU_add_test(suite, "task management abort task set - success",
+ lun_task_mgmt_execute_abort_task_all_not_supported) == NULL
+ || CU_add_test(suite, "task management - lun reset failure",
+ lun_task_mgmt_execute_lun_reset_failure) == NULL
+ || CU_add_test(suite, "task management - lun reset success",
+ lun_task_mgmt_execute_lun_reset) == NULL
+ || CU_add_test(suite, "task management - invalid option",
+ lun_task_mgmt_execute_invalid_case) == NULL
+ || CU_add_test(suite, "append task - null lun SPDK_SPC_INQUIRY",
+ lun_append_task_null_lun_task_cdb_spc_inquiry) == NULL
+ || CU_add_test(suite, "append task - allocated length less than 4096",
+ lun_append_task_null_lun_alloc_len_lt_4096) == NULL
+ || CU_add_test(suite, "append task - unsupported lun",
+ lun_append_task_null_lun_not_supported) == NULL
+ || CU_add_test(suite, "execute task - scsi task pending",
+ lun_execute_scsi_task_pending) == NULL
+ || CU_add_test(suite, "execute task - scsi task complete",
+ lun_execute_scsi_task_complete) == NULL
+ || CU_add_test(suite, "destruct task - success", lun_destruct_success) == NULL
+ || CU_add_test(suite, "construct - null ctx", lun_construct_null_ctx) == NULL
+ || CU_add_test(suite, "construct - success", lun_construct_success) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/scsi/scsi.c/.gitignore b/src/spdk/test/unit/lib/scsi/scsi.c/.gitignore
new file mode 100644
index 00000000..99a7db2b
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/scsi.c/.gitignore
@@ -0,0 +1 @@
+scsi_ut
diff --git a/src/spdk/test/unit/lib/scsi/scsi.c/Makefile b/src/spdk/test/unit/lib/scsi/scsi.c/Makefile
new file mode 100644
index 00000000..86893653
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/scsi.c/Makefile
@@ -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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+
+SPDK_LIB_LIST = trace
+TEST_FILE = scsi_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/scsi/scsi.c/scsi_ut.c b/src/spdk/test/unit/lib/scsi/scsi.c/scsi_ut.c
new file mode 100644
index 00000000..5a1a31f6
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/scsi.c/scsi_ut.c
@@ -0,0 +1,80 @@
+/*-
+ * 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/scsi.h"
+
+#include "spdk_cunit.h"
+
+#include "scsi/scsi.c"
+
+static void
+scsi_init(void)
+{
+ int rc;
+
+ rc = spdk_scsi_init();
+ CU_ASSERT_EQUAL(rc, 0);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("scsi_suite", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "scsi init", \
+ scsi_init) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/scsi/scsi_bdev.c/.gitignore b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/.gitignore
new file mode 100644
index 00000000..8f1ecc12
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/.gitignore
@@ -0,0 +1 @@
+scsi_bdev_ut
diff --git a/src/spdk/test/unit/lib/scsi/scsi_bdev.c/Makefile b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/Makefile
new file mode 100644
index 00000000..abb1de50
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = scsi_bdev_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/scsi/scsi_bdev.c/scsi_bdev_ut.c b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/scsi_bdev_ut.c
new file mode 100644
index 00000000..4deb2cec
--- /dev/null
+++ b/src/spdk/test/unit/lib/scsi/scsi_bdev.c/scsi_bdev_ut.c
@@ -0,0 +1,988 @@
+/*-
+ * 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 "scsi/task.c"
+#include "scsi/scsi_bdev.c"
+
+#include "spdk_cunit.h"
+
+SPDK_LOG_REGISTER_COMPONENT("scsi", SPDK_LOG_SCSI)
+
+struct spdk_scsi_globals g_spdk_scsi;
+
+static uint64_t g_test_bdev_num_blocks;
+
+TAILQ_HEAD(, spdk_bdev_io) g_bdev_io_queue;
+int g_scsi_cb_called = 0;
+
+TAILQ_HEAD(, spdk_bdev_io_wait_entry) g_io_wait_queue;
+bool g_bdev_io_pool_full = false;
+
+void *
+spdk_dma_malloc(size_t size, size_t align, uint64_t *phys_addr)
+{
+ void *buf = malloc(size);
+ if (phys_addr) {
+ *phys_addr = (uint64_t)buf;
+ }
+
+ return buf;
+}
+
+void *
+spdk_dma_zmalloc(size_t size, size_t align, uint64_t *phys_addr)
+{
+ void *buf = calloc(size, 1);
+ if (phys_addr) {
+ *phys_addr = (uint64_t)buf;
+ }
+
+ return buf;
+}
+
+void
+spdk_dma_free(void *buf)
+{
+ free(buf);
+}
+
+bool
+spdk_bdev_io_type_supported(struct spdk_bdev *bdev, enum spdk_bdev_io_type io_type)
+{
+ abort();
+ return false;
+}
+
+void
+spdk_bdev_free_io(struct spdk_bdev_io *bdev_io)
+{
+ CU_ASSERT(0);
+}
+
+const char *
+spdk_bdev_get_name(const struct spdk_bdev *bdev)
+{
+ return "test";
+}
+
+uint32_t
+spdk_bdev_get_block_size(const struct spdk_bdev *bdev)
+{
+ return 512;
+}
+
+uint64_t
+spdk_bdev_get_num_blocks(const struct spdk_bdev *bdev)
+{
+ return g_test_bdev_num_blocks;
+}
+
+const char *
+spdk_bdev_get_product_name(const struct spdk_bdev *bdev)
+{
+ return "test product";
+}
+
+bool
+spdk_bdev_has_write_cache(const struct spdk_bdev *bdev)
+{
+ return false;
+}
+
+void
+spdk_scsi_lun_complete_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task)
+{
+ g_scsi_cb_called++;
+}
+
+void
+spdk_scsi_lun_complete_mgmt_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task)
+{
+}
+
+static void
+ut_put_task(struct spdk_scsi_task *task)
+{
+ if (task->alloc_len) {
+ free(task->iov.iov_base);
+ }
+
+ task->iov.iov_base = NULL;
+ task->iov.iov_len = 0;
+ task->alloc_len = 0;
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue));
+}
+
+
+static void
+ut_init_task(struct spdk_scsi_task *task)
+{
+ memset(task, 0xFF, sizeof(*task));
+ task->iov.iov_base = NULL;
+ task->iovs = &task->iov;
+ task->iovcnt = 1;
+ task->alloc_len = 0;
+ task->dxfer_dir = SPDK_SCSI_DIR_NONE;
+}
+
+void
+spdk_bdev_io_get_scsi_status(const struct spdk_bdev_io *bdev_io,
+ int *sc, int *sk, int *asc, int *ascq)
+{
+ switch (bdev_io->internal.status) {
+ case SPDK_BDEV_IO_STATUS_SUCCESS:
+ *sc = SPDK_SCSI_STATUS_GOOD;
+ *sk = SPDK_SCSI_SENSE_NO_SENSE;
+ *asc = SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE;
+ *ascq = SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
+ break;
+ case SPDK_BDEV_IO_STATUS_SCSI_ERROR:
+ *sc = bdev_io->internal.error.scsi.sc;
+ *sk = bdev_io->internal.error.scsi.sk;
+ *asc = bdev_io->internal.error.scsi.asc;
+ *ascq = bdev_io->internal.error.scsi.ascq;
+ break;
+ default:
+ *sc = SPDK_SCSI_STATUS_CHECK_CONDITION;
+ *sk = SPDK_SCSI_SENSE_ABORTED_COMMAND;
+ *asc = SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE;
+ *ascq = SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
+ break;
+ }
+}
+
+void
+spdk_bdev_io_get_iovec(struct spdk_bdev_io *bdev_io, struct iovec **iovp, int *iovcntp)
+{
+ *iovp = NULL;
+ *iovcntp = 0;
+}
+
+static void
+ut_bdev_io_flush(void)
+{
+ struct spdk_bdev_io *bdev_io;
+ struct spdk_bdev_io_wait_entry *entry;
+
+ while (!TAILQ_EMPTY(&g_bdev_io_queue) || !TAILQ_EMPTY(&g_io_wait_queue)) {
+ while (!TAILQ_EMPTY(&g_bdev_io_queue)) {
+ bdev_io = TAILQ_FIRST(&g_bdev_io_queue);
+ TAILQ_REMOVE(&g_bdev_io_queue, bdev_io, internal.link);
+ bdev_io->internal.cb(bdev_io, true, bdev_io->internal.caller_ctx);
+ free(bdev_io);
+ }
+
+ while (!TAILQ_EMPTY(&g_io_wait_queue)) {
+ entry = TAILQ_FIRST(&g_io_wait_queue);
+ TAILQ_REMOVE(&g_io_wait_queue, entry, link);
+ entry->cb_fn(entry->cb_arg);
+ }
+ }
+}
+
+static int
+_spdk_bdev_io_op(spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ struct spdk_bdev_io *bdev_io;
+
+ if (g_bdev_io_pool_full) {
+ g_bdev_io_pool_full = false;
+ return -ENOMEM;
+ }
+
+ bdev_io = calloc(1, sizeof(*bdev_io));
+ SPDK_CU_ASSERT_FATAL(bdev_io != NULL);
+ bdev_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ bdev_io->internal.cb = cb;
+ bdev_io->internal.caller_ctx = cb_arg;
+
+ TAILQ_INSERT_TAIL(&g_bdev_io_queue, bdev_io, internal.link);
+
+ return 0;
+}
+
+int
+spdk_bdev_readv(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ struct iovec *iov, int iovcnt, uint64_t offset, uint64_t nbytes,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return _spdk_bdev_io_op(cb, cb_arg);
+}
+
+int
+spdk_bdev_writev(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ struct iovec *iov, int iovcnt,
+ uint64_t offset, uint64_t len,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return _spdk_bdev_io_op(cb, cb_arg);
+}
+
+int
+spdk_bdev_unmap_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return _spdk_bdev_io_op(cb, cb_arg);
+}
+
+int
+spdk_bdev_reset(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return _spdk_bdev_io_op(cb, cb_arg);
+}
+
+int
+spdk_bdev_flush_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch,
+ uint64_t offset_blocks, uint64_t num_blocks,
+ spdk_bdev_io_completion_cb cb, void *cb_arg)
+{
+ return _spdk_bdev_io_op(cb, cb_arg);
+}
+
+int
+spdk_bdev_queue_io_wait(struct spdk_bdev *bdev, struct spdk_io_channel *ch,
+ struct spdk_bdev_io_wait_entry *entry)
+{
+ TAILQ_INSERT_TAIL(&g_io_wait_queue, entry, link);
+ return 0;
+}
+
+/*
+ * This test specifically tests a mode select 6 command from the
+ * Windows SCSI compliance test that caused SPDK to crash.
+ */
+static void
+mode_select_6_test(void)
+{
+ struct spdk_bdev bdev;
+ struct spdk_scsi_task task;
+ struct spdk_scsi_lun lun;
+ struct spdk_scsi_dev dev;
+ char cdb[16];
+ char data[24];
+ int rc;
+
+ ut_init_task(&task);
+
+ cdb[0] = 0x15;
+ cdb[1] = 0x11;
+ cdb[2] = 0x00;
+ cdb[3] = 0x00;
+ cdb[4] = 0x18;
+ cdb[5] = 0x00;
+ task.cdb = cdb;
+
+ snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test");
+ lun.bdev = &bdev;
+ lun.dev = &dev;
+ task.lun = &lun;
+
+ memset(data, 0, sizeof(data));
+ data[4] = 0x08;
+ data[5] = 0x02;
+ spdk_scsi_task_set_data(&task, data, sizeof(data));
+
+ rc = spdk_bdev_scsi_execute(&task);
+
+ CU_ASSERT_EQUAL(rc, 0);
+
+ ut_put_task(&task);
+}
+
+/*
+ * This test specifically tests a mode select 6 command which
+ * contains no mode pages.
+ */
+static void
+mode_select_6_test2(void)
+{
+ struct spdk_bdev bdev;
+ struct spdk_scsi_task task;
+ struct spdk_scsi_lun lun;
+ struct spdk_scsi_dev dev;
+ char cdb[16];
+ int rc;
+
+ ut_init_task(&task);
+
+ cdb[0] = 0x15;
+ cdb[1] = 0x00;
+ cdb[2] = 0x00;
+ cdb[3] = 0x00;
+ cdb[4] = 0x00;
+ cdb[5] = 0x00;
+ task.cdb = cdb;
+
+ snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test");
+ lun.bdev = &bdev;
+ lun.dev = &dev;
+ task.lun = &lun;
+
+ rc = spdk_bdev_scsi_execute(&task);
+
+ CU_ASSERT_EQUAL(rc, 0);
+
+ ut_put_task(&task);
+}
+
+/*
+ * This test specifically tests a mode sense 6 command which
+ * return all subpage 00h mode pages.
+ */
+static void
+mode_sense_6_test(void)
+{
+ struct spdk_bdev bdev;
+ struct spdk_scsi_task task;
+ struct spdk_scsi_lun lun;
+ struct spdk_scsi_dev dev;
+ char cdb[12];
+ unsigned char *data;
+ int rc;
+ unsigned char mode_data_len = 0;
+ unsigned char medium_type = 0;
+ unsigned char dev_specific_param = 0;
+ unsigned char blk_descriptor_len = 0;
+
+ memset(&bdev, 0, sizeof(struct spdk_bdev));
+ ut_init_task(&task);
+ memset(cdb, 0, sizeof(cdb));
+
+ cdb[0] = 0x1A;
+ cdb[2] = 0x3F;
+ cdb[4] = 0xFF;
+ task.cdb = cdb;
+
+ snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test");
+ lun.bdev = &bdev;
+ lun.dev = &dev;
+ task.lun = &lun;
+
+ rc = spdk_bdev_scsi_execute(&task);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+
+ data = task.iovs[0].iov_base;
+ mode_data_len = data[0];
+ medium_type = data[1];
+ dev_specific_param = data[2];
+ blk_descriptor_len = data[3];
+
+ CU_ASSERT(mode_data_len >= 11);
+ CU_ASSERT_EQUAL(medium_type, 0);
+ CU_ASSERT_EQUAL(dev_specific_param, 0);
+ CU_ASSERT_EQUAL(blk_descriptor_len, 8);
+
+ ut_put_task(&task);
+}
+
+/*
+ * This test specifically tests a mode sense 10 command which
+ * return all subpage 00h mode pages.
+ */
+static void
+mode_sense_10_test(void)
+{
+ struct spdk_bdev bdev;
+ struct spdk_scsi_task task;
+ struct spdk_scsi_lun lun;
+ struct spdk_scsi_dev dev;
+ char cdb[12];
+ unsigned char *data;
+ int rc;
+ unsigned short mode_data_len = 0;
+ unsigned char medium_type = 0;
+ unsigned char dev_specific_param = 0;
+ unsigned short blk_descriptor_len = 0;
+
+ memset(&bdev, 0, sizeof(struct spdk_bdev));
+ ut_init_task(&task);
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = 0x5A;
+ cdb[2] = 0x3F;
+ cdb[8] = 0xFF;
+ task.cdb = cdb;
+
+ snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test");
+ lun.bdev = &bdev;
+ lun.dev = &dev;
+ task.lun = &lun;
+
+ rc = spdk_bdev_scsi_execute(&task);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+
+ data = task.iovs[0].iov_base;
+ mode_data_len = ((data[0] << 8) + data[1]);
+ medium_type = data[2];
+ dev_specific_param = data[3];
+ blk_descriptor_len = ((data[6] << 8) + data[7]);
+
+ CU_ASSERT(mode_data_len >= 14);
+ CU_ASSERT_EQUAL(medium_type, 0);
+ CU_ASSERT_EQUAL(dev_specific_param, 0);
+ CU_ASSERT_EQUAL(blk_descriptor_len, 8);
+
+ ut_put_task(&task);
+}
+
+/*
+ * This test specifically tests a scsi inquiry command from the
+ * Windows SCSI compliance test that failed to return the
+ * expected SCSI error sense code.
+ */
+static void
+inquiry_evpd_test(void)
+{
+ struct spdk_bdev bdev;
+ struct spdk_scsi_task task;
+ struct spdk_scsi_lun lun;
+ struct spdk_scsi_dev dev;
+ char cdb[6];
+ int rc;
+
+ ut_init_task(&task);
+
+ cdb[0] = 0x12;
+ cdb[1] = 0x00; // EVPD = 0
+ cdb[2] = 0xff; // PageCode non-zero
+ cdb[3] = 0x00;
+ cdb[4] = 0xff;
+ cdb[5] = 0x00;
+ task.cdb = cdb;
+
+ snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test");
+ lun.bdev = &bdev;
+ lun.dev = &dev;
+ task.lun = &lun;
+
+ rc = spdk_bdev_scsi_execute(&task);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+
+ CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT_EQUAL(task.sense_data[2] & 0xf, SPDK_SCSI_SENSE_ILLEGAL_REQUEST);
+ CU_ASSERT_EQUAL(task.sense_data[12], 0x24);
+ CU_ASSERT_EQUAL(task.sense_data[13], 0x0);
+
+ ut_put_task(&task);
+}
+
+/*
+ * This test is to verify specific return data for a standard scsi inquiry
+ * command: Version
+ */
+static void
+inquiry_standard_test(void)
+{
+ struct spdk_bdev bdev = { .blocklen = 512 };
+ struct spdk_scsi_task task;
+ struct spdk_scsi_lun lun;
+ struct spdk_scsi_dev dev;
+ char cdb[6];
+ char *data;
+ struct spdk_scsi_cdb_inquiry_data *inq_data;
+ int rc;
+
+ ut_init_task(&task);
+
+ cdb[0] = 0x12;
+ cdb[1] = 0x00; // EVPD = 0
+ cdb[2] = 0x00; // PageCode zero - requesting standard inquiry
+ cdb[3] = 0x00;
+ cdb[4] = 0xff; // Indicate data size used by conformance test
+ cdb[5] = 0x00;
+ task.cdb = cdb;
+
+ snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test");
+ lun.bdev = &bdev;
+ lun.dev = &dev;
+ task.lun = &lun;
+
+ rc = spdk_bdev_scsi_execute(&task);
+
+ data = task.iovs[0].iov_base;
+ inq_data = (struct spdk_scsi_cdb_inquiry_data *)&data[0];
+
+ CU_ASSERT_EQUAL(inq_data->version, SPDK_SPC_VERSION_SPC3);
+ CU_ASSERT_EQUAL(rc, 0);
+
+ ut_put_task(&task);
+}
+
+static void
+_inquiry_overflow_test(uint8_t alloc_len)
+{
+ struct spdk_bdev bdev = { .blocklen = 512 };
+ struct spdk_scsi_task task;
+ struct spdk_scsi_lun lun;
+ struct spdk_scsi_dev dev;
+ uint8_t cdb[6];
+ int rc;
+ /* expects a 4K internal data buffer */
+ char data[4096], data_compare[4096];
+
+ ut_init_task(&task);
+
+ cdb[0] = 0x12;
+ cdb[1] = 0x00; // EVPD = 0
+ cdb[2] = 0x00; // PageCode zero - requesting standard inquiry
+ cdb[3] = 0x00;
+ cdb[4] = alloc_len; // Indicate data size used by conformance test
+ cdb[5] = 0x00;
+ task.cdb = cdb;
+
+ snprintf(&dev.name[0], sizeof(dev.name), "spdk_iscsi_translation_test");
+ lun.bdev = &bdev;
+ lun.dev = &dev;
+ task.lun = &lun;
+
+ memset(data, 0, sizeof(data));
+ memset(data_compare, 0, sizeof(data_compare));
+
+ spdk_scsi_task_set_data(&task, data, sizeof(data));
+
+ rc = spdk_bdev_scsi_execute(&task);
+ SPDK_CU_ASSERT_FATAL(rc == 0);
+
+ CU_ASSERT_EQUAL(memcmp(data + alloc_len, data_compare + alloc_len, sizeof(data) - alloc_len), 0);
+ CU_ASSERT(task.data_transferred <= alloc_len);
+
+ ut_put_task(&task);
+}
+
+static void
+inquiry_overflow_test(void)
+{
+ int i;
+
+ for (i = 0; i < 256; i++) {
+ _inquiry_overflow_test(i);
+ }
+}
+
+static void
+scsi_name_padding_test(void)
+{
+ char name[SPDK_SCSI_DEV_MAX_NAME + 1];
+ char buf[SPDK_SCSI_DEV_MAX_NAME + 1];
+ int written, i;
+
+ /* case 1 */
+ memset(name, '\0', sizeof(name));
+ memset(name, 'x', 251);
+ written = spdk_bdev_scsi_pad_scsi_name(buf, name);
+
+ CU_ASSERT(written == 252);
+ CU_ASSERT(buf[250] == 'x');
+ CU_ASSERT(buf[251] == '\0');
+
+ /* case 2: */
+ memset(name, '\0', sizeof(name));
+ memset(name, 'x', 252);
+ written = spdk_bdev_scsi_pad_scsi_name(buf, name);
+
+ CU_ASSERT(written == 256);
+ CU_ASSERT(buf[251] == 'x');
+ for (i = 252; i < 256; i++) {
+ CU_ASSERT(buf[i] == '\0');
+ }
+
+ /* case 3 */
+ memset(name, '\0', sizeof(name));
+ memset(name, 'x', 255);
+ written = spdk_bdev_scsi_pad_scsi_name(buf, name);
+
+ CU_ASSERT(written == 256);
+ CU_ASSERT(buf[254] == 'x');
+ CU_ASSERT(buf[255] == '\0');
+}
+
+/*
+ * This test is to verify specific error translation from bdev to scsi.
+ */
+static void
+task_complete_test(void)
+{
+ struct spdk_scsi_task task;
+ struct spdk_bdev_io bdev_io = {};
+ struct spdk_scsi_lun lun;
+
+ ut_init_task(&task);
+
+ TAILQ_INIT(&lun.tasks);
+ TAILQ_INSERT_TAIL(&lun.tasks, &task, scsi_link);
+ task.lun = &lun;
+
+ bdev_io.internal.status = SPDK_BDEV_IO_STATUS_SUCCESS;
+ spdk_bdev_scsi_task_complete_cmd(&bdev_io, bdev_io.internal.status, &task);
+ CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_GOOD);
+ CU_ASSERT(g_scsi_cb_called == 1);
+ g_scsi_cb_called = 0;
+
+ bdev_io.internal.status = SPDK_BDEV_IO_STATUS_SCSI_ERROR;
+ bdev_io.internal.error.scsi.sc = SPDK_SCSI_STATUS_CHECK_CONDITION;
+ bdev_io.internal.error.scsi.sk = SPDK_SCSI_SENSE_HARDWARE_ERROR;
+ bdev_io.internal.error.scsi.asc = SPDK_SCSI_ASC_WARNING;
+ bdev_io.internal.error.scsi.ascq = SPDK_SCSI_ASCQ_POWER_LOSS_EXPECTED;
+ spdk_bdev_scsi_task_complete_cmd(&bdev_io, bdev_io.internal.status, &task);
+ CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT_EQUAL(task.sense_data[2] & 0xf, SPDK_SCSI_SENSE_HARDWARE_ERROR);
+ CU_ASSERT_EQUAL(task.sense_data[12], SPDK_SCSI_ASC_WARNING);
+ CU_ASSERT_EQUAL(task.sense_data[13], SPDK_SCSI_ASCQ_POWER_LOSS_EXPECTED);
+ CU_ASSERT(g_scsi_cb_called == 1);
+ g_scsi_cb_called = 0;
+
+ bdev_io.internal.status = SPDK_BDEV_IO_STATUS_FAILED;
+ spdk_bdev_scsi_task_complete_cmd(&bdev_io, bdev_io.internal.status, &task);
+ CU_ASSERT_EQUAL(task.status, SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT_EQUAL(task.sense_data[2] & 0xf, SPDK_SCSI_SENSE_ABORTED_COMMAND);
+ CU_ASSERT_EQUAL(task.sense_data[12], SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE);
+ CU_ASSERT_EQUAL(task.sense_data[13], SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE);
+ CU_ASSERT(g_scsi_cb_called == 1);
+ g_scsi_cb_called = 0;
+
+ ut_put_task(&task);
+}
+
+static void
+lba_range_test(void)
+{
+ struct spdk_bdev bdev;
+ struct spdk_scsi_lun lun;
+ struct spdk_scsi_task task;
+ uint8_t cdb[16];
+ int rc;
+
+ lun.bdev = &bdev;
+
+ ut_init_task(&task);
+ task.lun = &lun;
+ task.lun->bdev_desc = NULL;
+ task.lun->io_channel = NULL;
+ task.cdb = cdb;
+
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = 0x88; /* READ (16) */
+
+ /* Test block device size of 4 blocks */
+ g_test_bdev_num_blocks = 4;
+
+ /* LBA = 0, length = 1 (in range) */
+ to_be64(&cdb[2], 0); /* LBA */
+ to_be32(&cdb[10], 1); /* transfer length */
+ task.transfer_len = 1 * 512;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING);
+ CU_ASSERT(task.status == 0xFF);
+ SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_bdev_io_queue));
+ ut_bdev_io_flush();
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD);
+ CU_ASSERT(g_scsi_cb_called == 1);
+ g_scsi_cb_called = 0;
+
+ /* LBA = 4, length = 1 (LBA out of range) */
+ to_be64(&cdb[2], 4); /* LBA */
+ to_be32(&cdb[10], 1); /* transfer length */
+ task.transfer_len = 1 * 512;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_COMPLETE);
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT(task.sense_data[12] == SPDK_SCSI_ASC_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE);
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue));
+
+ /* LBA = 0, length = 4 (in range, max valid size) */
+ to_be64(&cdb[2], 0); /* LBA */
+ to_be32(&cdb[10], 4); /* transfer length */
+ task.transfer_len = 4 * 512;
+ task.status = 0xFF;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING);
+ CU_ASSERT(task.status == 0xFF);
+ SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_bdev_io_queue));
+ ut_bdev_io_flush();
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD);
+ CU_ASSERT(g_scsi_cb_called == 1);
+ g_scsi_cb_called = 0;
+
+ /* LBA = 0, length = 5 (LBA in range, length beyond end of bdev) */
+ to_be64(&cdb[2], 0); /* LBA */
+ to_be32(&cdb[10], 5); /* transfer length */
+ task.transfer_len = 5 * 512;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_COMPLETE);
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT(task.sense_data[12] == SPDK_SCSI_ASC_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE);
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue));
+
+ ut_put_task(&task);
+}
+
+static void
+xfer_len_test(void)
+{
+ struct spdk_bdev bdev;
+ struct spdk_scsi_lun lun;
+ struct spdk_scsi_task task;
+ uint8_t cdb[16];
+ int rc;
+
+ lun.bdev = &bdev;
+
+ ut_init_task(&task);
+ task.lun = &lun;
+ task.lun->bdev_desc = NULL;
+ task.lun->io_channel = NULL;
+ task.cdb = cdb;
+
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = 0x88; /* READ (16) */
+
+ /* Test block device size of 512 MiB */
+ g_test_bdev_num_blocks = 512 * 1024 * 1024;
+
+ /* 1 block */
+ to_be64(&cdb[2], 0); /* LBA */
+ to_be32(&cdb[10], 1); /* transfer length */
+ task.transfer_len = 1 * 512;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING);
+ CU_ASSERT(task.status == 0xFF);
+ SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_bdev_io_queue));
+ ut_bdev_io_flush();
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD);
+ CU_ASSERT(g_scsi_cb_called == 1);
+ g_scsi_cb_called = 0;
+
+ /* max transfer length (as reported in block limits VPD page) */
+ to_be64(&cdb[2], 0); /* LBA */
+ to_be32(&cdb[10], SPDK_WORK_BLOCK_SIZE / 512); /* transfer length */
+ task.transfer_len = SPDK_WORK_BLOCK_SIZE;
+ task.status = 0xFF;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING);
+ CU_ASSERT(task.status == 0xFF);
+ SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&g_bdev_io_queue));
+ ut_bdev_io_flush();
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD);
+ CU_ASSERT(g_scsi_cb_called == 1);
+ g_scsi_cb_called = 0;
+
+ /* max transfer length plus one block (invalid) */
+ to_be64(&cdb[2], 0); /* LBA */
+ to_be32(&cdb[10], SPDK_WORK_BLOCK_SIZE / 512 + 1); /* transfer length */
+ task.transfer_len = SPDK_WORK_BLOCK_SIZE + 512;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_COMPLETE);
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT((task.sense_data[2] & 0xf) == SPDK_SCSI_SENSE_ILLEGAL_REQUEST);
+ CU_ASSERT(task.sense_data[12] == SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB);
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue));
+
+ /* zero transfer length (valid) */
+ to_be64(&cdb[2], 0); /* LBA */
+ to_be32(&cdb[10], 0); /* transfer length */
+ task.transfer_len = 0;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_COMPLETE);
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD);
+ CU_ASSERT(task.data_transferred == 0);
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue));
+
+ /* zero transfer length past end of disk (invalid) */
+ to_be64(&cdb[2], g_test_bdev_num_blocks); /* LBA */
+ to_be32(&cdb[10], 0); /* transfer length */
+ task.transfer_len = 0;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_COMPLETE);
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_CHECK_CONDITION);
+ CU_ASSERT(task.sense_data[12] == SPDK_SCSI_ASC_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE);
+ SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_io_queue));
+
+ ut_put_task(&task);
+}
+
+static void
+_xfer_test(bool bdev_io_pool_full)
+{
+ struct spdk_bdev bdev;
+ struct spdk_scsi_lun lun;
+ struct spdk_scsi_task task;
+ uint8_t cdb[16];
+ char data[4096];
+ int rc;
+
+ lun.bdev = &bdev;
+
+ /* Test block device size of 512 MiB */
+ g_test_bdev_num_blocks = 512 * 1024 * 1024;
+
+ /* Read 1 block */
+ ut_init_task(&task);
+ task.lun = &lun;
+ task.lun->bdev_desc = NULL;
+ task.lun->io_channel = NULL;
+ task.cdb = cdb;
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = 0x88; /* READ (16) */
+ to_be64(&cdb[2], 0); /* LBA */
+ to_be32(&cdb[10], 1); /* transfer length */
+ task.transfer_len = 1 * 512;
+ g_bdev_io_pool_full = bdev_io_pool_full;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING);
+ CU_ASSERT(task.status == 0xFF);
+
+ ut_bdev_io_flush();
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD);
+ CU_ASSERT(g_scsi_cb_called == 1);
+ g_scsi_cb_called = 0;
+ ut_put_task(&task);
+
+ /* Write 1 block */
+ ut_init_task(&task);
+ task.lun = &lun;
+ task.cdb = cdb;
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = 0x8a; /* WRITE (16) */
+ to_be64(&cdb[2], 0); /* LBA */
+ to_be32(&cdb[10], 1); /* transfer length */
+ task.transfer_len = 1 * 512;
+ g_bdev_io_pool_full = bdev_io_pool_full;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING);
+ CU_ASSERT(task.status == 0xFF);
+
+ ut_bdev_io_flush();
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD);
+ CU_ASSERT(g_scsi_cb_called == 1);
+ g_scsi_cb_called = 0;
+ ut_put_task(&task);
+
+ /* Unmap 5 blocks using 2 descriptors */
+ ut_init_task(&task);
+ task.lun = &lun;
+ task.cdb = cdb;
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = 0x42; /* UNMAP */
+ to_be16(&data[7], 2); /* 2 parameters in list */
+ memset(data, 0, sizeof(data));
+ to_be16(&data[2], 32); /* 2 descriptors */
+ to_be64(&data[8], 1); /* LBA 1 */
+ to_be32(&data[16], 2); /* 2 blocks */
+ to_be64(&data[24], 10); /* LBA 10 */
+ to_be32(&data[32], 3); /* 3 blocks */
+ spdk_scsi_task_set_data(&task, data, sizeof(data));
+ task.status = SPDK_SCSI_STATUS_GOOD;
+ g_bdev_io_pool_full = bdev_io_pool_full;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING);
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD);
+
+ ut_bdev_io_flush();
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD);
+ CU_ASSERT(g_scsi_cb_called == 1);
+ g_scsi_cb_called = 0;
+ ut_put_task(&task);
+
+ /* Flush 1 block */
+ ut_init_task(&task);
+ task.lun = &lun;
+ task.cdb = cdb;
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = 0x91; /* SYNCHRONIZE CACHE (16) */
+ to_be64(&cdb[2], 0); /* LBA */
+ to_be32(&cdb[10], 1); /* 1 blocks */
+ g_bdev_io_pool_full = bdev_io_pool_full;
+ rc = spdk_bdev_scsi_execute(&task);
+ CU_ASSERT(rc == SPDK_SCSI_TASK_PENDING);
+ CU_ASSERT(task.status == 0xFF);
+
+ ut_bdev_io_flush();
+ CU_ASSERT(task.status == SPDK_SCSI_STATUS_GOOD);
+ CU_ASSERT(g_scsi_cb_called == 1);
+ g_scsi_cb_called = 0;
+ ut_put_task(&task);
+}
+
+static void
+xfer_test(void)
+{
+ _xfer_test(false);
+ _xfer_test(true);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ TAILQ_INIT(&g_bdev_io_queue);
+ TAILQ_INIT(&g_io_wait_queue);
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("translation_suite", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "mode select 6 test", mode_select_6_test) == NULL
+ || CU_add_test(suite, "mode select 6 test2", mode_select_6_test2) == NULL
+ || CU_add_test(suite, "mode sense 6 test", mode_sense_6_test) == NULL
+ || CU_add_test(suite, "mode sense 10 test", mode_sense_10_test) == NULL
+ || CU_add_test(suite, "inquiry evpd test", inquiry_evpd_test) == NULL
+ || CU_add_test(suite, "inquiry standard test", inquiry_standard_test) == NULL
+ || CU_add_test(suite, "inquiry overflow test", inquiry_overflow_test) == NULL
+ || CU_add_test(suite, "task complete test", task_complete_test) == NULL
+ || CU_add_test(suite, "LBA range test", lba_range_test) == NULL
+ || CU_add_test(suite, "transfer length test", xfer_len_test) == NULL
+ || CU_add_test(suite, "transfer test", xfer_test) == NULL
+ || CU_add_test(suite, "scsi name padding test", scsi_name_padding_test) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/sock/Makefile b/src/spdk/test/unit/lib/sock/Makefile
new file mode 100644
index 00000000..5e16429d
--- /dev/null
+++ b/src/spdk/test/unit/lib/sock/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 = sock.c
+
+.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/unit/lib/sock/sock.c/.gitignore b/src/spdk/test/unit/lib/sock/sock.c/.gitignore
new file mode 100644
index 00000000..bd9bf833
--- /dev/null
+++ b/src/spdk/test/unit/lib/sock/sock.c/.gitignore
@@ -0,0 +1 @@
+sock_ut
diff --git a/src/spdk/test/unit/lib/sock/sock.c/Makefile b/src/spdk/test/unit/lib/sock/sock.c/Makefile
new file mode 100644
index 00000000..845c9ade
--- /dev/null
+++ b/src/spdk/test/unit/lib/sock/sock.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = sock_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/sock/sock.c/sock_ut.c b/src/spdk/test/unit/lib/sock/sock.c/sock_ut.c
new file mode 100644
index 00000000..a0176f11
--- /dev/null
+++ b/src/spdk/test/unit/lib/sock/sock.c/sock_ut.c
@@ -0,0 +1,643 @@
+/*-
+ * 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/util.h"
+
+#include "spdk_cunit.h"
+
+#include "sock/sock.c"
+#include "sock/posix/posix.c"
+
+#define UT_IP "test_ip"
+#define UT_PORT 1234
+
+bool g_read_data_called;
+ssize_t g_bytes_read;
+char g_buf[256];
+struct spdk_sock *g_server_sock_read;
+int g_ut_accept_count;
+struct spdk_ut_sock *g_ut_listen_sock;
+struct spdk_ut_sock *g_ut_client_sock;
+
+struct spdk_ut_sock {
+ struct spdk_sock base;
+ struct spdk_ut_sock *peer;
+ size_t bytes_avail;
+ char buf[256];
+};
+
+struct spdk_ut_sock_group_impl {
+ struct spdk_sock_group_impl base;
+ struct spdk_ut_sock *sock;
+};
+
+#define __ut_sock(sock) (struct spdk_ut_sock *)sock
+#define __ut_group(group) (struct spdk_ut_sock_group_impl *)group
+
+static int
+spdk_ut_sock_getaddr(struct spdk_sock *_sock, char *saddr, int slen, uint16_t *sport,
+ char *caddr, int clen, uint16_t *cport)
+{
+ return 0;
+}
+
+static struct spdk_sock *
+spdk_ut_sock_listen(const char *ip, int port)
+{
+ struct spdk_ut_sock *sock;
+
+ if (strcmp(ip, UT_IP) || port != UT_PORT) {
+ return NULL;
+ }
+
+ CU_ASSERT(g_ut_listen_sock == NULL);
+
+ sock = calloc(1, sizeof(*sock));
+ SPDK_CU_ASSERT_FATAL(sock != NULL);
+ g_ut_listen_sock = sock;
+
+ return &sock->base;
+}
+
+static struct spdk_sock *
+spdk_ut_sock_connect(const char *ip, int port)
+{
+ struct spdk_ut_sock *sock;
+
+ if (strcmp(ip, UT_IP) || port != UT_PORT) {
+ return NULL;
+ }
+
+ sock = calloc(1, sizeof(*sock));
+ SPDK_CU_ASSERT_FATAL(sock != NULL);
+ g_ut_accept_count++;
+ CU_ASSERT(g_ut_client_sock == NULL);
+ g_ut_client_sock = sock;
+
+ return &sock->base;
+}
+
+static struct spdk_sock *
+spdk_ut_sock_accept(struct spdk_sock *_sock)
+{
+ struct spdk_ut_sock *sock = __ut_sock(_sock);
+ struct spdk_ut_sock *new_sock;
+
+ CU_ASSERT(sock == g_ut_listen_sock);
+
+ if (g_ut_accept_count == 0) {
+ errno = EAGAIN;
+ return NULL;
+ }
+
+ g_ut_accept_count--;
+ new_sock = calloc(1, sizeof(*sock));
+ if (new_sock == NULL) {
+ SPDK_ERRLOG("sock allocation failed\n");
+ return NULL;
+ }
+
+ SPDK_CU_ASSERT_FATAL(g_ut_client_sock != NULL);
+ g_ut_client_sock->peer = new_sock;
+
+ return &new_sock->base;
+}
+
+static int
+spdk_ut_sock_close(struct spdk_sock *_sock)
+{
+ struct spdk_ut_sock *sock = __ut_sock(_sock);
+
+ if (sock == g_ut_listen_sock) {
+ g_ut_listen_sock = NULL;
+ }
+ if (sock == g_ut_client_sock) {
+ g_ut_client_sock = NULL;
+ }
+ free(_sock);
+
+ return 0;
+}
+
+static ssize_t
+spdk_ut_sock_recv(struct spdk_sock *_sock, void *buf, size_t len)
+{
+ struct spdk_ut_sock *sock = __ut_sock(_sock);
+ char tmp[256];
+
+ len = spdk_min(len, sock->bytes_avail);
+
+ if (len == 0) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ memcpy(buf, sock->buf, len);
+ memcpy(tmp, &sock->buf[len], sock->bytes_avail - len);
+ memcpy(sock->buf, tmp, sock->bytes_avail - len);
+ sock->bytes_avail -= len;
+
+ return len;
+}
+
+static ssize_t
+spdk_ut_sock_writev(struct spdk_sock *_sock, struct iovec *iov, int iovcnt)
+{
+ struct spdk_ut_sock *sock = __ut_sock(_sock);
+ struct spdk_ut_sock *peer;
+
+ SPDK_CU_ASSERT_FATAL(sock->peer != NULL);
+ peer = sock->peer;
+
+ /* Test implementation only supports single iov for now. */
+ CU_ASSERT(iovcnt == 1);
+
+ memcpy(&peer->buf[peer->bytes_avail], iov[0].iov_base, iov[0].iov_len);
+ peer->bytes_avail += iov[0].iov_len;
+
+ return iov[0].iov_len;
+}
+
+static int
+spdk_ut_sock_set_recvlowat(struct spdk_sock *_sock, int nbytes)
+{
+ return 0;
+}
+
+static int
+spdk_ut_sock_set_recvbuf(struct spdk_sock *_sock, int sz)
+{
+ return 0;
+}
+
+static int
+spdk_ut_sock_set_sendbuf(struct spdk_sock *_sock, int sz)
+{
+ return 0;
+}
+
+static bool
+spdk_ut_sock_is_ipv6(struct spdk_sock *_sock)
+{
+ return false;
+}
+
+static bool
+spdk_ut_sock_is_ipv4(struct spdk_sock *_sock)
+{
+ return true;
+}
+
+static struct spdk_sock_group_impl *
+spdk_ut_sock_group_impl_create(void)
+{
+ struct spdk_ut_sock_group_impl *group_impl;
+
+ group_impl = calloc(1, sizeof(*group_impl));
+ SPDK_CU_ASSERT_FATAL(group_impl != NULL);
+
+ return &group_impl->base;
+}
+
+static int
+spdk_ut_sock_group_impl_add_sock(struct spdk_sock_group_impl *_group, struct spdk_sock *_sock)
+{
+ struct spdk_ut_sock_group_impl *group = __ut_group(_group);
+ struct spdk_ut_sock *sock = __ut_sock(_sock);
+
+ group->sock = sock;
+
+ return 0;
+}
+
+static int
+spdk_ut_sock_group_impl_remove_sock(struct spdk_sock_group_impl *_group, struct spdk_sock *_sock)
+{
+ struct spdk_ut_sock_group_impl *group = __ut_group(_group);
+ struct spdk_ut_sock *sock = __ut_sock(_sock);
+
+ CU_ASSERT(group->sock == sock);
+ group->sock = NULL;
+
+ return 0;
+}
+
+static int
+spdk_ut_sock_group_impl_poll(struct spdk_sock_group_impl *_group, int max_events,
+ struct spdk_sock **socks)
+{
+ struct spdk_ut_sock_group_impl *group = __ut_group(_group);
+
+ if (group->sock != NULL && group->sock->bytes_avail > 0) {
+ socks[0] = &group->sock->base;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+spdk_ut_sock_group_impl_close(struct spdk_sock_group_impl *_group)
+{
+ struct spdk_ut_sock_group_impl *group = __ut_group(_group);
+
+ CU_ASSERT(group->sock == NULL);
+
+ return 0;
+}
+
+static struct spdk_net_impl g_ut_net_impl = {
+ .name = "ut",
+ .getaddr = spdk_ut_sock_getaddr,
+ .connect = spdk_ut_sock_connect,
+ .listen = spdk_ut_sock_listen,
+ .accept = spdk_ut_sock_accept,
+ .close = spdk_ut_sock_close,
+ .recv = spdk_ut_sock_recv,
+ .writev = spdk_ut_sock_writev,
+ .set_recvlowat = spdk_ut_sock_set_recvlowat,
+ .set_recvbuf = spdk_ut_sock_set_recvbuf,
+ .set_sendbuf = spdk_ut_sock_set_sendbuf,
+ .is_ipv6 = spdk_ut_sock_is_ipv6,
+ .is_ipv4 = spdk_ut_sock_is_ipv4,
+ .group_impl_create = spdk_ut_sock_group_impl_create,
+ .group_impl_add_sock = spdk_ut_sock_group_impl_add_sock,
+ .group_impl_remove_sock = spdk_ut_sock_group_impl_remove_sock,
+ .group_impl_poll = spdk_ut_sock_group_impl_poll,
+ .group_impl_close = spdk_ut_sock_group_impl_close,
+};
+
+SPDK_NET_IMPL_REGISTER(ut, &g_ut_net_impl);
+
+static void
+_sock(const char *ip, int port)
+{
+ struct spdk_sock *listen_sock;
+ struct spdk_sock *server_sock;
+ struct spdk_sock *client_sock;
+ char *test_string = "abcdef";
+ char buffer[64];
+ ssize_t bytes_read, bytes_written;
+ struct iovec iov;
+ int rc;
+
+ listen_sock = spdk_sock_listen(ip, port);
+ SPDK_CU_ASSERT_FATAL(listen_sock != NULL);
+
+ server_sock = spdk_sock_accept(listen_sock);
+ CU_ASSERT(server_sock == NULL);
+ CU_ASSERT(errno == EAGAIN || errno == EWOULDBLOCK);
+
+ client_sock = spdk_sock_connect(ip, port);
+ SPDK_CU_ASSERT_FATAL(client_sock != NULL);
+
+ /*
+ * Delay a bit here before checking if server socket is
+ * ready.
+ */
+ usleep(1000);
+
+ server_sock = spdk_sock_accept(listen_sock);
+ SPDK_CU_ASSERT_FATAL(server_sock != NULL);
+
+ iov.iov_base = test_string;
+ iov.iov_len = 7;
+ bytes_written = spdk_sock_writev(client_sock, &iov, 1);
+ CU_ASSERT(bytes_written == 7);
+
+ usleep(1000);
+
+ bytes_read = spdk_sock_recv(server_sock, buffer, 2);
+ CU_ASSERT(bytes_read == 2);
+
+ usleep(1000);
+
+ bytes_read += spdk_sock_recv(server_sock, buffer + 2, 5);
+ CU_ASSERT(bytes_read == 7);
+
+ CU_ASSERT(strncmp(test_string, buffer, 7) == 0);
+
+ rc = spdk_sock_close(&client_sock);
+ CU_ASSERT(client_sock == NULL);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_sock_close(&server_sock);
+ CU_ASSERT(server_sock == NULL);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_sock_close(&listen_sock);
+ CU_ASSERT(listen_sock == NULL);
+ CU_ASSERT(rc == 0);
+}
+
+static void
+posix_sock(void)
+{
+ _sock("127.0.0.1", 3260);
+}
+
+static void
+ut_sock(void)
+{
+ _sock(UT_IP, UT_PORT);
+}
+
+static void
+read_data(void *cb_arg, struct spdk_sock_group *group, struct spdk_sock *sock)
+{
+ struct spdk_sock *server_sock = cb_arg;
+
+ CU_ASSERT(server_sock == sock);
+
+ g_read_data_called = true;
+ g_bytes_read += spdk_sock_recv(server_sock, g_buf + g_bytes_read, sizeof(g_buf) - g_bytes_read);
+}
+
+static void
+_sock_group(const char *ip, int port)
+{
+ struct spdk_sock_group *group;
+ struct spdk_sock *listen_sock;
+ struct spdk_sock *server_sock;
+ struct spdk_sock *client_sock;
+ char *test_string = "abcdef";
+ ssize_t bytes_written;
+ struct iovec iov;
+ int rc;
+
+ listen_sock = spdk_sock_listen(ip, port);
+ SPDK_CU_ASSERT_FATAL(listen_sock != NULL);
+
+ server_sock = spdk_sock_accept(listen_sock);
+ CU_ASSERT(server_sock == NULL);
+ CU_ASSERT(errno == EAGAIN || errno == EWOULDBLOCK);
+
+ client_sock = spdk_sock_connect(ip, port);
+ SPDK_CU_ASSERT_FATAL(client_sock != NULL);
+
+ usleep(1000);
+
+ server_sock = spdk_sock_accept(listen_sock);
+ SPDK_CU_ASSERT_FATAL(server_sock != NULL);
+
+ group = spdk_sock_group_create();
+ SPDK_CU_ASSERT_FATAL(group != NULL);
+
+ /* pass null cb_fn */
+ rc = spdk_sock_group_add_sock(group, server_sock, NULL, NULL);
+ CU_ASSERT(rc == -1);
+ CU_ASSERT(errno == EINVAL);
+
+ rc = spdk_sock_group_add_sock(group, server_sock, read_data, server_sock);
+ CU_ASSERT(rc == 0);
+
+ /* try adding sock a second time */
+ rc = spdk_sock_group_add_sock(group, server_sock, read_data, server_sock);
+ CU_ASSERT(rc == -1);
+ CU_ASSERT(errno == EBUSY);
+
+ g_read_data_called = false;
+ g_bytes_read = 0;
+ rc = spdk_sock_group_poll(group);
+
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_read_data_called == false);
+
+ iov.iov_base = test_string;
+ iov.iov_len = 7;
+ bytes_written = spdk_sock_writev(client_sock, &iov, 1);
+ CU_ASSERT(bytes_written == 7);
+
+ usleep(1000);
+
+ g_read_data_called = false;
+ g_bytes_read = 0;
+ rc = spdk_sock_group_poll(group);
+
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_read_data_called == true);
+ CU_ASSERT(g_bytes_read == 7);
+
+ CU_ASSERT(strncmp(test_string, g_buf, 7) == 0);
+
+ rc = spdk_sock_close(&client_sock);
+ CU_ASSERT(client_sock == NULL);
+ CU_ASSERT(rc == 0);
+
+ /* Try to close sock_group while it still has sockets. */
+ rc = spdk_sock_group_close(&group);
+ CU_ASSERT(rc == -1);
+ CU_ASSERT(errno == EBUSY);
+
+ /* Try to close sock while it is still part of a sock_group. */
+ rc = spdk_sock_close(&server_sock);
+ CU_ASSERT(rc == -1);
+ CU_ASSERT(errno == EBUSY);
+
+ rc = spdk_sock_group_remove_sock(group, server_sock);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_sock_group_close(&group);
+ CU_ASSERT(group == NULL);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_sock_close(&server_sock);
+ CU_ASSERT(server_sock == NULL);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_sock_close(&listen_sock);
+ CU_ASSERT(listen_sock == NULL);
+ CU_ASSERT(rc == 0);
+}
+
+static void
+posix_sock_group(void)
+{
+ _sock_group("127.0.0.1", 3260);
+}
+
+static void
+ut_sock_group(void)
+{
+ _sock_group(UT_IP, UT_PORT);
+}
+
+static void
+read_data_fairness(void *cb_arg, struct spdk_sock_group *group, struct spdk_sock *sock)
+{
+ struct spdk_sock *server_sock = cb_arg;
+ ssize_t bytes_read;
+ char buf[1];
+
+ CU_ASSERT(g_server_sock_read == NULL);
+ CU_ASSERT(server_sock == sock);
+
+ g_server_sock_read = server_sock;
+ bytes_read = spdk_sock_recv(server_sock, buf, 1);
+ CU_ASSERT(bytes_read == 1);
+}
+
+static void
+posix_sock_group_fairness(void)
+{
+ struct spdk_sock_group *group;
+ struct spdk_sock *listen_sock;
+ struct spdk_sock *server_sock[3];
+ struct spdk_sock *client_sock[3];
+ char test_char = 'a';
+ ssize_t bytes_written;
+ struct iovec iov;
+ int i, rc;
+
+ listen_sock = spdk_sock_listen("127.0.0.1", 3260);
+ SPDK_CU_ASSERT_FATAL(listen_sock != NULL);
+
+ group = spdk_sock_group_create();
+ SPDK_CU_ASSERT_FATAL(group != NULL);
+
+ for (i = 0; i < 3; i++) {
+ client_sock[i] = spdk_sock_connect("127.0.0.1", 3260);
+ SPDK_CU_ASSERT_FATAL(client_sock[i] != NULL);
+
+ usleep(1000);
+
+ server_sock[i] = spdk_sock_accept(listen_sock);
+ SPDK_CU_ASSERT_FATAL(server_sock[i] != NULL);
+
+ rc = spdk_sock_group_add_sock(group, server_sock[i],
+ read_data_fairness, server_sock[i]);
+ CU_ASSERT(rc == 0);
+ }
+
+ iov.iov_base = &test_char;
+ iov.iov_len = 1;
+
+ for (i = 0; i < 3; i++) {
+ bytes_written = spdk_sock_writev(client_sock[i], &iov, 1);
+ CU_ASSERT(bytes_written == 1);
+ }
+
+ usleep(1000);
+
+ /*
+ * Poll for just one event - this should be server sock 0, since that
+ * is the peer of the first client sock that we wrote to.
+ */
+ g_server_sock_read = NULL;
+ rc = spdk_sock_group_poll_count(group, 1);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_server_sock_read == server_sock[0]);
+
+ /*
+ * Now write another byte to client sock 0. We want to ensure that
+ * the sock group does not unfairly process the event for this sock
+ * before the socks that were written to earlier.
+ */
+ bytes_written = spdk_sock_writev(client_sock[0], &iov, 1);
+ CU_ASSERT(bytes_written == 1);
+
+ usleep(1000);
+
+ g_server_sock_read = NULL;
+ rc = spdk_sock_group_poll_count(group, 1);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_server_sock_read == server_sock[1]);
+
+ g_server_sock_read = NULL;
+ rc = spdk_sock_group_poll_count(group, 1);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_server_sock_read == server_sock[2]);
+
+ g_server_sock_read = NULL;
+ rc = spdk_sock_group_poll_count(group, 1);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(g_server_sock_read == server_sock[0]);
+
+ for (i = 0; i < 3; i++) {
+ rc = spdk_sock_group_remove_sock(group, server_sock[i]);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_sock_close(&client_sock[i]);
+ CU_ASSERT(client_sock[i] == NULL);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_sock_close(&server_sock[i]);
+ CU_ASSERT(server_sock[i] == NULL);
+ CU_ASSERT(rc == 0);
+ }
+
+ rc = spdk_sock_group_close(&group);
+ CU_ASSERT(group == NULL);
+ CU_ASSERT(rc == 0);
+
+ rc = spdk_sock_close(&listen_sock);
+ CU_ASSERT(listen_sock == NULL);
+ CU_ASSERT(rc == 0);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("sock", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "posix_sock", posix_sock) == NULL ||
+ CU_add_test(suite, "ut_sock", ut_sock) == NULL ||
+ CU_add_test(suite, "posix_sock_group", posix_sock_group) == NULL ||
+ CU_add_test(suite, "ut_sock_group", ut_sock_group) == NULL ||
+ CU_add_test(suite, "posix_sock_group_fairness", posix_sock_group_fairness) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/thread/Makefile b/src/spdk/test/unit/lib/thread/Makefile
new file mode 100644
index 00000000..d7381694
--- /dev/null
+++ b/src/spdk/test/unit/lib/thread/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 = thread.c
+
+.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/unit/lib/thread/thread.c/.gitignore b/src/spdk/test/unit/lib/thread/thread.c/.gitignore
new file mode 100644
index 00000000..1a165acb
--- /dev/null
+++ b/src/spdk/test/unit/lib/thread/thread.c/.gitignore
@@ -0,0 +1 @@
+thread_ut
diff --git a/src/spdk/test/unit/lib/thread/thread.c/Makefile b/src/spdk/test/unit/lib/thread/thread.c/Makefile
new file mode 100644
index 00000000..23cfa45a
--- /dev/null
+++ b/src/spdk/test/unit/lib/thread/thread.c/Makefile
@@ -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.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.mock.unittest.mk
+
+TEST_FILE = thread_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/thread/thread.c/thread_ut.c b/src/spdk/test/unit/lib/thread/thread.c/thread_ut.c
new file mode 100644
index 00000000..464e430f
--- /dev/null
+++ b/src/spdk/test/unit/lib/thread/thread.c/thread_ut.c
@@ -0,0 +1,501 @@
+/*-
+ * 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_cunit.h"
+
+#include "thread/thread.c"
+#include "common/lib/test_env.c"
+#include "common/lib/ut_multithread.c"
+
+static void
+_send_msg(spdk_thread_fn fn, void *ctx, void *thread_ctx)
+{
+ fn(ctx);
+}
+
+static void
+thread_alloc(void)
+{
+ CU_ASSERT(TAILQ_EMPTY(&g_threads));
+ allocate_threads(1);
+ CU_ASSERT(!TAILQ_EMPTY(&g_threads));
+ free_threads();
+ CU_ASSERT(TAILQ_EMPTY(&g_threads));
+}
+
+static void
+send_msg_cb(void *ctx)
+{
+ bool *done = ctx;
+
+ *done = true;
+}
+
+static void
+thread_send_msg(void)
+{
+ struct spdk_thread *thread0;
+ bool done = false;
+
+ allocate_threads(2);
+ set_thread(0);
+ thread0 = spdk_get_thread();
+
+ set_thread(1);
+ /* Simulate thread 1 sending a message to thread 0. */
+ spdk_thread_send_msg(thread0, send_msg_cb, &done);
+
+ /* We have not polled thread 0 yet, so done should be false. */
+ CU_ASSERT(!done);
+
+ /*
+ * Poll thread 1. The message was sent to thread 0, so this should be
+ * a nop and done should still be false.
+ */
+ poll_thread(1);
+ CU_ASSERT(!done);
+
+ /*
+ * Poll thread 0. This should execute the message and done should then
+ * be true.
+ */
+ poll_thread(0);
+ CU_ASSERT(done);
+
+ free_threads();
+}
+
+static int
+poller_run_done(void *ctx)
+{
+ bool *poller_run = ctx;
+
+ *poller_run = true;
+
+ return -1;
+}
+
+static void
+thread_poller(void)
+{
+ struct spdk_poller *poller = NULL;
+ bool poller_run = false;
+
+ allocate_threads(1);
+
+ set_thread(0);
+ reset_time();
+ /* Register a poller with no-wait time and test execution */
+ poller = spdk_poller_register(poller_run_done, &poller_run, 0);
+ CU_ASSERT(poller != NULL);
+
+ poll_threads();
+ CU_ASSERT(poller_run == true);
+
+ spdk_poller_unregister(&poller);
+ CU_ASSERT(poller == NULL);
+
+ /* Register a poller with 1000us wait time and test single execution */
+ poller_run = false;
+ poller = spdk_poller_register(poller_run_done, &poller_run, 1000);
+ CU_ASSERT(poller != NULL);
+
+ poll_threads();
+ CU_ASSERT(poller_run == false);
+
+ increment_time(1000);
+ poll_threads();
+ CU_ASSERT(poller_run == true);
+
+ reset_time();
+ poller_run = false;
+ poll_threads();
+ CU_ASSERT(poller_run == false);
+
+ increment_time(1000);
+ poll_threads();
+ CU_ASSERT(poller_run == true);
+
+ spdk_poller_unregister(&poller);
+ CU_ASSERT(poller == NULL);
+
+ free_threads();
+}
+
+static void
+for_each_cb(void *ctx)
+{
+ int *count = ctx;
+
+ (*count)++;
+}
+
+static void
+thread_for_each(void)
+{
+ int count = 0;
+ int i;
+
+ allocate_threads(3);
+ set_thread(0);
+
+ spdk_for_each_thread(for_each_cb, &count, for_each_cb);
+
+ /* We have not polled thread 0 yet, so count should be 0 */
+ CU_ASSERT(count == 0);
+
+ /* Poll each thread to verify the message is passed to each */
+ for (i = 0; i < 3; i++) {
+ poll_thread(i);
+ CU_ASSERT(count == (i + 1));
+ }
+
+ /*
+ * After each thread is called, the completion calls it
+ * one more time.
+ */
+ poll_thread(0);
+ CU_ASSERT(count == 4);
+
+ free_threads();
+}
+
+static int
+channel_create(void *io_device, void *ctx_buf)
+{
+ return 0;
+}
+
+static void
+channel_destroy(void *io_device, void *ctx_buf)
+{
+}
+
+static void
+channel_msg(struct spdk_io_channel_iter *i)
+{
+ struct spdk_io_channel *ch = spdk_io_channel_iter_get_channel(i);
+ int *count = spdk_io_channel_get_ctx(ch);
+
+ (*count)++;
+
+ spdk_for_each_channel_continue(i, 0);
+}
+
+static void
+channel_cpl(struct spdk_io_channel_iter *i, int status)
+{
+}
+
+static void
+for_each_channel_remove(void)
+{
+ struct spdk_io_channel *ch0, *ch1, *ch2;
+ int io_target;
+ int count = 0;
+
+ allocate_threads(3);
+ spdk_io_device_register(&io_target, channel_create, channel_destroy, sizeof(int), NULL);
+ set_thread(0);
+ ch0 = spdk_get_io_channel(&io_target);
+ set_thread(1);
+ ch1 = spdk_get_io_channel(&io_target);
+ set_thread(2);
+ ch2 = spdk_get_io_channel(&io_target);
+
+ /*
+ * Test that io_channel handles the case where we start to iterate through
+ * the channels, and during the iteration, one of the channels is deleted.
+ * This is done in some different and sometimes non-intuitive orders, because
+ * some operations are deferred and won't execute until their threads are
+ * polled.
+ *
+ * Case #1: Put the I/O channel before spdk_for_each_channel.
+ */
+ set_thread(0);
+ spdk_put_io_channel(ch0);
+ spdk_for_each_channel(&io_target, channel_msg, &count, channel_cpl);
+ poll_threads();
+
+ /*
+ * Case #2: Put the I/O channel after spdk_for_each_channel, but before
+ * thread 0 is polled.
+ */
+ ch0 = spdk_get_io_channel(&io_target);
+ spdk_for_each_channel(&io_target, channel_msg, &count, channel_cpl);
+ spdk_put_io_channel(ch0);
+ poll_threads();
+
+ set_thread(1);
+ spdk_put_io_channel(ch1);
+ set_thread(2);
+ spdk_put_io_channel(ch2);
+ spdk_io_device_unregister(&io_target, NULL);
+ poll_threads();
+
+ free_threads();
+}
+
+struct unreg_ctx {
+ bool ch_done;
+ bool foreach_done;
+};
+
+static void
+unreg_ch_done(struct spdk_io_channel_iter *i)
+{
+ struct unreg_ctx *ctx = spdk_io_channel_iter_get_ctx(i);
+
+ ctx->ch_done = true;
+
+ SPDK_CU_ASSERT_FATAL(i->cur_thread != NULL);
+ spdk_for_each_channel_continue(i, 0);
+}
+
+static void
+unreg_foreach_done(struct spdk_io_channel_iter *i, int status)
+{
+ struct unreg_ctx *ctx = spdk_io_channel_iter_get_ctx(i);
+
+ ctx->foreach_done = true;
+}
+
+static void
+for_each_channel_unreg(void)
+{
+ struct spdk_io_channel *ch0;
+ struct io_device *dev;
+ struct unreg_ctx ctx = {};
+ int io_target;
+
+ allocate_threads(1);
+ CU_ASSERT(TAILQ_EMPTY(&g_io_devices));
+ spdk_io_device_register(&io_target, channel_create, channel_destroy, sizeof(int), NULL);
+ CU_ASSERT(!TAILQ_EMPTY(&g_io_devices));
+ dev = TAILQ_FIRST(&g_io_devices);
+ SPDK_CU_ASSERT_FATAL(dev != NULL);
+ CU_ASSERT(TAILQ_NEXT(dev, tailq) == NULL);
+ set_thread(0);
+ ch0 = spdk_get_io_channel(&io_target);
+ spdk_for_each_channel(&io_target, unreg_ch_done, &ctx, unreg_foreach_done);
+
+ spdk_io_device_unregister(&io_target, NULL);
+ /*
+ * There is an outstanding foreach call on the io_device, so the unregister should not
+ * have removed the device.
+ */
+ CU_ASSERT(dev == TAILQ_FIRST(&g_io_devices));
+ spdk_io_device_register(&io_target, channel_create, channel_destroy, sizeof(int), NULL);
+ /*
+ * There is already a device registered at &io_target, so a new io_device should not
+ * have been added to g_io_devices.
+ */
+ CU_ASSERT(dev == TAILQ_FIRST(&g_io_devices));
+ CU_ASSERT(TAILQ_NEXT(dev, tailq) == NULL);
+
+ poll_thread(0);
+ CU_ASSERT(ctx.ch_done == true);
+ CU_ASSERT(ctx.foreach_done == true);
+ /*
+ * There are no more foreach operations outstanding, so we can unregister the device,
+ * even though a channel still exists for the device.
+ */
+ spdk_io_device_unregister(&io_target, NULL);
+ CU_ASSERT(TAILQ_EMPTY(&g_io_devices));
+
+ set_thread(0);
+ spdk_put_io_channel(ch0);
+
+ poll_threads();
+
+ free_threads();
+}
+
+static void
+thread_name(void)
+{
+ struct spdk_thread *thread;
+ const char *name;
+
+ /* Create thread with no name, which automatically generates one */
+ spdk_allocate_thread(_send_msg, NULL, NULL, NULL, NULL);
+ thread = spdk_get_thread();
+ SPDK_CU_ASSERT_FATAL(thread != NULL);
+ name = spdk_thread_get_name(thread);
+ CU_ASSERT(name != NULL);
+ spdk_free_thread();
+
+ /* Create thread named "test_thread" */
+ spdk_allocate_thread(_send_msg, NULL, NULL, NULL, "test_thread");
+ thread = spdk_get_thread();
+ SPDK_CU_ASSERT_FATAL(thread != NULL);
+ name = spdk_thread_get_name(thread);
+ SPDK_CU_ASSERT_FATAL(name != NULL);
+ CU_ASSERT(strcmp(name, "test_thread") == 0);
+ spdk_free_thread();
+}
+
+static uint64_t device1;
+static uint64_t device2;
+static uint64_t device3;
+
+static uint64_t ctx1 = 0x1111;
+static uint64_t ctx2 = 0x2222;
+
+static int g_create_cb_calls = 0;
+static int g_destroy_cb_calls = 0;
+
+static int
+create_cb_1(void *io_device, void *ctx_buf)
+{
+ CU_ASSERT(io_device == &device1);
+ *(uint64_t *)ctx_buf = ctx1;
+ g_create_cb_calls++;
+ return 0;
+}
+
+static void
+destroy_cb_1(void *io_device, void *ctx_buf)
+{
+ CU_ASSERT(io_device == &device1);
+ CU_ASSERT(*(uint64_t *)ctx_buf == ctx1);
+ g_destroy_cb_calls++;
+}
+
+static int
+create_cb_2(void *io_device, void *ctx_buf)
+{
+ CU_ASSERT(io_device == &device2);
+ *(uint64_t *)ctx_buf = ctx2;
+ g_create_cb_calls++;
+ return 0;
+}
+
+static void
+destroy_cb_2(void *io_device, void *ctx_buf)
+{
+ CU_ASSERT(io_device == &device2);
+ CU_ASSERT(*(uint64_t *)ctx_buf == ctx2);
+ g_destroy_cb_calls++;
+}
+
+static void
+channel(void)
+{
+ struct spdk_io_channel *ch1, *ch2;
+ void *ctx;
+
+ spdk_allocate_thread(_send_msg, NULL, NULL, NULL, "thread0");
+ spdk_io_device_register(&device1, create_cb_1, destroy_cb_1, sizeof(ctx1), NULL);
+ spdk_io_device_register(&device2, create_cb_2, destroy_cb_2, sizeof(ctx2), NULL);
+
+ g_create_cb_calls = 0;
+ ch1 = spdk_get_io_channel(&device1);
+ CU_ASSERT(g_create_cb_calls == 1);
+ SPDK_CU_ASSERT_FATAL(ch1 != NULL);
+
+ g_create_cb_calls = 0;
+ ch2 = spdk_get_io_channel(&device1);
+ CU_ASSERT(g_create_cb_calls == 0);
+ CU_ASSERT(ch1 == ch2);
+ SPDK_CU_ASSERT_FATAL(ch2 != NULL);
+
+ g_destroy_cb_calls = 0;
+ spdk_put_io_channel(ch2);
+ CU_ASSERT(g_destroy_cb_calls == 0);
+
+ g_create_cb_calls = 0;
+ ch2 = spdk_get_io_channel(&device2);
+ CU_ASSERT(g_create_cb_calls == 1);
+ CU_ASSERT(ch1 != ch2);
+ SPDK_CU_ASSERT_FATAL(ch2 != NULL);
+
+ ctx = spdk_io_channel_get_ctx(ch2);
+ CU_ASSERT(*(uint64_t *)ctx == ctx2);
+
+ g_destroy_cb_calls = 0;
+ spdk_put_io_channel(ch1);
+ CU_ASSERT(g_destroy_cb_calls == 1);
+
+ g_destroy_cb_calls = 0;
+ spdk_put_io_channel(ch2);
+ CU_ASSERT(g_destroy_cb_calls == 1);
+
+ ch1 = spdk_get_io_channel(&device3);
+ CU_ASSERT(ch1 == NULL);
+
+ spdk_io_device_unregister(&device1, NULL);
+ spdk_io_device_unregister(&device2, NULL);
+ CU_ASSERT(TAILQ_EMPTY(&g_io_devices));
+ spdk_free_thread();
+ CU_ASSERT(TAILQ_EMPTY(&g_threads));
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("io_channel", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "thread_alloc", thread_alloc) == NULL ||
+ CU_add_test(suite, "thread_send_msg", thread_send_msg) == NULL ||
+ CU_add_test(suite, "thread_poller", thread_poller) == NULL ||
+ CU_add_test(suite, "thread_for_each", thread_for_each) == NULL ||
+ CU_add_test(suite, "for_each_channel_remove", for_each_channel_remove) == NULL ||
+ CU_add_test(suite, "for_each_channel_unreg", for_each_channel_unreg) == NULL ||
+ CU_add_test(suite, "thread_name", thread_name) == NULL ||
+ CU_add_test(suite, "channel", channel) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/util/Makefile b/src/spdk/test/unit/lib/util/Makefile
new file mode 100644
index 00000000..4813e63b
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/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 = base64.c bit_array.c cpuset.c crc16.c crc32_ieee.c crc32c.c string.c
+
+.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/unit/lib/util/base64.c/.gitignore b/src/spdk/test/unit/lib/util/base64.c/.gitignore
new file mode 100644
index 00000000..a5b17523
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/base64.c/.gitignore
@@ -0,0 +1 @@
+base64_ut
diff --git a/src/spdk/test/unit/lib/util/base64.c/Makefile b/src/spdk/test/unit/lib/util/base64.c/Makefile
new file mode 100644
index 00000000..ff6c9214
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/base64.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = base64_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/util/base64.c/base64_ut.c b/src/spdk/test/unit/lib/util/base64.c/base64_ut.c
new file mode 100644
index 00000000..652a1e94
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/base64.c/base64_ut.c
@@ -0,0 +1,268 @@
+/*-
+ * 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_cunit.h"
+
+#include "util/base64.c"
+
+char text_A[] = "FZB3";
+uint8_t raw_A[] = {0x15, 0x90, 0x77};
+char text_B[] = "AbC/1+c=";
+char text_urlsafe_B[] = "AbC_1-c=";
+uint8_t raw_B[] = {0x01, 0xB0, 0xBF, 0xD7, 0xE7};
+char text_C[] = "AbC/1+cC";
+char text_urlsafe_C[] = "AbC_1-cC";
+uint8_t raw_C[] = {0x01, 0xB0, 0xBF, 0xD7, 0xE7, 0x02};
+char text_D[] = "AbC/1w==";
+char text_urlsafe_D[] = "AbC_1w==";
+uint8_t raw_D[] = {0x01, 0xB0, 0xBF, 0xD7};
+char text_E[] = "AbC12===";
+char text_F[] = "AbCd112";
+char text_G[] = "AbCd12";
+char text_H[] = "AbC12";
+
+static void
+test_base64_get_encoded_strlen(void)
+{
+ uint32_t raw_lens[4] = {8, 9, 10, 11};
+ uint32_t text_strlens[4] = {12, 12, 16, 16};
+ uint32_t text_strlen;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ text_strlen = spdk_base64_get_encoded_strlen(raw_lens[i]);
+ CU_ASSERT_EQUAL(text_strlen, text_strlens[i]);
+ }
+}
+
+static void
+test_base64_get_decoded_len(void)
+{
+ uint32_t text_strlens[4] = {8, 10, 11, 12};
+ uint32_t raw_lens[4] = {6, 7, 8, 9};
+ uint32_t bin_len;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ bin_len = spdk_base64_get_decoded_len(text_strlens[i]);
+ CU_ASSERT_EQUAL(bin_len, raw_lens[i]);
+ }
+}
+
+static void
+test_base64_encode(void)
+{
+ char text[100];
+ int ret;
+
+ ret = spdk_base64_encode(text, raw_A, sizeof(raw_A));
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT(strcmp(text, text_A) == 0);
+ CU_ASSERT_EQUAL(strlen(text), strlen(text_A));
+
+ ret = spdk_base64_encode(text, raw_B, sizeof(raw_B));
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT(strcmp(text, text_B) == 0);
+ CU_ASSERT_EQUAL(strlen(text), strlen(text_B));
+
+ ret = spdk_base64_encode(text, raw_C, sizeof(raw_C));
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT(strcmp(text, text_C) == 0);
+
+ ret = spdk_base64_encode(text, raw_D, sizeof(raw_D));
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT(strcmp(text, text_D) == 0);
+
+ ret = spdk_base64_encode(NULL, raw_A, sizeof(raw_A));
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_encode(text, NULL, sizeof(raw_A));
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_encode(text, raw_A, 0);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+}
+
+static void
+test_base64_decode(void)
+{
+ char raw_buf[100];
+ void *raw = (void *)raw_buf;
+ size_t raw_len;
+ int ret;
+
+ ret = spdk_base64_decode(raw, &raw_len, text_A);
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT_EQUAL(raw_len, sizeof(raw_A));
+ CU_ASSERT(memcmp(raw, raw_A, sizeof(raw_A)) == 0);
+
+ ret = spdk_base64_decode(raw, &raw_len, text_B);
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT_EQUAL(raw_len, sizeof(raw_B));
+ CU_ASSERT(memcmp(raw, raw_B, sizeof(raw_B)) == 0);
+
+ ret = spdk_base64_decode(raw, &raw_len, text_C);
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT_EQUAL(raw_len, sizeof(raw_C));
+ CU_ASSERT(memcmp(raw, raw_C, sizeof(raw_C)) == 0);
+
+ ret = spdk_base64_decode(raw, &raw_len, text_D);
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT_EQUAL(raw_len, sizeof(raw_D));
+ CU_ASSERT(memcmp(raw, raw_D, sizeof(raw_D)) == 0);
+
+ ret = spdk_base64_decode(raw, &raw_len, text_E);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_decode(raw, &raw_len, text_F);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_decode(raw, &raw_len, text_G);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_decode(raw, &raw_len, text_H);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_decode(NULL, &raw_len, text_H);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_decode(raw, &raw_len, NULL);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+}
+
+static void
+test_base64_urlsafe_encode(void)
+{
+ char text[100];
+ int ret;
+
+ ret = spdk_base64_urlsafe_encode(text, raw_A, sizeof(raw_A));
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT(strcmp(text, text_A) == 0);
+ CU_ASSERT_EQUAL(strlen(text), strlen(text_A));
+
+ ret = spdk_base64_urlsafe_encode(text, raw_B, sizeof(raw_B));
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT(strcmp(text, text_urlsafe_B) == 0);
+ CU_ASSERT_EQUAL(strlen(text), strlen(text_urlsafe_B));
+
+ ret = spdk_base64_urlsafe_encode(text, raw_C, sizeof(raw_C));
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT(strcmp(text, text_urlsafe_C) == 0);
+
+ ret = spdk_base64_urlsafe_encode(text, raw_D, sizeof(raw_D));
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT(strcmp(text, text_urlsafe_D) == 0);
+
+ ret = spdk_base64_urlsafe_encode(NULL, raw_A, sizeof(raw_A));
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_urlsafe_encode(text, NULL, sizeof(raw_A));
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_urlsafe_encode(text, raw_A, 0);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+}
+
+static void
+test_base64_urlsafe_decode(void)
+{
+ char raw_buf[100];
+ void *raw = (void *)raw_buf;
+ size_t raw_len = 0;
+ int ret;
+
+ ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_A);
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT_EQUAL(raw_len, sizeof(raw_A));
+ CU_ASSERT(memcmp(raw, raw_A, sizeof(raw_A)) == 0);
+
+ ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_urlsafe_B);
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT_EQUAL(raw_len, sizeof(raw_B));
+ CU_ASSERT(memcmp(raw, raw_B, sizeof(raw_B)) == 0);
+
+ ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_urlsafe_C);
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT_EQUAL(raw_len, sizeof(raw_C));
+ CU_ASSERT(memcmp(raw, raw_C, sizeof(raw_C)) == 0);
+
+ ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_urlsafe_D);
+ CU_ASSERT_EQUAL(ret, 0);
+ CU_ASSERT_EQUAL(raw_len, sizeof(raw_D));
+ CU_ASSERT(memcmp(raw, raw_D, sizeof(raw_D)) == 0);
+
+ ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_E);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_F);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_G);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_urlsafe_decode(raw, &raw_len, text_H);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_urlsafe_decode(NULL, &raw_len, text_H);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+ ret = spdk_base64_urlsafe_decode(raw, &raw_len, NULL);
+ CU_ASSERT_EQUAL(ret, -EINVAL);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("base64", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test_base64_get_encoded_strlen", test_base64_get_encoded_strlen) == NULL ||
+ CU_add_test(suite, "test_base64_get_decoded_len",
+ test_base64_get_decoded_len) == NULL ||
+ CU_add_test(suite, "test_base64_encode", test_base64_encode) == NULL ||
+ CU_add_test(suite, "test_base64_decode", test_base64_decode) == NULL ||
+ CU_add_test(suite, "test_base64_urlsafe_encode", test_base64_urlsafe_encode) == NULL ||
+ CU_add_test(suite, "test_base64_urlsafe_decode", test_base64_urlsafe_decode) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/util/bit_array.c/.gitignore b/src/spdk/test/unit/lib/util/bit_array.c/.gitignore
new file mode 100644
index 00000000..24300cdb
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/bit_array.c/.gitignore
@@ -0,0 +1 @@
+bit_array_ut
diff --git a/src/spdk/test/unit/lib/util/bit_array.c/Makefile b/src/spdk/test/unit/lib/util/bit_array.c/Makefile
new file mode 100644
index 00000000..b7f8e3f6
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/bit_array.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = bit_array_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/util/bit_array.c/bit_array_ut.c b/src/spdk/test/unit/lib/util/bit_array.c/bit_array_ut.c
new file mode 100644
index 00000000..18d84b94
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/bit_array.c/bit_array_ut.c
@@ -0,0 +1,327 @@
+/*-
+ * 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_cunit.h"
+
+#include "util/bit_array.c"
+
+void *
+spdk_dma_realloc(void *buf, size_t size, size_t align, uint64_t *phys_addr)
+{
+ return realloc(buf, size);
+}
+
+void
+spdk_dma_free(void *buf)
+{
+ free(buf);
+}
+
+static void
+test_1bit(void)
+{
+ struct spdk_bit_array *ba;
+
+ ba = spdk_bit_array_create(1);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_capacity(ba) == 1);
+
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == false);
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == UINT32_MAX);
+
+ /* Set bit 0 */
+ CU_ASSERT(spdk_bit_array_set(ba, 0) == 0);
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == true);
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == 0);
+
+ /* Clear bit 0 */
+ spdk_bit_array_clear(ba, 0);
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == false);
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == UINT32_MAX);
+
+ spdk_bit_array_free(&ba);
+ CU_ASSERT(ba == NULL);
+}
+
+static void
+test_64bit(void)
+{
+ struct spdk_bit_array *ba;
+
+ ba = spdk_bit_array_create(64);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_capacity(ba) == 64);
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == false);
+ CU_ASSERT(spdk_bit_array_get(ba, 63) == false);
+ CU_ASSERT(spdk_bit_array_get(ba, 64) == false);
+ CU_ASSERT(spdk_bit_array_get(ba, 1000) == false);
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == UINT32_MAX);
+
+ /* Set bit 1 */
+ CU_ASSERT(spdk_bit_array_set(ba, 1) == 0);
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == false);
+ CU_ASSERT(spdk_bit_array_get(ba, 1) == true);
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == 1);
+
+ /* Set bit 63 (1 still set) */
+ CU_ASSERT(spdk_bit_array_set(ba, 63) == 0);
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == false);
+ CU_ASSERT(spdk_bit_array_get(ba, 1) == true);
+ CU_ASSERT(spdk_bit_array_get(ba, 63) == true);
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == 1);
+
+ /* Clear bit 1 (63 still set) */
+ spdk_bit_array_clear(ba, 1);
+ CU_ASSERT(spdk_bit_array_get(ba, 1) == false);
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == 63);
+
+ /* Clear bit 63 (no bits set) */
+ spdk_bit_array_clear(ba, 63);
+ CU_ASSERT(spdk_bit_array_get(ba, 63) == false);
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, 0) == UINT32_MAX);
+
+ spdk_bit_array_free(&ba);
+}
+
+static void
+test_find(void)
+{
+ struct spdk_bit_array *ba;
+ uint32_t i;
+
+ ba = spdk_bit_array_create(256);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_capacity(ba) == 256);
+
+ /* Set all bits */
+ for (i = 0; i < 256; i++) {
+ CU_ASSERT(spdk_bit_array_set(ba, i) == 0);
+ }
+
+ /* Verify that find_first_set and find_first_clear work for each starting position */
+ for (i = 0; i < 256; i++) {
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, i) == i);
+ CU_ASSERT(spdk_bit_array_find_first_clear(ba, i) == UINT32_MAX);
+ }
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, 256) == UINT32_MAX);
+ CU_ASSERT(spdk_bit_array_find_first_clear(ba, 256) == UINT32_MAX);
+
+ /* Clear bits 0 through 31 */
+ for (i = 0; i < 32; i++) {
+ spdk_bit_array_clear(ba, i);
+ }
+
+ for (i = 0; i < 32; i++) {
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, i) == 32);
+ CU_ASSERT(spdk_bit_array_find_first_clear(ba, i) == i);
+ }
+
+ for (i = 32; i < 256; i++) {
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, i) == i);
+ CU_ASSERT(spdk_bit_array_find_first_clear(ba, i) == UINT32_MAX);
+ }
+
+ /* Clear bit 255 */
+ spdk_bit_array_clear(ba, 255);
+
+ for (i = 0; i < 32; i++) {
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, i) == 32);
+ CU_ASSERT(spdk_bit_array_find_first_clear(ba, i) == i);
+ }
+
+ for (i = 32; i < 255; i++) {
+ CU_ASSERT(spdk_bit_array_find_first_set(ba, i) == i);
+ CU_ASSERT(spdk_bit_array_find_first_clear(ba, i) == 255);
+ }
+
+ CU_ASSERT(spdk_bit_array_find_first_clear(ba, 256) == UINT32_MAX);
+
+ spdk_bit_array_free(&ba);
+}
+
+static void
+test_resize(void)
+{
+ struct spdk_bit_array *ba;
+
+ /* Start with a 0 bit array */
+ ba = spdk_bit_array_create(0);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_capacity(ba) == 0);
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == false);
+ CU_ASSERT(spdk_bit_array_set(ba, 0) == -EINVAL);
+ spdk_bit_array_clear(ba, 0);
+
+ /* Increase size to 1 bit */
+ SPDK_CU_ASSERT_FATAL(spdk_bit_array_resize(&ba, 1) == 0);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_capacity(ba) == 1);
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == false);
+ CU_ASSERT(spdk_bit_array_set(ba, 0) == 0);
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == true);
+
+ /* Increase size to 2 bits */
+ SPDK_CU_ASSERT_FATAL(spdk_bit_array_resize(&ba, 2) == 0);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_capacity(ba) == 2);
+ CU_ASSERT(spdk_bit_array_get(ba, 1) == false);
+ CU_ASSERT(spdk_bit_array_set(ba, 1) == 0);
+ CU_ASSERT(spdk_bit_array_get(ba, 1) == true);
+
+ /* Shrink size back to 1 bit */
+ SPDK_CU_ASSERT_FATAL(spdk_bit_array_resize(&ba, 1) == 0);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_capacity(ba) == 1);
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == true);
+ CU_ASSERT(spdk_bit_array_get(ba, 1) == false);
+
+ /* Increase size to 65 bits */
+ SPDK_CU_ASSERT_FATAL(spdk_bit_array_resize(&ba, 65) == 0);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_capacity(ba) == 65);
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == true);
+ CU_ASSERT(spdk_bit_array_get(ba, 1) == false);
+ CU_ASSERT(spdk_bit_array_set(ba, 64) == 0);
+ CU_ASSERT(spdk_bit_array_get(ba, 64) == true);
+
+ /* Shrink size back to 0 bits */
+ SPDK_CU_ASSERT_FATAL(spdk_bit_array_resize(&ba, 0) == 0);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_capacity(ba) == 0);
+ CU_ASSERT(spdk_bit_array_get(ba, 0) == false);
+ CU_ASSERT(spdk_bit_array_get(ba, 1) == false);
+
+ spdk_bit_array_free(&ba);
+}
+
+static void
+test_errors(void)
+{
+ /* Passing NULL to resize should fail. */
+ CU_ASSERT(spdk_bit_array_resize(NULL, 0) == -EINVAL);
+
+ /* Passing NULL to free is a no-op. */
+ spdk_bit_array_free(NULL);
+}
+
+static void
+test_count(void)
+{
+ struct spdk_bit_array *ba;
+ uint32_t i;
+
+ /* 0-bit array should have 0 bits set and 0 bits clear */
+ ba = spdk_bit_array_create(0);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_count_set(ba) == 0);
+ CU_ASSERT(spdk_bit_array_count_clear(ba) == 0);
+ spdk_bit_array_free(&ba);
+
+ /* 1-bit array */
+ ba = spdk_bit_array_create(1);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_count_set(ba) == 0);
+ CU_ASSERT(spdk_bit_array_count_clear(ba) == 1);
+ spdk_bit_array_set(ba, 0);
+ CU_ASSERT(spdk_bit_array_count_set(ba) == 1);
+ CU_ASSERT(spdk_bit_array_count_clear(ba) == 0);
+ spdk_bit_array_free(&ba);
+
+ /* 65-bit array */
+ ba = spdk_bit_array_create(65);
+ SPDK_CU_ASSERT_FATAL(ba != NULL);
+ CU_ASSERT(spdk_bit_array_count_set(ba) == 0);
+ CU_ASSERT(spdk_bit_array_count_clear(ba) == 65);
+ spdk_bit_array_set(ba, 0);
+ CU_ASSERT(spdk_bit_array_count_set(ba) == 1);
+ CU_ASSERT(spdk_bit_array_count_clear(ba) == 64);
+ spdk_bit_array_set(ba, 5);
+ CU_ASSERT(spdk_bit_array_count_set(ba) == 2);
+ CU_ASSERT(spdk_bit_array_count_clear(ba) == 63);
+ spdk_bit_array_set(ba, 13);
+ CU_ASSERT(spdk_bit_array_count_set(ba) == 3);
+ CU_ASSERT(spdk_bit_array_count_clear(ba) == 62);
+ spdk_bit_array_clear(ba, 0);
+ CU_ASSERT(spdk_bit_array_count_set(ba) == 2);
+ CU_ASSERT(spdk_bit_array_count_clear(ba) == 63);
+ for (i = 0; i < 65; i++) {
+ spdk_bit_array_set(ba, i);
+ }
+ CU_ASSERT(spdk_bit_array_count_set(ba) == 65);
+ CU_ASSERT(spdk_bit_array_count_clear(ba) == 0);
+ for (i = 0; i < 65; i++) {
+ spdk_bit_array_clear(ba, i);
+ CU_ASSERT(spdk_bit_array_count_set(ba) == 65 - i - 1);
+ CU_ASSERT(spdk_bit_array_count_clear(ba) == i + 1);
+ }
+ spdk_bit_array_free(&ba);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("bit_array", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test_1bit", test_1bit) == NULL ||
+ CU_add_test(suite, "test_64bit", test_64bit) == NULL ||
+ CU_add_test(suite, "test_find", test_find) == NULL ||
+ CU_add_test(suite, "test_resize", test_resize) == NULL ||
+ CU_add_test(suite, "test_errors", test_errors) == NULL ||
+ CU_add_test(suite, "test_count", test_count) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/util/cpuset.c/.gitignore b/src/spdk/test/unit/lib/util/cpuset.c/.gitignore
new file mode 100644
index 00000000..2ca1a2d3
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/cpuset.c/.gitignore
@@ -0,0 +1 @@
+cpuset_ut
diff --git a/src/spdk/test/unit/lib/util/cpuset.c/Makefile b/src/spdk/test/unit/lib/util/cpuset.c/Makefile
new file mode 100644
index 00000000..da7a1400
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/cpuset.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = cpuset_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/util/cpuset.c/cpuset_ut.c b/src/spdk/test/unit/lib/util/cpuset.c/cpuset_ut.c
new file mode 100644
index 00000000..6fea0ad3
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/cpuset.c/cpuset_ut.c
@@ -0,0 +1,265 @@
+/*-
+ * 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/cpuset.h"
+
+#include "spdk_cunit.h"
+
+#include "util/cpuset.c"
+
+static int
+cpuset_check_range(struct spdk_cpuset *core_mask, uint32_t min, uint32_t max, bool isset)
+{
+ uint32_t core;
+ for (core = min; core <= max; core++) {
+ if (isset != spdk_cpuset_get_cpu(core_mask, core)) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void
+test_cpuset(void)
+{
+ uint32_t cpu;
+ struct spdk_cpuset *set = spdk_cpuset_alloc();
+
+ SPDK_CU_ASSERT_FATAL(set != NULL);
+ CU_ASSERT(spdk_cpuset_count(set) == 0);
+
+ /* Set cpu 0 */
+ spdk_cpuset_set_cpu(set, 0, true);
+ CU_ASSERT(spdk_cpuset_get_cpu(set, 0) == true);
+ CU_ASSERT(cpuset_check_range(set, 1, SPDK_CPUSET_SIZE - 1, false) == 0);
+ CU_ASSERT(spdk_cpuset_count(set) == 1);
+
+ /* Set last cpu (cpu 0 already set) */
+ spdk_cpuset_set_cpu(set, SPDK_CPUSET_SIZE - 1, true);
+ CU_ASSERT(spdk_cpuset_get_cpu(set, 0) == true);
+ CU_ASSERT(spdk_cpuset_get_cpu(set, SPDK_CPUSET_SIZE - 1) == true);
+ CU_ASSERT(cpuset_check_range(set, 1, SPDK_CPUSET_SIZE - 2, false) == 0);
+ CU_ASSERT(spdk_cpuset_count(set) == 2);
+
+ /* Clear cpu 0 (last cpu already set) */
+ spdk_cpuset_set_cpu(set, 0, false);
+ CU_ASSERT(spdk_cpuset_get_cpu(set, 0) == false);
+ CU_ASSERT(cpuset_check_range(set, 1, SPDK_CPUSET_SIZE - 2, false) == 0);
+ CU_ASSERT(spdk_cpuset_get_cpu(set, SPDK_CPUSET_SIZE - 1) == true);
+ CU_ASSERT(spdk_cpuset_count(set) == 1);
+
+ /* Set middle cpu (last cpu already set) */
+ cpu = (SPDK_CPUSET_SIZE - 1) / 2;
+ spdk_cpuset_set_cpu(set, cpu, true);
+ CU_ASSERT(spdk_cpuset_get_cpu(set, cpu) == true);
+ CU_ASSERT(spdk_cpuset_get_cpu(set, SPDK_CPUSET_SIZE - 1) == true);
+ CU_ASSERT(cpuset_check_range(set, 1, cpu - 1, false) == 0);
+ CU_ASSERT(cpuset_check_range(set, cpu + 1, SPDK_CPUSET_SIZE - 2, false) == 0);
+ CU_ASSERT(spdk_cpuset_count(set) == 2);
+
+ /* Set all cpus */
+ for (cpu = 0; cpu < SPDK_CPUSET_SIZE; cpu++) {
+ spdk_cpuset_set_cpu(set, cpu, true);
+ }
+ CU_ASSERT(cpuset_check_range(set, 0, SPDK_CPUSET_SIZE - 1, true) == 0);
+ CU_ASSERT(spdk_cpuset_count(set) == SPDK_CPUSET_SIZE);
+
+ /* Clear all cpus */
+ spdk_cpuset_zero(set);
+ CU_ASSERT(cpuset_check_range(set, 0, SPDK_CPUSET_SIZE - 1, false) == 0);
+ CU_ASSERT(spdk_cpuset_count(set) == 0);
+
+ spdk_cpuset_free(set);
+}
+
+static void
+test_cpuset_parse(void)
+{
+ int rc;
+ struct spdk_cpuset *core_mask;
+ char buf[1024];
+
+ core_mask = spdk_cpuset_alloc();
+ SPDK_CU_ASSERT_FATAL(core_mask != NULL);
+
+ /* Only core 0 should be set */
+ rc = spdk_cpuset_parse(core_mask, "0x1");
+ CU_ASSERT(rc >= 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 0, 0, true) == 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 1, SPDK_CPUSET_SIZE - 1, false) == 0);
+
+ /* Only core 1 should be set */
+ rc = spdk_cpuset_parse(core_mask, "[1]");
+ CU_ASSERT(rc >= 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 0, 0, false) == 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 1, 1, true) == 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 2, SPDK_CPUSET_SIZE - 1, false) == 0);
+
+ /* Set cores 0-10,12,128-254 */
+ rc = spdk_cpuset_parse(core_mask, "[0-10,12,128-254]");
+ CU_ASSERT(rc >= 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 0, 10, true) == 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 11, 11, false) == 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 12, 12, true) == 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 13, 127, false) == 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 128, 254, true) == 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 255, SPDK_CPUSET_SIZE - 1, false) == 0);
+
+ /* Set all cores */
+ snprintf(buf, sizeof(buf), "[0-%d]", SPDK_CPUSET_SIZE - 1);
+ rc = spdk_cpuset_parse(core_mask, buf);
+ CU_ASSERT(rc >= 0);
+ CU_ASSERT(cpuset_check_range(core_mask, 0, SPDK_CPUSET_SIZE - 1, true) == 0);
+
+ /* Null parameters not allowed */
+ rc = spdk_cpuset_parse(core_mask, NULL);
+ CU_ASSERT(rc < 0);
+
+ rc = spdk_cpuset_parse(NULL, "[1]");
+ CU_ASSERT(rc < 0);
+
+ /* Wrong formated core lists */
+ rc = spdk_cpuset_parse(core_mask, "");
+ CU_ASSERT(rc < 0);
+
+ rc = spdk_cpuset_parse(core_mask, "[");
+ CU_ASSERT(rc < 0);
+
+ rc = spdk_cpuset_parse(core_mask, "[]");
+ CU_ASSERT(rc < 0);
+
+ rc = spdk_cpuset_parse(core_mask, "[10--11]");
+ CU_ASSERT(rc < 0);
+
+ rc = spdk_cpuset_parse(core_mask, "[11-10]");
+ CU_ASSERT(rc < 0);
+
+ rc = spdk_cpuset_parse(core_mask, "[10-11,]");
+ CU_ASSERT(rc < 0);
+
+ rc = spdk_cpuset_parse(core_mask, "[,10-11]");
+ CU_ASSERT(rc < 0);
+
+ /* Out of range value */
+ snprintf(buf, sizeof(buf), "[%d]", SPDK_CPUSET_SIZE + 1);
+ rc = spdk_cpuset_parse(core_mask, buf);
+ CU_ASSERT(rc < 0);
+
+ /* Overflow value (UINT64_MAX * 10) */
+ rc = spdk_cpuset_parse(core_mask, "[184467440737095516150]");
+ CU_ASSERT(rc < 0);
+
+ spdk_cpuset_free(core_mask);
+}
+
+static void
+test_cpuset_fmt(void)
+{
+ int i;
+ uint32_t lcore;
+ struct spdk_cpuset *core_mask = spdk_cpuset_alloc();
+ const char *hex_mask;
+ char hex_mask_ref[SPDK_CPUSET_SIZE / 4 + 1];
+
+ /* Clear coremask. hex_mask should be "0" */
+ spdk_cpuset_zero(core_mask);
+ hex_mask = spdk_cpuset_fmt(core_mask);
+ SPDK_CU_ASSERT_FATAL(hex_mask != NULL);
+ CU_ASSERT(strcmp("0", hex_mask) == 0);
+
+ /* Set coremask 0x51234. Result should be "51234" */
+ spdk_cpuset_zero(core_mask);
+ spdk_cpuset_set_cpu(core_mask, 2, true);
+ spdk_cpuset_set_cpu(core_mask, 4, true);
+ spdk_cpuset_set_cpu(core_mask, 5, true);
+ spdk_cpuset_set_cpu(core_mask, 9, true);
+ spdk_cpuset_set_cpu(core_mask, 12, true);
+ spdk_cpuset_set_cpu(core_mask, 16, true);
+ spdk_cpuset_set_cpu(core_mask, 18, true);
+ hex_mask = spdk_cpuset_fmt(core_mask);
+ SPDK_CU_ASSERT_FATAL(hex_mask != NULL);
+ CU_ASSERT(strcmp("51234", hex_mask) == 0);
+
+ /* Set all cores */
+ spdk_cpuset_zero(core_mask);
+ for (lcore = 0; lcore < SPDK_CPUSET_SIZE; lcore++) {
+ spdk_cpuset_set_cpu(core_mask, lcore, true);
+ }
+ for (i = 0; i < SPDK_CPUSET_SIZE / 4 - 1; i++) {
+ hex_mask_ref[i] = 'f';
+ }
+ hex_mask_ref[SPDK_CPUSET_SIZE / 4 - 1] = '\0';
+
+ hex_mask = spdk_cpuset_fmt(core_mask);
+ CU_ASSERT(hex_mask != NULL);
+ if (hex_mask != NULL) {
+ CU_ASSERT(strcmp(hex_mask_ref, hex_mask) == 0);
+ }
+
+ spdk_cpuset_free(core_mask);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("cpuset", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test_cpuset", test_cpuset) == NULL ||
+ CU_add_test(suite, "test_cpuset_parse", test_cpuset_parse) == NULL ||
+ CU_add_test(suite, "test_cpuset_fmt", test_cpuset_fmt) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/util/crc16.c/.gitignore b/src/spdk/test/unit/lib/util/crc16.c/.gitignore
new file mode 100644
index 00000000..d026adf0
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/crc16.c/.gitignore
@@ -0,0 +1 @@
+crc16_ut
diff --git a/src/spdk/test/unit/lib/util/crc16.c/Makefile b/src/spdk/test/unit/lib/util/crc16.c/Makefile
new file mode 100644
index 00000000..6b8b2ad4
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/crc16.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = crc16_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/util/crc16.c/crc16_ut.c b/src/spdk/test/unit/lib/util/crc16.c/crc16_ut.c
new file mode 100644
index 00000000..8b05e900
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/crc16.c/crc16_ut.c
@@ -0,0 +1,80 @@
+/*-
+ * 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_cunit.h"
+
+#include "util/crc16.c"
+
+static void
+test_crc16_t10dif(void)
+{
+ uint16_t crc;
+ char buf[] = "123456789";
+
+ crc = spdk_crc16_t10dif(buf, strlen(buf));
+ CU_ASSERT(crc == 0xd0db);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("crc16", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test_crc16_t10dif", test_crc16_t10dif) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/util/crc32_ieee.c/.gitignore b/src/spdk/test/unit/lib/util/crc32_ieee.c/.gitignore
new file mode 100644
index 00000000..40a85a93
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/crc32_ieee.c/.gitignore
@@ -0,0 +1 @@
+crc32_ieee_ut
diff --git a/src/spdk/test/unit/lib/util/crc32_ieee.c/Makefile b/src/spdk/test/unit/lib/util/crc32_ieee.c/Makefile
new file mode 100644
index 00000000..000e1ba6
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/crc32_ieee.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = crc32_ieee_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/util/crc32_ieee.c/crc32_ieee_ut.c b/src/spdk/test/unit/lib/util/crc32_ieee.c/crc32_ieee_ut.c
new file mode 100644
index 00000000..9a076998
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/crc32_ieee.c/crc32_ieee_ut.c
@@ -0,0 +1,83 @@
+/*-
+ * 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_cunit.h"
+
+#include "util/crc32.c"
+#include "util/crc32_ieee.c"
+
+static void
+test_crc32_ieee(void)
+{
+ uint32_t crc;
+ char buf[] = "Hello world!";
+
+ crc = 0xFFFFFFFFu;
+ crc = spdk_crc32_ieee_update(buf, strlen(buf), crc);
+ crc ^= 0xFFFFFFFFu;
+ CU_ASSERT(crc == 0x1b851995);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("crc32_ieee", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test_crc32_ieee", test_crc32_ieee) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/util/crc32c.c/.gitignore b/src/spdk/test/unit/lib/util/crc32c.c/.gitignore
new file mode 100644
index 00000000..55bedec7
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/crc32c.c/.gitignore
@@ -0,0 +1 @@
+crc32c_ut
diff --git a/src/spdk/test/unit/lib/util/crc32c.c/Makefile b/src/spdk/test/unit/lib/util/crc32c.c/Makefile
new file mode 100644
index 00000000..eba81722
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/crc32c.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = crc32c_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/util/crc32c.c/crc32c_ut.c b/src/spdk/test/unit/lib/util/crc32c.c/crc32c_ut.c
new file mode 100644
index 00000000..49b2f852
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/crc32c.c/crc32c_ut.c
@@ -0,0 +1,154 @@
+/*-
+ * 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_cunit.h"
+
+#include "util/crc32.c"
+#include "util/crc32c.c"
+
+static void
+test_crc32c(void)
+{
+ uint32_t crc;
+ char buf[1024];
+
+ /* Verify a string's CRC32-C value against the known correct result. */
+ snprintf(buf, sizeof(buf), "%s", "Hello world!");
+ crc = 0xFFFFFFFFu;
+ crc = spdk_crc32c_update(buf, strlen(buf), crc);
+ crc ^= 0xFFFFFFFFu;
+ CU_ASSERT(crc == 0x7b98e751);
+
+ /*
+ * The main loop of the optimized CRC32-C implementation processes data in 8-byte blocks,
+ * followed by a loop to handle the 0-7 trailing bytes.
+ * Test all buffer sizes from 0 to 7 in order to hit all possible trailing byte counts.
+ */
+
+ /* 0-byte buffer should not modify CRC at all, so final result should be ~0 ^ ~0 == 0 */
+ snprintf(buf, sizeof(buf), "%s", "");
+ crc = 0xFFFFFFFFu;
+ crc = spdk_crc32c_update(buf, strlen(buf), crc);
+ crc ^= 0xFFFFFFFFu;
+ CU_ASSERT(crc == 0);
+
+ /* 1-byte buffer */
+ snprintf(buf, sizeof(buf), "%s", "1");
+ crc = 0xFFFFFFFFu;
+ crc = spdk_crc32c_update(buf, strlen(buf), crc);
+ crc ^= 0xFFFFFFFFu;
+ CU_ASSERT(crc == 0x90F599E3);
+
+ /* 2-byte buffer */
+ snprintf(buf, sizeof(buf), "%s", "12");
+ crc = 0xFFFFFFFFu;
+ crc = spdk_crc32c_update(buf, strlen(buf), crc);
+ crc ^= 0xFFFFFFFFu;
+ CU_ASSERT(crc == 0x7355C460);
+
+ /* 3-byte buffer */
+ snprintf(buf, sizeof(buf), "%s", "123");
+ crc = 0xFFFFFFFFu;
+ crc = spdk_crc32c_update(buf, strlen(buf), crc);
+ crc ^= 0xFFFFFFFFu;
+ CU_ASSERT(crc == 0x107B2FB2);
+
+ /* 4-byte buffer */
+ snprintf(buf, sizeof(buf), "%s", "1234");
+ crc = 0xFFFFFFFFu;
+ crc = spdk_crc32c_update(buf, strlen(buf), crc);
+ crc ^= 0xFFFFFFFFu;
+ CU_ASSERT(crc == 0xF63AF4EE);
+
+ /* 5-byte buffer */
+ snprintf(buf, sizeof(buf), "%s", "12345");
+ crc = 0xFFFFFFFFu;
+ crc = spdk_crc32c_update(buf, strlen(buf), crc);
+ crc ^= 0xFFFFFFFFu;
+ CU_ASSERT(crc == 0x18D12335);
+
+ /* 6-byte buffer */
+ snprintf(buf, sizeof(buf), "%s", "123456");
+ crc = 0xFFFFFFFFu;
+ crc = spdk_crc32c_update(buf, strlen(buf), crc);
+ crc ^= 0xFFFFFFFFu;
+ CU_ASSERT(crc == 0x41357186);
+
+ /* 7-byte buffer */
+ snprintf(buf, sizeof(buf), "%s", "1234567");
+ crc = 0xFFFFFFFFu;
+ crc = spdk_crc32c_update(buf, strlen(buf), crc);
+ crc ^= 0xFFFFFFFFu;
+ CU_ASSERT(crc == 0x124297EA);
+
+ /* Test a buffer of exactly 8 bytes (one block in the main CRC32-C loop). */
+ snprintf(buf, sizeof(buf), "%s", "12345678");
+ crc = 0xFFFFFFFFu;
+ crc = spdk_crc32c_update(buf, strlen(buf), crc);
+ crc ^= 0xFFFFFFFFu;
+ CU_ASSERT(crc == 0x6087809A);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("crc32c", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test_crc32c", test_crc32c) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/util/string.c/.gitignore b/src/spdk/test/unit/lib/util/string.c/.gitignore
new file mode 100644
index 00000000..5d85d4d9
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/string.c/.gitignore
@@ -0,0 +1 @@
+string_ut
diff --git a/src/spdk/test/unit/lib/util/string.c/Makefile b/src/spdk/test/unit/lib/util/string.c/Makefile
new file mode 100644
index 00000000..8ee11909
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/string.c/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.app.mk
+
+TEST_FILE = string_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/util/string.c/string_ut.c b/src/spdk/test/unit/lib/util/string.c/string_ut.c
new file mode 100644
index 00000000..2ca32cbe
--- /dev/null
+++ b/src/spdk/test/unit/lib/util/string.c/string_ut.c
@@ -0,0 +1,237 @@
+/*-
+ * 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_cunit.h"
+
+#include "util/string.c"
+
+static void
+test_parse_ip_addr(void)
+{
+ int rc;
+ char *host;
+ char *port;
+ char ip[255];
+
+ /* IPv4 */
+ snprintf(ip, 255, "%s", "192.168.0.1");
+ rc = spdk_parse_ip_addr(ip, &host, &port);
+ CU_ASSERT_EQUAL(rc, 0);
+ SPDK_CU_ASSERT_FATAL(host != NULL);
+ CU_ASSERT(strcmp(host, "192.168.0.1") == 0);
+ CU_ASSERT_EQUAL(strlen(host), 11);
+ CU_ASSERT_EQUAL(port, NULL);
+
+ /* IPv4 with port */
+ snprintf(ip, 255, "%s", "123.456.789.0:5520");
+ rc = spdk_parse_ip_addr(ip, &host, &port);
+ CU_ASSERT_EQUAL(rc, 0);
+ SPDK_CU_ASSERT_FATAL(host != NULL);
+ CU_ASSERT(strcmp(host, "123.456.789.0") == 0);
+ CU_ASSERT_EQUAL(strlen(host), 13);
+ SPDK_CU_ASSERT_FATAL(port != NULL);
+ CU_ASSERT(strcmp(port, "5520") == 0);
+ CU_ASSERT_EQUAL(strlen(port), 4);
+
+ /* IPv6 */
+ snprintf(ip, 255, "%s", "[2001:db8:85a3:8d3:1319:8a2e:370:7348]");
+ rc = spdk_parse_ip_addr(ip, &host, &port);
+ CU_ASSERT_EQUAL(rc, 0);
+ SPDK_CU_ASSERT_FATAL(host != NULL);
+ CU_ASSERT(strcmp(host, "2001:db8:85a3:8d3:1319:8a2e:370:7348") == 0);
+ CU_ASSERT_EQUAL(strlen(host), 36);
+ CU_ASSERT_EQUAL(port, NULL);
+
+ /* IPv6 with port */
+ snprintf(ip, 255, "%s", "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443");
+ rc = spdk_parse_ip_addr(ip, &host, &port);
+ CU_ASSERT_EQUAL(rc, 0);
+ SPDK_CU_ASSERT_FATAL(host != NULL);
+ CU_ASSERT(strcmp(host, "2001:db8:85a3:8d3:1319:8a2e:370:7348") == 0);
+ CU_ASSERT_EQUAL(strlen(host), 36);
+ SPDK_CU_ASSERT_FATAL(port != NULL);
+ CU_ASSERT(strcmp(port, "443") == 0);
+ CU_ASSERT_EQUAL(strlen(port), 3);
+
+ /* IPv6 dangling colon */
+ snprintf(ip, 255, "%s", "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:");
+ rc = spdk_parse_ip_addr(ip, &host, &port);
+ CU_ASSERT_EQUAL(rc, 0);
+ SPDK_CU_ASSERT_FATAL(host != NULL);
+ CU_ASSERT(strcmp(host, "2001:db8:85a3:8d3:1319:8a2e:370:7348") == 0);
+ CU_ASSERT_EQUAL(strlen(host), 36);
+ CU_ASSERT_EQUAL(port, NULL);
+}
+
+static void
+test_str_chomp(void)
+{
+ char s[1024];
+
+ /* One \n newline */
+ snprintf(s, sizeof(s), "%s", "hello world\n");
+ CU_ASSERT(spdk_str_chomp(s) == 1);
+ CU_ASSERT(strcmp(s, "hello world") == 0);
+
+ /* One \r\n newline */
+ snprintf(s, sizeof(s), "%s", "hello world\r\n");
+ CU_ASSERT(spdk_str_chomp(s) == 2);
+ CU_ASSERT(strcmp(s, "hello world") == 0);
+
+ /* No newlines */
+ snprintf(s, sizeof(s), "%s", "hello world");
+ CU_ASSERT(spdk_str_chomp(s) == 0);
+ CU_ASSERT(strcmp(s, "hello world") == 0);
+
+ /* Two newlines */
+ snprintf(s, sizeof(s), "%s", "hello world\n\n");
+ CU_ASSERT(spdk_str_chomp(s) == 2);
+ CU_ASSERT(strcmp(s, "hello world") == 0);
+
+ /* Empty string */
+ snprintf(s, sizeof(s), "%s", "");
+ CU_ASSERT(spdk_str_chomp(s) == 0);
+ CU_ASSERT(strcmp(s, "") == 0);
+
+ /* One-character string with only \n */
+ snprintf(s, sizeof(s), "%s", "\n");
+ CU_ASSERT(spdk_str_chomp(s) == 1);
+ CU_ASSERT(strcmp(s, "") == 0);
+
+ /* One-character string without a newline */
+ snprintf(s, sizeof(s), "%s", "a");
+ CU_ASSERT(spdk_str_chomp(s) == 0);
+ CU_ASSERT(strcmp(s, "a") == 0);
+}
+
+static void
+test_parse_capacity(void)
+{
+ char str[128];
+ uint64_t cap;
+ int rc;
+ bool has_prefix = true;
+
+ rc = spdk_parse_capacity("472", &cap, &has_prefix);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(cap == 472);
+ CU_ASSERT(has_prefix == false);
+
+ snprintf(str, sizeof(str), "%"PRIu64, UINT64_MAX);
+ rc = spdk_parse_capacity(str, &cap, &has_prefix);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(cap == UINT64_MAX);
+ CU_ASSERT(has_prefix == false);
+
+ rc = spdk_parse_capacity("12k", &cap, &has_prefix);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(cap == 12 * 1024);
+ CU_ASSERT(has_prefix == true);
+
+ rc = spdk_parse_capacity("12K", &cap, &has_prefix);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(cap == 12 * 1024);
+ CU_ASSERT(has_prefix == true);
+
+ rc = spdk_parse_capacity("12KB", &cap, &has_prefix);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(cap == 12 * 1024);
+ CU_ASSERT(has_prefix == true);
+
+ rc = spdk_parse_capacity("100M", &cap, &has_prefix);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(cap == 100 * 1024 * 1024);
+ CU_ASSERT(has_prefix == true);
+
+ rc = spdk_parse_capacity("128M", &cap, &has_prefix);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(cap == 128 * 1024 * 1024);
+ CU_ASSERT(has_prefix == true);
+
+ rc = spdk_parse_capacity("4G", &cap, &has_prefix);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(cap == 4ULL * 1024 * 1024 * 1024);
+ CU_ASSERT(has_prefix == true);
+
+ rc = spdk_parse_capacity("100M 512k", &cap, &has_prefix);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(cap == 100ULL * 1024 * 1024);
+
+ rc = spdk_parse_capacity("12k8K", &cap, &has_prefix);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(cap == 12 * 1024);
+ CU_ASSERT(has_prefix == true);
+
+ /* Non-number */
+ rc = spdk_parse_capacity("G", &cap, &has_prefix);
+ CU_ASSERT(rc != 0);
+
+ rc = spdk_parse_capacity("darsto", &cap, &has_prefix);
+ CU_ASSERT(rc != 0);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("string", NULL, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "test_parse_ip_addr", test_parse_ip_addr) == NULL ||
+ CU_add_test(suite, "test_str_chomp", test_str_chomp) == NULL ||
+ CU_add_test(suite, "test_parse_capacity", test_parse_capacity) == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+
+ CU_basic_run_tests();
+
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/lib/vhost/Makefile b/src/spdk/test/unit/lib/vhost/Makefile
new file mode 100644
index 00000000..0f569f6d
--- /dev/null
+++ b/src/spdk/test/unit/lib/vhost/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 = vhost.c
+
+.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/unit/lib/vhost/test_vhost.c b/src/spdk/test/unit/lib/vhost/test_vhost.c
new file mode 100644
index 00000000..437e1230
--- /dev/null
+++ b/src/spdk/test/unit/lib/vhost/test_vhost.c
@@ -0,0 +1,121 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) Intel Corporation. All rights reserved.
+ * 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 "CUnit/Basic.h"
+#include "spdk_cunit.h"
+#include "spdk_internal/mock.h"
+#include "spdk/thread.h"
+
+#include "unit/lib/json_mock.c"
+
+struct spdk_conf_section {
+ struct spdk_conf_section *next;
+ char *name;
+ int num;
+ struct spdk_conf_item *item;
+};
+
+DEFINE_STUB(spdk_vhost_vq_get_desc, int, (struct spdk_vhost_dev *vdev,
+ struct spdk_vhost_virtqueue *vq, uint16_t req_idx, struct vring_desc **desc,
+ struct vring_desc **desc_table, uint32_t *desc_table_size), 0);
+DEFINE_STUB(spdk_vhost_vring_desc_is_wr, bool, (struct vring_desc *cur_desc), false);
+DEFINE_STUB(spdk_vhost_vring_desc_to_iov, int, (struct spdk_vhost_dev *vdev, struct iovec *iov,
+ uint16_t *iov_index, const struct vring_desc *desc), 0);
+DEFINE_STUB_V(spdk_vhost_vq_used_ring_enqueue, (struct spdk_vhost_dev *vdev,
+ struct spdk_vhost_virtqueue *vq, uint16_t id, uint32_t len));
+DEFINE_STUB(spdk_vhost_vring_desc_get_next, int, (struct vring_desc **desc,
+ struct vring_desc *desc_table, uint32_t desc_table_size), 0);
+DEFINE_STUB(spdk_vhost_vq_avail_ring_get, uint16_t, (struct spdk_vhost_virtqueue *vq,
+ uint16_t *reqs, uint16_t reqs_len), 0);
+DEFINE_STUB(spdk_vhost_vq_used_signal, int, (struct spdk_vhost_dev *vdev,
+ struct spdk_vhost_virtqueue *virtqueue), 0);
+DEFINE_STUB_V(spdk_vhost_dev_used_signal, (struct spdk_vhost_dev *vdev));
+DEFINE_STUB_V(spdk_vhost_dev_mem_register, (struct spdk_vhost_dev *vdev));
+DEFINE_STUB_P(spdk_vhost_dev_find, struct spdk_vhost_dev, (const char *ctrlr_name), {0});
+DEFINE_STUB_P(spdk_conf_first_section, struct spdk_conf_section, (struct spdk_conf *cp), {0});
+DEFINE_STUB(spdk_conf_section_match_prefix, bool, (const struct spdk_conf_section *sp,
+ const char *name_prefix), false);
+DEFINE_STUB_P(spdk_conf_next_section, struct spdk_conf_section, (struct spdk_conf_section *sp), {0});
+DEFINE_STUB_P(spdk_conf_section_get_name, const char, (const struct spdk_conf_section *sp), {0});
+DEFINE_STUB(spdk_conf_section_get_boolval, bool, (struct spdk_conf_section *sp, const char *key,
+ bool default_val), false);
+DEFINE_STUB_P(spdk_conf_section_get_nmval, char, (struct spdk_conf_section *sp, const char *key,
+ int idx1, int idx2), {0});
+DEFINE_STUB_V(spdk_vhost_dev_mem_unregister, (struct spdk_vhost_dev *vdev));
+DEFINE_STUB(spdk_vhost_event_send, int, (struct spdk_vhost_dev *vdev, spdk_vhost_event_fn cb_fn,
+ void *arg, unsigned timeout_sec, const char *errmsg), 0);
+DEFINE_STUB(spdk_env_get_socket_id, uint32_t, (uint32_t core), 0);
+DEFINE_STUB_V(spdk_vhost_dev_backend_event_done, (void *event_ctx, int response));
+DEFINE_STUB_V(spdk_vhost_lock, (void));
+DEFINE_STUB_V(spdk_vhost_unlock, (void));
+DEFINE_STUB(spdk_env_get_current_core, uint32_t, (void), 0);
+DEFINE_STUB_V(spdk_vhost_call_external_event, (const char *ctrlr_name, spdk_vhost_event_fn fn,
+ void *arg));
+DEFINE_STUB(spdk_vhost_vring_desc_has_next, bool, (struct vring_desc *cur_desc), false);
+DEFINE_STUB_VP(spdk_vhost_gpa_to_vva, (struct spdk_vhost_dev *vdev, uint64_t addr, uint64_t len),
+{0});
+DEFINE_STUB(spdk_scsi_dev_get_id, int, (const struct spdk_scsi_dev *dev), {0});
+
+/* This sets spdk_vhost_dev_unregister to either to fail or success */
+DEFINE_STUB(spdk_vhost_dev_unregister_fail, bool, (void), false);
+/* This sets spdk_vhost_dev_register to either to fail or success */
+DEFINE_STUB(spdk_vhost_dev_register_fail, bool, (void), false);
+
+static struct spdk_vhost_dev *g_spdk_vhost_device;
+int
+spdk_vhost_dev_register(struct spdk_vhost_dev *vdev, const char *name, const char *mask_str,
+ const struct spdk_vhost_dev_backend *backend)
+{
+ if (spdk_vhost_dev_register_fail()) {
+ return -1;
+ }
+
+ vdev->backend = backend;
+ g_spdk_vhost_device = vdev;
+ vdev->registered = true;
+ return 0;
+}
+
+int
+spdk_vhost_dev_unregister(struct spdk_vhost_dev *vdev)
+{
+ if (spdk_vhost_dev_unregister_fail()) {
+ return -1;
+ }
+
+ free(vdev->name);
+ g_spdk_vhost_device = NULL;
+ return 0;
+}
diff --git a/src/spdk/test/unit/lib/vhost/vhost.c/.gitignore b/src/spdk/test/unit/lib/vhost/vhost.c/.gitignore
new file mode 100644
index 00000000..16cead8f
--- /dev/null
+++ b/src/spdk/test/unit/lib/vhost/vhost.c/.gitignore
@@ -0,0 +1 @@
+vhost_ut
diff --git a/src/spdk/test/unit/lib/vhost/vhost.c/Makefile b/src/spdk/test/unit/lib/vhost/vhost.c/Makefile
new file mode 100644
index 00000000..3c30f5a8
--- /dev/null
+++ b/src/spdk/test/unit/lib/vhost/vhost.c/Makefile
@@ -0,0 +1,42 @@
+#
+# 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.app.mk
+
+CFLAGS += -I$(SPDK_ROOT_DIR)/lib/vhost/rte_vhost
+CFLAGS += $(ENV_CFLAGS)
+TEST_FILE = vhost_ut.c
+
+include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk
diff --git a/src/spdk/test/unit/lib/vhost/vhost.c/vhost_ut.c b/src/spdk/test/unit/lib/vhost/vhost.c/vhost_ut.c
new file mode 100644
index 00000000..49e879ed
--- /dev/null
+++ b/src/spdk/test/unit/lib/vhost/vhost.c/vhost_ut.c
@@ -0,0 +1,364 @@
+/*-
+ * 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 "CUnit/Basic.h"
+#include "spdk_cunit.h"
+#include "spdk/thread.h"
+#include "spdk_internal/mock.h"
+#include "common/lib/test_env.c"
+#include "unit/lib/json_mock.c"
+
+#include "vhost/vhost.c"
+
+DEFINE_STUB(rte_vhost_driver_unregister, int, (const char *path), 0);
+DEFINE_STUB(spdk_event_allocate, struct spdk_event *,
+ (uint32_t lcore, spdk_event_fn fn, void *arg1, void *arg2), NULL);
+DEFINE_STUB(spdk_mem_register, int, (void *vaddr, size_t len), 0);
+DEFINE_STUB(spdk_mem_unregister, int, (void *vaddr, size_t len), 0);
+
+static struct spdk_cpuset *g_app_core_mask;
+struct spdk_cpuset *spdk_app_get_core_mask(void)
+{
+ if (g_app_core_mask == NULL) {
+ g_app_core_mask = spdk_cpuset_alloc();
+ spdk_cpuset_set_cpu(g_app_core_mask, 0, true);
+ }
+ return g_app_core_mask;
+}
+
+int
+spdk_app_parse_core_mask(const char *mask, struct spdk_cpuset *cpumask)
+{
+ int ret;
+ struct spdk_cpuset *validmask;
+
+ ret = spdk_cpuset_parse(cpumask, mask);
+ if (ret < 0) {
+ return ret;
+ }
+
+ validmask = spdk_app_get_core_mask();
+ spdk_cpuset_and(cpumask, validmask);
+
+ return 0;
+}
+
+DEFINE_STUB(spdk_env_get_first_core, uint32_t, (void), 0);
+DEFINE_STUB(spdk_env_get_next_core, uint32_t, (uint32_t prev_core), 0);
+DEFINE_STUB(spdk_env_get_last_core, uint32_t, (void), 0);
+DEFINE_STUB_V(spdk_app_stop, (int rc));
+DEFINE_STUB_V(spdk_event_call, (struct spdk_event *event));
+DEFINE_STUB(spdk_poller_register, struct spdk_poller *, (spdk_poller_fn fn, void *arg,
+ uint64_t period_microseconds), NULL);
+DEFINE_STUB_V(spdk_poller_unregister, (struct spdk_poller **ppoller));
+DEFINE_STUB(spdk_iommu_mem_unregister, int, (uint64_t addr, uint64_t len), 0);
+DEFINE_STUB(rte_vhost_get_mem_table, int, (int vid, struct rte_vhost_memory **mem), 0);
+DEFINE_STUB(rte_vhost_get_negotiated_features, int, (int vid, uint64_t *features), 0);
+DEFINE_STUB(rte_vhost_get_vhost_vring, int,
+ (int vid, uint16_t vring_idx, struct rte_vhost_vring *vring), 0);
+DEFINE_STUB(rte_vhost_enable_guest_notification, int,
+ (int vid, uint16_t queue_id, int enable), 0);
+DEFINE_STUB(rte_vhost_get_ifname, int, (int vid, char *buf, size_t len), 0);
+DEFINE_STUB(rte_vhost_get_vring_num, uint16_t, (int vid), 0);
+DEFINE_STUB(rte_vhost_driver_start, int, (const char *name), 0);
+DEFINE_STUB(rte_vhost_driver_callback_register, int,
+ (const char *path, struct vhost_device_ops const *const ops), 0);
+DEFINE_STUB(rte_vhost_driver_disable_features, int, (const char *path, uint64_t features), 0);
+DEFINE_STUB(rte_vhost_driver_set_features, int, (const char *path, uint64_t features), 0);
+DEFINE_STUB(rte_vhost_driver_register, int, (const char *path, uint64_t flags), 0);
+DEFINE_STUB_V(rte_vhost_log_used_vring, (int vid, uint16_t vring_idx, uint64_t offset,
+ uint64_t len));
+DEFINE_STUB_V(rte_vhost_log_write, (int vid, uint64_t addr, uint64_t len));
+DEFINE_STUB(spdk_vhost_scsi_controller_construct, int, (void), 0);
+DEFINE_STUB(spdk_vhost_blk_controller_construct, int, (void), 0);
+DEFINE_STUB(spdk_vhost_nvme_admin_passthrough, int, (int vid, void *cmd, void *cqe, void *buf), 0);
+DEFINE_STUB(spdk_vhost_nvme_set_cq_call, int, (int vid, uint16_t qid, int fd), 0);
+DEFINE_STUB(spdk_vhost_nvme_get_cap, int, (int vid, uint64_t *cap), 0);
+DEFINE_STUB(spdk_vhost_nvme_controller_construct, int, (void), 0);
+DEFINE_STUB(rte_vhost_set_vhost_vring_last_idx, int,
+ (int vid, uint16_t vring_idx, uint16_t last_avail_idx, uint16_t last_used_idx), 0);
+DEFINE_STUB(spdk_env_get_current_core, uint32_t, (void), 0);
+
+void *
+spdk_call_unaffinitized(void *cb(void *arg), void *arg)
+{
+ return cb(arg);
+}
+
+static struct spdk_vhost_dev_backend g_vdev_backend;
+
+static int
+test_setup(void)
+{
+ return 0;
+}
+
+static int
+alloc_vdev(struct spdk_vhost_dev **vdev_p, const char *name, const char *cpumask)
+{
+ struct spdk_vhost_dev *vdev = NULL;
+ int rc;
+
+ /* spdk_vhost_dev must be allocated on a cache line boundary. */
+ rc = posix_memalign((void **)&vdev, 64, sizeof(*vdev));
+ CU_ASSERT(rc == 0);
+ SPDK_CU_ASSERT_FATAL(vdev != NULL);
+ memset(vdev, 0, sizeof(*vdev));
+ rc = spdk_vhost_dev_register(vdev, name, cpumask, &g_vdev_backend);
+ if (rc == 0) {
+ *vdev_p = vdev;
+ } else {
+ free(vdev);
+ *vdev_p = NULL;
+ }
+
+ return rc;
+}
+
+static void
+start_vdev(struct spdk_vhost_dev *vdev)
+{
+ vdev->vid = 0;
+ vdev->lcore = 0;
+ vdev->mem = calloc(1, sizeof(*vdev->mem) + 2 * sizeof(struct rte_vhost_mem_region));
+ SPDK_CU_ASSERT_FATAL(vdev->mem != NULL);
+ vdev->mem->nregions = 2;
+ vdev->mem->regions[0].guest_phys_addr = 0;
+ vdev->mem->regions[0].size = 0x400000; /* 4 MB */
+ vdev->mem->regions[0].host_user_addr = 0x1000000;
+ vdev->mem->regions[1].guest_phys_addr = 0x400000;
+ vdev->mem->regions[1].size = 0x400000; /* 4 MB */
+ vdev->mem->regions[1].host_user_addr = 0x2000000;
+}
+
+static void
+stop_vdev(struct spdk_vhost_dev *vdev)
+{
+ free(vdev->mem);
+ vdev->mem = NULL;
+ vdev->vid = -1;
+}
+
+static void
+cleanup_vdev(struct spdk_vhost_dev *vdev)
+{
+ stop_vdev(vdev);
+ spdk_vhost_dev_unregister(vdev);
+ free(vdev);
+}
+
+static void
+desc_to_iov_test(void)
+{
+ struct spdk_vhost_dev *vdev;
+ struct iovec iov[SPDK_VHOST_IOVS_MAX];
+ uint16_t iov_index;
+ struct vring_desc desc;
+ int rc;
+
+ rc = alloc_vdev(&vdev, "vdev_name_0", "0x1");
+ SPDK_CU_ASSERT_FATAL(rc == 0 && vdev);
+ start_vdev(vdev);
+
+ /* Test simple case where iov falls fully within a 2MB page. */
+ desc.addr = 0x110000;
+ desc.len = 0x1000;
+ iov_index = 0;
+ rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(iov_index == 1);
+ CU_ASSERT(iov[0].iov_base == (void *)0x1110000);
+ CU_ASSERT(iov[0].iov_len == 0x1000);
+ /*
+ * Always memset the iov to ensure each test validates data written by its call
+ * to the function under test.
+ */
+ memset(iov, 0, sizeof(iov));
+
+ /* Same test, but ensure it respects the non-zero starting iov_index. */
+ iov_index = SPDK_VHOST_IOVS_MAX - 1;
+ rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(iov_index == SPDK_VHOST_IOVS_MAX);
+ CU_ASSERT(iov[SPDK_VHOST_IOVS_MAX - 1].iov_base == (void *)0x1110000);
+ CU_ASSERT(iov[SPDK_VHOST_IOVS_MAX - 1].iov_len == 0x1000);
+ memset(iov, 0, sizeof(iov));
+
+ /* Test for failure if iov_index already equals SPDK_VHOST_IOVS_MAX. */
+ iov_index = SPDK_VHOST_IOVS_MAX;
+ rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc);
+ CU_ASSERT(rc != 0);
+ memset(iov, 0, sizeof(iov));
+
+ /* Test case where iov spans a 2MB boundary, but does not span a vhost memory region. */
+ desc.addr = 0x1F0000;
+ desc.len = 0x20000;
+ iov_index = 0;
+ rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(iov_index == 1);
+ CU_ASSERT(iov[0].iov_base == (void *)0x11F0000);
+ CU_ASSERT(iov[0].iov_len == 0x20000);
+ memset(iov, 0, sizeof(iov));
+
+ /* Same test, but ensure it respects the non-zero starting iov_index. */
+ iov_index = SPDK_VHOST_IOVS_MAX - 1;
+ rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(iov_index == SPDK_VHOST_IOVS_MAX);
+ CU_ASSERT(iov[SPDK_VHOST_IOVS_MAX - 1].iov_base == (void *)0x11F0000);
+ CU_ASSERT(iov[SPDK_VHOST_IOVS_MAX - 1].iov_len == 0x20000);
+ memset(iov, 0, sizeof(iov));
+
+ /* Test case where iov spans a vhost memory region. */
+ desc.addr = 0x3F0000;
+ desc.len = 0x20000;
+ iov_index = 0;
+ rc = spdk_vhost_vring_desc_to_iov(vdev, iov, &iov_index, &desc);
+ CU_ASSERT(rc == 0);
+ CU_ASSERT(iov_index == 2);
+ CU_ASSERT(iov[0].iov_base == (void *)0x13F0000);
+ CU_ASSERT(iov[0].iov_len == 0x10000);
+ CU_ASSERT(iov[1].iov_base == (void *)0x2000000);
+ CU_ASSERT(iov[1].iov_len == 0x10000);
+ memset(iov, 0, sizeof(iov));
+
+ cleanup_vdev(vdev);
+
+ CU_ASSERT(true);
+}
+
+static void
+create_controller_test(void)
+{
+ struct spdk_vhost_dev *vdev, *vdev2;
+ int ret;
+ char long_name[PATH_MAX];
+
+ /* NOTE: spdk_app_get_core_mask stub always sets coremask 0x01 */
+
+ /* Create device with no name */
+ ret = alloc_vdev(&vdev, NULL, "0x1");
+ CU_ASSERT(ret != 0);
+
+ /* Create device with incorrect cpumask */
+ ret = alloc_vdev(&vdev, "vdev_name_0", "0x2");
+ CU_ASSERT(ret != 0);
+
+ /* Create device with too long name and path */
+ memset(long_name, 'x', sizeof(long_name));
+ long_name[PATH_MAX - 1] = 0;
+ snprintf(dev_dirname, sizeof(dev_dirname), "some_path/");
+ ret = alloc_vdev(&vdev, long_name, "0x1");
+ CU_ASSERT(ret != 0);
+ dev_dirname[0] = 0;
+
+ /* Create device when device name is already taken */
+ ret = alloc_vdev(&vdev, "vdev_name_0", "0x1");
+ SPDK_CU_ASSERT_FATAL(ret == 0 && vdev);
+ ret = alloc_vdev(&vdev2, "vdev_name_0", "0x1");
+ CU_ASSERT(ret != 0);
+ cleanup_vdev(vdev);
+}
+
+static void
+dev_find_by_vid_test(void)
+{
+ struct spdk_vhost_dev *vdev, *tmp;
+ int rc;
+
+ rc = alloc_vdev(&vdev, "vdev_name_0", "0x1");
+ SPDK_CU_ASSERT_FATAL(rc == 0 && vdev);
+
+ tmp = spdk_vhost_dev_find_by_vid(vdev->vid);
+ CU_ASSERT(tmp == vdev);
+
+ /* Search for a device with incorrect vid */
+ tmp = spdk_vhost_dev_find_by_vid(vdev->vid + 0xFF);
+ CU_ASSERT(tmp == NULL);
+
+ cleanup_vdev(vdev);
+}
+
+static void
+remove_controller_test(void)
+{
+ struct spdk_vhost_dev *vdev;
+ int ret;
+
+ ret = alloc_vdev(&vdev, "vdev_name_0", "0x1");
+ SPDK_CU_ASSERT_FATAL(ret == 0 && vdev);
+
+ /* Remove device when controller is in use */
+ start_vdev(vdev);
+ ret = spdk_vhost_dev_unregister(vdev);
+ CU_ASSERT(ret != 0);
+
+ cleanup_vdev(vdev);
+}
+
+int
+main(int argc, char **argv)
+{
+ CU_pSuite suite = NULL;
+ unsigned int num_failures;
+
+ if (CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("vhost_suite", test_setup, NULL);
+ if (suite == NULL) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ if (
+ CU_add_test(suite, "desc_to_iov", desc_to_iov_test) == NULL ||
+ CU_add_test(suite, "create_controller", create_controller_test) == NULL ||
+ CU_add_test(suite, "dev_find_by_vid", dev_find_by_vid_test) == NULL ||
+ CU_add_test(suite, "remove_controller", remove_controller_test) == NULL
+ ) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_failures = CU_get_number_of_failures();
+ CU_cleanup_registry();
+
+ return num_failures;
+}
diff --git a/src/spdk/test/unit/unittest.sh b/src/spdk/test/unit/unittest.sh
new file mode 100755
index 00000000..6e79b381
--- /dev/null
+++ b/src/spdk/test/unit/unittest.sh
@@ -0,0 +1,173 @@
+#!/usr/bin/env bash
+#
+# Environment variables:
+# $valgrind Specify the valgrind command line, if not
+# then a default command line is used
+
+set -xe
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $(dirname $0)/../..)
+
+cd "$rootdir"
+
+
+# if ASAN is enabled, use it. If not use valgrind if installed but allow
+# the env variable to override the default shown below.
+if [ -z ${valgrind+x} ]; then
+ if grep -q '#undef SPDK_CONFIG_ASAN' $rootdir/include/spdk/config.h && hash valgrind; then
+ valgrind='valgrind --leak-check=full --error-exitcode=2'
+ else
+ valgrind=''
+ fi
+fi
+
+# setup local unit test coverage if cov is available
+if hash lcov && grep -q '#define SPDK_CONFIG_COVERAGE 1' $rootdir/include/spdk/config.h; then
+ cov_avail="yes"
+else
+ cov_avail="no"
+fi
+if [ "$cov_avail" = "yes" ]; then
+ # set unit test output dir if not specified in env var
+ if [ -z ${UT_COVERAGE+x} ]; then
+ UT_COVERAGE="ut_coverage"
+ fi
+ mkdir -p $UT_COVERAGE
+ export LCOV_OPTS="
+ --rc lcov_branch_coverage=1
+ --rc lcov_function_coverage=1
+ --rc genhtml_branch_coverage=1
+ --rc genhtml_function_coverage=1
+ --rc genhtml_legend=1
+ --rc geninfo_all_blocks=1
+ "
+ export LCOV="lcov $LCOV_OPTS --no-external"
+ # zero out coverage data
+ $LCOV -q -c -i -d . -t "Baseline" -o $UT_COVERAGE/ut_cov_base.info
+fi
+$valgrind $testdir/include/spdk/histogram_data.h/histogram_ut
+
+$valgrind $testdir/lib/bdev/bdev.c/bdev_ut
+$valgrind $testdir/lib/bdev/bdev_raid.c/bdev_raid_ut
+$valgrind $testdir/lib/bdev/part.c/part_ut
+$valgrind $testdir/lib/bdev/scsi_nvme.c/scsi_nvme_ut
+$valgrind $testdir/lib/bdev/gpt/gpt.c/gpt_ut
+$valgrind $testdir/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut
+
+if grep -q '#define SPDK_CONFIG_CRYPTO 1' $rootdir/include/spdk/config.h; then
+ $valgrind $testdir/lib/bdev/crypto.c/crypto_ut
+fi
+
+if grep -q '#define SPDK_CONFIG_PMDK 1' $rootdir/include/spdk/config.h; then
+ $valgrind $testdir/lib/bdev/pmem/bdev_pmem_ut
+fi
+
+$valgrind $testdir/lib/bdev/mt/bdev.c/bdev_ut
+
+$valgrind $testdir/lib/blob/blob.c/blob_ut
+$valgrind $testdir/lib/blobfs/tree.c/tree_ut
+
+$valgrind $testdir/lib/blobfs/blobfs_async_ut/blobfs_async_ut
+# blobfs_sync_ut hangs when run under valgrind, so don't use $valgrind
+$testdir/lib/blobfs/blobfs_sync_ut/blobfs_sync_ut
+
+$valgrind $testdir/lib/event/subsystem.c/subsystem_ut
+$valgrind $testdir/lib/event/app.c/app_ut
+
+$valgrind $testdir/lib/sock/sock.c/sock_ut
+
+$valgrind $testdir/lib/nvme/nvme.c/nvme_ut
+$valgrind $testdir/lib/nvme/nvme_ctrlr.c/nvme_ctrlr_ut
+$valgrind $testdir/lib/nvme/nvme_ctrlr_cmd.c/nvme_ctrlr_cmd_ut
+$valgrind $testdir/lib/nvme/nvme_ctrlr_ocssd_cmd.c/nvme_ctrlr_ocssd_cmd_ut
+$valgrind $testdir/lib/nvme/nvme_ns.c/nvme_ns_ut
+$valgrind $testdir/lib/nvme/nvme_ns_cmd.c/nvme_ns_cmd_ut
+$valgrind $testdir/lib/nvme/nvme_ns_ocssd_cmd.c/nvme_ns_ocssd_cmd_ut
+$valgrind $testdir/lib/nvme/nvme_qpair.c/nvme_qpair_ut
+$valgrind $testdir/lib/nvme/nvme_pcie.c/nvme_pcie_ut
+$valgrind $testdir/lib/nvme/nvme_quirks.c/nvme_quirks_ut
+if grep -q '#define SPDK_CONFIG_RDMA 1' $rootdir/config.h; then
+ $valgrind $testdir/lib/nvme/nvme_rdma.c/nvme_rdma_ut
+fi
+
+$valgrind $testdir/lib/ioat/ioat.c/ioat_ut
+
+$valgrind $testdir/lib/json/json_parse.c/json_parse_ut
+$valgrind $testdir/lib/json/json_util.c/json_util_ut
+$valgrind $testdir/lib/json/json_write.c/json_write_ut
+
+$valgrind $testdir/lib/jsonrpc/jsonrpc_server.c/jsonrpc_server_ut
+
+$valgrind $testdir/lib/log/log.c/log_ut
+
+$valgrind $testdir/lib/nvmf/ctrlr.c/ctrlr_ut
+$valgrind $testdir/lib/nvmf/ctrlr_bdev.c/ctrlr_bdev_ut
+$valgrind $testdir/lib/nvmf/ctrlr_discovery.c/ctrlr_discovery_ut
+$valgrind $testdir/lib/nvmf/request.c/request_ut
+$valgrind $testdir/lib/nvmf/subsystem.c/subsystem_ut
+
+$valgrind $testdir/lib/scsi/dev.c/dev_ut
+$valgrind $testdir/lib/scsi/lun.c/lun_ut
+$valgrind $testdir/lib/scsi/scsi.c/scsi_ut
+$valgrind $testdir/lib/scsi/scsi_bdev.c/scsi_bdev_ut
+
+$valgrind $testdir/lib/lvol/lvol.c/lvol_ut
+
+$valgrind $testdir/lib/iscsi/conn.c/conn_ut
+$valgrind $testdir/lib/iscsi/param.c/param_ut
+$valgrind $testdir/lib/iscsi/tgt_node.c/tgt_node_ut $testdir/lib/iscsi/tgt_node.c/tgt_node.conf
+$valgrind $testdir/lib/iscsi/iscsi.c/iscsi_ut
+$valgrind $testdir/lib/iscsi/init_grp.c/init_grp_ut $testdir/lib/iscsi/init_grp.c/init_grp.conf
+$valgrind $testdir/lib/iscsi/portal_grp.c/portal_grp_ut $testdir/lib/iscsi/portal_grp.c/portal_grp.conf
+
+$valgrind $testdir/lib/thread/thread.c/thread_ut
+
+$valgrind $testdir/lib/util/base64.c/base64_ut
+$valgrind $testdir/lib/util/bit_array.c/bit_array_ut
+$valgrind $testdir/lib/util/crc16.c/crc16_ut
+$valgrind $testdir/lib/util/crc32_ieee.c/crc32_ieee_ut
+$valgrind $testdir/lib/util/crc32c.c/crc32c_ut
+$valgrind $testdir/lib/util/string.c/string_ut
+
+if [ $(uname -s) = Linux ]; then
+$valgrind $testdir/lib/vhost/vhost.c/vhost_ut
+fi
+
+# local unit test coverage
+if [ "$cov_avail" = "yes" ]; then
+ $LCOV -q -d . -c -t "$(hostname)" -o $UT_COVERAGE/ut_cov_test.info
+ $LCOV -q -a $UT_COVERAGE/ut_cov_base.info -a $UT_COVERAGE/ut_cov_test.info -o $UT_COVERAGE/ut_cov_total.info
+ $LCOV -q -a $UT_COVERAGE/ut_cov_total.info -o $UT_COVERAGE/ut_cov_unit.info
+ $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/app/*" -o $UT_COVERAGE/ut_cov_unit.info
+ $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/dpdk/*" -o $UT_COVERAGE/ut_cov_unit.info
+ $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/examples/*" -o $UT_COVERAGE/ut_cov_unit.info
+ $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/include/*" -o $UT_COVERAGE/ut_cov_unit.info
+ $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/lib/vhost/rte_vhost/*" -o $UT_COVERAGE/ut_cov_unit.info
+ $LCOV -q -r $UT_COVERAGE/ut_cov_unit.info "$rootdir/test/*" -o $UT_COVERAGE/ut_cov_unit.info
+ rm -f $UT_COVERAGE/ut_cov_base.info $UT_COVERAGE/ut_cov_test.info
+ genhtml $UT_COVERAGE/ut_cov_unit.info --output-directory $UT_COVERAGE
+ # git -C option not used for compatibility reasons
+ cd $rootdir
+ git clean -f "*.gcda"
+ cd -
+fi
+
+set +x
+
+echo
+echo
+echo "====================="
+echo "All unit tests passed"
+echo "====================="
+if [ "$cov_avail" = "yes" ]; then
+ echo "Note: coverage report is here: $rootdir/$UT_COVERAGE"
+else
+ echo "WARN: lcov not installed or SPDK built without coverage!"
+fi
+if grep -q '#undef SPDK_CONFIG_ASAN' $rootdir/include/spdk/config.h && [ "$valgrind" = "" ]; then
+ echo "WARN: neither valgrind nor ASAN is enabled!"
+fi
+
+echo
+echo
diff --git a/src/spdk/test/vhost/common/autotest.config b/src/spdk/test/vhost/common/autotest.config
new file mode 100644
index 00000000..96b0d08b
--- /dev/null
+++ b/src/spdk/test/vhost/common/autotest.config
@@ -0,0 +1,38 @@
+vhost_0_reactor_mask="[0]"
+vhost_0_master_core=0
+
+VM_0_qemu_mask=1-2
+VM_0_qemu_numa_node=0
+
+VM_1_qemu_mask=3-4
+VM_1_qemu_numa_node=0
+
+VM_2_qemu_mask=5-6
+VM_2_qemu_numa_node=0
+
+VM_3_qemu_mask=7-8
+VM_3_qemu_numa_node=0
+
+VM_4_qemu_mask=9-10
+VM_4_qemu_numa_node=0
+
+VM_5_qemu_mask=11-12
+VM_5_qemu_numa_node=0
+
+VM_6_qemu_mask=13-14
+VM_6_qemu_numa_node=1
+
+VM_7_qemu_mask=15-16
+VM_7_qemu_numa_node=1
+
+VM_8_qemu_mask=17-18
+VM_8_qemu_numa_node=1
+
+VM_9_qemu_mask=19-20
+VM_9_qemu_numa_node=1
+
+VM_10_qemu_mask=21-22
+VM_10_qemu_numa_node=1
+
+VM_11_qemu_mask=23-24
+VM_11_qemu_numa_node=1
diff --git a/src/spdk/test/vhost/common/common.sh b/src/spdk/test/vhost/common/common.sh
new file mode 100644
index 00000000..19c4be62
--- /dev/null
+++ b/src/spdk/test/vhost/common/common.sh
@@ -0,0 +1,1109 @@
+set -e
+
+: ${SPDK_VHOST_VERBOSE=false}
+: ${QEMU_PREFIX="/usr/local/qemu/spdk-2.12"}
+
+BASE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]}))
+
+# Default running dir -> spdk/..
+[[ -z "$TEST_DIR" ]] && TEST_DIR=$BASE_DIR/../../../../
+
+TEST_DIR="$(mkdir -p $TEST_DIR && cd $TEST_DIR && echo $PWD)"
+SPDK_BUILD_DIR=$BASE_DIR/../../../
+
+SPDK_VHOST_SCSI_TEST_DIR=$TEST_DIR/vhost
+
+function message()
+{
+ if ! $SPDK_VHOST_VERBOSE; then
+ local verbose_out=""
+ elif [[ ${FUNCNAME[2]} == "source" ]]; then
+ local verbose_out=" (file $(basename ${BASH_SOURCE[1]}):${BASH_LINENO[1]})"
+ else
+ local verbose_out=" (function ${FUNCNAME[2]}:${BASH_LINENO[1]})"
+ fi
+
+ local msg_type="$1"
+ shift
+ echo -e "${msg_type}${verbose_out}: $@"
+}
+
+function fail()
+{
+ echo "===========" >&2
+ message "FAIL" "$@" >&2
+ echo "===========" >&2
+ exit 1
+}
+
+function error()
+{
+ echo "===========" >&2
+ message "ERROR" "$@" >&2
+ echo "===========" >&2
+ # Don't 'return 1' since the stack trace will be incomplete (why?) missing upper command.
+ false
+}
+
+function warning()
+{
+ message "WARN" "$@" >&2
+}
+
+function notice()
+{
+ message "INFO" "$@"
+}
+
+
+# SSH key file
+: ${SPDK_VHOST_SSH_KEY_FILE="$(readlink -e $HOME/.ssh/spdk_vhost_id_rsa)"}
+if [[ ! -r "$SPDK_VHOST_SSH_KEY_FILE" ]]; then
+ error "Could not find SSH key file $SPDK_VHOST_SSH_KEY_FILE"
+ exit 1
+fi
+echo "Using SSH key file $SPDK_VHOST_SSH_KEY_FILE"
+
+VM_BASE_DIR="$TEST_DIR/vms"
+
+
+mkdir -p $TEST_DIR
+
+#
+# Source config describing QEMU and VHOST cores and NUMA
+#
+source $(readlink -f $(dirname ${BASH_SOURCE[0]}))/autotest.config
+
+# Trace flag is optional, if it wasn't set earlier - disable it after sourcing
+# autotest_common.sh
+if [[ $- =~ x ]]; then
+ source $SPDK_BUILD_DIR/test/common/autotest_common.sh
+else
+ source $SPDK_BUILD_DIR/test/common/autotest_common.sh
+ set +x
+fi
+
+function get_vhost_dir()
+{
+ if [[ ! -z "$1" ]]; then
+ assert_number "$1"
+ local vhost_num=$1
+ else
+ local vhost_num=0
+ fi
+
+ echo "$SPDK_VHOST_SCSI_TEST_DIR${vhost_num}"
+}
+
+function spdk_vhost_list_all()
+{
+ shopt -s nullglob
+ local vhost_list="$(echo $SPDK_VHOST_SCSI_TEST_DIR[0-9]*)"
+ shopt -u nullglob
+
+ if [[ ! -z "$vhost_list" ]]; then
+ vhost_list="$(basename --multiple $vhost_list)"
+ echo "${vhost_list//vhost/}"
+ fi
+}
+
+function spdk_vhost_run()
+{
+ local param
+ local vhost_num=0
+ local vhost_conf_path=""
+ local memory=1024
+
+ for param in "$@"; do
+ case $param in
+ --vhost-num=*)
+ vhost_num="${param#*=}"
+ assert_number "$vhost_num"
+ ;;
+ --conf-path=*) local vhost_conf_path="${param#*=}" ;;
+ --json-path=*) local vhost_json_path="${param#*=}" ;;
+ --memory=*) local memory=${param#*=} ;;
+ --no-pci*) local no_pci="-u" ;;
+ *)
+ error "Invalid parameter '$param'"
+ return 1
+ ;;
+ esac
+ done
+
+ local vhost_dir="$(get_vhost_dir $vhost_num)"
+ local vhost_app="$SPDK_BUILD_DIR/app/vhost/vhost"
+ local vhost_log_file="$vhost_dir/vhost.log"
+ local vhost_pid_file="$vhost_dir/vhost.pid"
+ local vhost_socket="$vhost_dir/usvhost"
+ local vhost_conf_template="$vhost_conf_path/vhost.conf.in"
+ local vhost_conf_file="$vhost_conf_path/vhost.conf"
+ notice "starting vhost app in background"
+ [[ -r "$vhost_pid_file" ]] && spdk_vhost_kill $vhost_num
+ [[ -d $vhost_dir ]] && rm -f $vhost_dir/*
+ mkdir -p $vhost_dir
+
+ if [[ ! -x $vhost_app ]]; then
+ error "application not found: $vhost_app"
+ return 1
+ fi
+
+ local reactor_mask="vhost_${vhost_num}_reactor_mask"
+ reactor_mask="${!reactor_mask}"
+
+ local master_core="vhost_${vhost_num}_master_core"
+ master_core="${!master_core}"
+
+ if [[ -z "$reactor_mask" ]] || [[ -z "$master_core" ]]; then
+ error "Parameters vhost_${vhost_num}_reactor_mask or vhost_${vhost_num}_master_core not found in autotest.config file"
+ return 1
+ fi
+
+ local cmd="$vhost_app -m $reactor_mask -p $master_core -s $memory -r $vhost_dir/rpc.sock $no_pci"
+ if [[ -n "$vhost_conf_path" ]]; then
+ cp $vhost_conf_template $vhost_conf_file
+ $SPDK_BUILD_DIR/scripts/gen_nvme.sh >> $vhost_conf_file
+ cmd="$vhost_app -m $reactor_mask -p $master_core -c $vhost_conf_file -s $memory -r $vhost_dir/rpc.sock $no_pci"
+ fi
+
+ notice "Loging to: $vhost_log_file"
+ notice "Socket: $vhost_socket"
+ notice "Command: $cmd"
+
+ timing_enter vhost_start
+ cd $vhost_dir; $cmd &
+ vhost_pid=$!
+ echo $vhost_pid > $vhost_pid_file
+
+ notice "waiting for app to run..."
+ waitforlisten "$vhost_pid" "$vhost_dir/rpc.sock"
+ #do not generate nvmes if pci access is disabled
+ if [[ -z "$vhost_conf_path" ]] && [[ -z "$no_pci" ]]; then
+ $SPDK_BUILD_DIR/scripts/gen_nvme.sh "--json" | $SPDK_BUILD_DIR/scripts/rpc.py\
+ -s $vhost_dir/rpc.sock load_subsystem_config
+ fi
+
+ if [[ -n "$vhost_json_path" ]]; then
+ $SPDK_BUILD_DIR/scripts/rpc.py -s $vhost_dir/rpc.sock load_config < "$vhost_json_path/conf.json"
+ fi
+
+ notice "vhost started - pid=$vhost_pid"
+ timing_exit vhost_start
+
+ rm -f $vhost_conf_file
+}
+
+function spdk_vhost_kill()
+{
+ local rc=0
+ local vhost_num=0
+ if [[ ! -z "$1" ]]; then
+ vhost_num=$1
+ assert_number "$vhost_num"
+ fi
+
+ local vhost_pid_file="$(get_vhost_dir $vhost_num)/vhost.pid"
+
+ if [[ ! -r $vhost_pid_file ]]; then
+ warning "no vhost pid file found"
+ return 0
+ fi
+
+ timing_enter vhost_kill
+ local vhost_pid="$(cat $vhost_pid_file)"
+ notice "killing vhost (PID $vhost_pid) app"
+
+ if /bin/kill -INT $vhost_pid >/dev/null; then
+ notice "sent SIGINT to vhost app - waiting 60 seconds to exit"
+ for ((i=0; i<60; i++)); do
+ if /bin/kill -0 $vhost_pid; then
+ echo "."
+ sleep 1
+ else
+ break
+ fi
+ done
+ if /bin/kill -0 $vhost_pid; then
+ error "ERROR: vhost was NOT killed - sending SIGABRT"
+ /bin/kill -ABRT $vhost_pid
+ rm $vhost_pid_file
+ rc=1
+ else
+ while kill -0 $vhost_pid; do
+ echo "."
+ done
+ fi
+ elif /bin/kill -0 $vhost_pid; then
+ error "vhost NOT killed - you need to kill it manually"
+ rc=1
+ else
+ notice "vhost was no running"
+ fi
+
+ timing_exit vhost_kill
+ if [[ $rc == 0 ]]; then
+ rm $vhost_pid_file
+ fi
+
+ return $rc
+}
+
+###
+# Mgmt functions
+###
+
+function assert_number()
+{
+ [[ "$1" =~ [0-9]+ ]] && return 0
+
+ error "Invalid or missing paramter: need number but got '$1'"
+ return 1;
+}
+
+# Helper to validate VM number
+# param $1 VM number
+#
+function vm_num_is_valid()
+{
+ [[ "$1" =~ ^[0-9]+$ ]] && return 0
+
+ error "Invalid or missing paramter: vm number '$1'"
+ return 1;
+}
+
+
+# Print network socket for given VM number
+# param $1 virtual machine number
+#
+function vm_ssh_socket()
+{
+ vm_num_is_valid $1 || return 1
+ local vm_dir="$VM_BASE_DIR/$1"
+
+ cat $vm_dir/ssh_socket
+}
+
+function vm_fio_socket()
+{
+ vm_num_is_valid $1 || return 1
+ local vm_dir="$VM_BASE_DIR/$1"
+
+ cat $vm_dir/fio_socket
+}
+
+function vm_create_ssh_config()
+{
+ local ssh_config="$VM_BASE_DIR/ssh_config"
+ if [[ ! -f $ssh_config ]]; then
+ (
+ echo "Host *"
+ echo " ControlPersist=10m"
+ echo " ConnectTimeout=1"
+ echo " Compression=no"
+ echo " ControlMaster=auto"
+ echo " UserKnownHostsFile=/dev/null"
+ echo " StrictHostKeyChecking=no"
+ echo " User root"
+ echo " ControlPath=/tmp/%r@%h:%p.ssh"
+ echo ""
+ ) > $ssh_config
+ # Control path created at /tmp because of live migration test case 3.
+ # In case of using sshfs share for the test - control path cannot be
+ # on share because remote server will fail on ssh commands.
+ fi
+}
+
+# Execute ssh command on given VM
+# param $1 virtual machine number
+#
+function vm_ssh()
+{
+ vm_num_is_valid $1 || return 1
+ vm_create_ssh_config
+ local ssh_config="$VM_BASE_DIR/ssh_config"
+
+ local ssh_cmd="ssh -i $SPDK_VHOST_SSH_KEY_FILE -F $ssh_config \
+ -p $(vm_ssh_socket $1) $VM_SSH_OPTIONS 127.0.0.1"
+
+ shift
+ $ssh_cmd "$@"
+}
+
+# Execute scp command on given VM
+# param $1 virtual machine number
+#
+function vm_scp()
+{
+ vm_num_is_valid $1 || return 1
+ vm_create_ssh_config
+ local ssh_config="$VM_BASE_DIR/ssh_config"
+
+ local scp_cmd="scp -i $SPDK_VHOST_SSH_KEY_FILE -F $ssh_config \
+ -P $(vm_ssh_socket $1) "
+
+ shift
+ $scp_cmd "$@"
+}
+
+
+# check if specified VM is running
+# param $1 VM num
+function vm_is_running()
+{
+ vm_num_is_valid $1 || return 1
+ local vm_dir="$VM_BASE_DIR/$1"
+
+ if [[ ! -r $vm_dir/qemu.pid ]]; then
+ return 1
+ fi
+
+ local vm_pid="$(cat $vm_dir/qemu.pid)"
+
+ if /bin/kill -0 $vm_pid; then
+ return 0
+ else
+ if [[ $EUID -ne 0 ]]; then
+ warning "not root - assuming VM running since can't be checked"
+ return 0
+ fi
+
+ # not running - remove pid file
+ rm $vm_dir/qemu.pid
+ return 1
+ fi
+}
+
+# check if specified VM is running
+# param $1 VM num
+function vm_os_booted()
+{
+ vm_num_is_valid $1 || return 1
+ local vm_dir="$VM_BASE_DIR/$1"
+
+ if [[ ! -r $vm_dir/qemu.pid ]]; then
+ error "VM $1 is not running"
+ return 1
+ fi
+
+ if ! VM_SSH_OPTIONS="-o ControlMaster=no" vm_ssh $1 "true" 2>/dev/null; then
+ # Shutdown existing master. Ignore errors as it might not exist.
+ VM_SSH_OPTIONS="-O exit" vm_ssh $1 "true" 2>/dev/null
+ return 1
+ fi
+
+ return 0
+}
+
+
+# Shutdown given VM
+# param $1 virtual machine number
+# return non-zero in case of error.
+function vm_shutdown()
+{
+ vm_num_is_valid $1 || return 1
+ local vm_dir="$VM_BASE_DIR/$1"
+ if [[ ! -d "$vm_dir" ]]; then
+ error "VM$1 ($vm_dir) not exist - setup it first"
+ return 1
+ fi
+
+ if ! vm_is_running $1; then
+ notice "VM$1 ($vm_dir) is not running"
+ return 0
+ fi
+
+ # Temporarily disabling exit flag for next ssh command, since it will
+ # "fail" due to shutdown
+ notice "Shutting down virtual machine $vm_dir"
+ set +e
+ vm_ssh $1 "nohup sh -c 'shutdown -h -P now'" || true
+ notice "VM$1 is shutting down - wait a while to complete"
+ set -e
+}
+
+# Kill given VM
+# param $1 virtual machine number
+#
+function vm_kill()
+{
+ vm_num_is_valid $1 || return 1
+ local vm_dir="$VM_BASE_DIR/$1"
+
+ if [[ ! -r $vm_dir/qemu.pid ]]; then
+ return 0
+ fi
+
+ local vm_pid="$(cat $vm_dir/qemu.pid)"
+
+ notice "Killing virtual machine $vm_dir (pid=$vm_pid)"
+ # First kill should fail, second one must fail
+ if /bin/kill $vm_pid; then
+ notice "process $vm_pid killed"
+ rm $vm_dir/qemu.pid
+ elif vm_is_running $1; then
+ error "Process $vm_pid NOT killed"
+ return 1
+ fi
+}
+
+# List all VM numbers in VM_BASE_DIR
+#
+function vm_list_all()
+{
+ local vms="$(shopt -s nullglob; echo $VM_BASE_DIR/[0-9]*)"
+ if [[ ! -z "$vms" ]]; then
+ basename --multiple $vms
+ fi
+}
+
+# Kills all VM in $VM_BASE_DIR
+#
+function vm_kill_all()
+{
+ local vm
+ for vm in $(vm_list_all); do
+ vm_kill $vm
+ done
+}
+
+# Shutdown all VM in $VM_BASE_DIR
+#
+function vm_shutdown_all()
+{
+ local shell_restore_x="$( [[ "$-" =~ x ]] && echo 'set -x' )"
+ # XXX: temporally disable to debug shutdown issue
+ # set +x
+
+ local vms=$(vm_list_all)
+ local vm
+
+ for vm in $vms; do
+ vm_shutdown $vm
+ done
+
+ notice "Waiting for VMs to shutdown..."
+ local timeo=30
+ while [[ $timeo -gt 0 ]]; do
+ local all_vms_down=1
+ for vm in $vms; do
+ if vm_is_running $vm; then
+ all_vms_down=0
+ break
+ fi
+ done
+
+ if [[ $all_vms_down == 1 ]]; then
+ notice "All VMs successfully shut down"
+ $shell_restore_x
+ return 0
+ fi
+
+ ((timeo-=1))
+ sleep 1
+ done
+
+ $shell_restore_x
+ error "Timeout waiting for some VMs to shutdown"
+ return 1
+}
+
+function vm_setup()
+{
+ local shell_restore_x="$( [[ "$-" =~ x ]] && echo 'set -x' )"
+ local OPTIND optchar vm_num
+
+ local os=""
+ local os_mode=""
+ local qemu_args=""
+ local disk_type_g=NOT_DEFINED
+ local read_only="false"
+ local disks=""
+ local raw_cache=""
+ local vm_incoming=""
+ local vm_migrate_to=""
+ local force_vm=""
+ local guest_memory=1024
+ local queue_number=""
+ local vhost_dir="$(get_vhost_dir)"
+ while getopts ':-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ os=*) local os="${OPTARG#*=}" ;;
+ os-mode=*) local os_mode="${OPTARG#*=}" ;;
+ qemu-args=*) local qemu_args="${qemu_args} ${OPTARG#*=}" ;;
+ disk-type=*) local disk_type_g="${OPTARG#*=}" ;;
+ read-only=*) local read_only="${OPTARG#*=}" ;;
+ disks=*) local disks="${OPTARG#*=}" ;;
+ raw-cache=*) local raw_cache=",cache${OPTARG#*=}" ;;
+ force=*) local force_vm=${OPTARG#*=} ;;
+ memory=*) local guest_memory=${OPTARG#*=} ;;
+ queue_num=*) local queue_number=${OPTARG#*=} ;;
+ incoming=*) local vm_incoming="${OPTARG#*=}" ;;
+ migrate-to=*) local vm_migrate_to="${OPTARG#*=}" ;;
+ vhost-num=*) local vhost_dir="$(get_vhost_dir ${OPTARG#*=})" ;;
+ spdk-boot=*) local boot_from="${OPTARG#*=}" ;;
+ *)
+ error "unknown argument $OPTARG"
+ return 1
+ esac
+ ;;
+ *)
+ error "vm_create Unknown param $OPTARG"
+ return 1
+ ;;
+ esac
+ done
+
+ # Find next directory we can use
+ if [[ ! -z $force_vm ]]; then
+ vm_num=$force_vm
+
+ vm_num_is_valid $vm_num || return 1
+ local vm_dir="$VM_BASE_DIR/$vm_num"
+ [[ -d $vm_dir ]] && warning "removing existing VM in '$vm_dir'"
+ else
+ local vm_dir=""
+
+ set +x
+ for (( i=0; i<=256; i++)); do
+ local vm_dir="$VM_BASE_DIR/$i"
+ [[ ! -d $vm_dir ]] && break
+ done
+ $shell_restore_x
+
+ vm_num=$i
+ fi
+
+ if [[ $i -eq 256 ]]; then
+ error "no free VM found. do some cleanup (256 VMs created, are you insane?)"
+ return 1
+ fi
+
+ if [[ ! -z "$vm_migrate_to" && ! -z "$vm_incoming" ]]; then
+ error "'--incoming' and '--migrate-to' cannot be used together"
+ return 1
+ elif [[ ! -z "$vm_incoming" ]]; then
+ if [[ ! -z "$os_mode" || ! -z "$os_img" ]]; then
+ error "'--incoming' can't be used together with '--os' nor '--os-mode'"
+ return 1
+ fi
+
+ os_mode="original"
+ os="$VM_BASE_DIR/$vm_incoming/os.qcow2"
+ elif [[ ! -z "$vm_migrate_to" ]]; then
+ [[ "$os_mode" != "backing" ]] && warning "Using 'backing' mode for OS since '--migrate-to' is used"
+ os_mode=backing
+ fi
+
+ notice "Creating new VM in $vm_dir"
+ mkdir -p $vm_dir
+
+ if [[ "$os_mode" == "backing" ]]; then
+ notice "Creating backing file for OS image file: $os"
+ if ! $QEMU_PREFIX/bin/qemu-img create -f qcow2 -b $os $vm_dir/os.qcow2; then
+ error "Failed to create OS backing file in '$vm_dir/os.qcow2' using '$os'"
+ return 1
+ fi
+
+ local os=$vm_dir/os.qcow2
+ elif [[ "$os_mode" == "original" ]]; then
+ warning "Using original OS image file: $os"
+ elif [[ "$os_mode" != "snapshot" ]]; then
+ if [[ -z "$os_mode" ]]; then
+ notice "No '--os-mode' parameter provided - using 'snapshot'"
+ os_mode="snapshot"
+ else
+ error "Invalid '--os-mode=$os_mode'"
+ return 1
+ fi
+ fi
+
+ # WARNING:
+ # each cmd+= must contain ' ${eol}' at the end
+ #
+ local eol="\\\\\n "
+ local qemu_mask_param="VM_${vm_num}_qemu_mask"
+ local qemu_numa_node_param="VM_${vm_num}_qemu_numa_node"
+
+ if [[ -z "${!qemu_mask_param}" ]] || [[ -z "${!qemu_numa_node_param}" ]]; then
+ error "Parameters ${qemu_mask_param} or ${qemu_numa_node_param} not found in autotest.config file"
+ return 1
+ fi
+
+ local task_mask=${!qemu_mask_param}
+
+ notice "TASK MASK: $task_mask"
+ local cmd="taskset -a -c $task_mask $QEMU_PREFIX/bin/qemu-system-x86_64 ${eol}"
+ local vm_socket_offset=$(( 10000 + 100 * vm_num ))
+
+ local ssh_socket=$(( vm_socket_offset + 0 ))
+ local fio_socket=$(( vm_socket_offset + 1 ))
+ local monitor_port=$(( vm_socket_offset + 2 ))
+ local migration_port=$(( vm_socket_offset + 3 ))
+ local gdbserver_socket=$(( vm_socket_offset + 4 ))
+ local vnc_socket=$(( 100 + vm_num ))
+ local qemu_pid_file="$vm_dir/qemu.pid"
+ local cpu_num=0
+
+ set +x
+ # cpu list for taskset can be comma separated or range
+ # or both at the same time, so first split on commas
+ cpu_list=$(echo $task_mask | tr "," "\n")
+ queue_number=0
+ for c in $cpu_list; do
+ # if range is detected - count how many cpus
+ if [[ $c =~ [0-9]+-[0-9]+ ]]; then
+ val=$(($c-1))
+ val=${val#-}
+ else
+ val=1
+ fi
+ cpu_num=$((cpu_num+val))
+ queue_number=$((queue_number+val))
+ done
+
+ if [ -z $queue_number ]; then
+ queue_number=$cpu_num
+ fi
+
+ $shell_restore_x
+
+ local node_num=${!qemu_numa_node_param}
+ local boot_disk_present=false
+ notice "NUMA NODE: $node_num"
+ cmd+="-m $guest_memory --enable-kvm -cpu host -smp $cpu_num -vga std -vnc :$vnc_socket -daemonize ${eol}"
+ cmd+="-object memory-backend-file,id=mem,size=${guest_memory}M,mem-path=/dev/hugepages,share=on,prealloc=yes,host-nodes=$node_num,policy=bind ${eol}"
+ [[ $os_mode == snapshot ]] && cmd+="-snapshot ${eol}"
+ [[ ! -z "$vm_incoming" ]] && cmd+=" -incoming tcp:0:$migration_port ${eol}"
+ cmd+="-monitor telnet:127.0.0.1:$monitor_port,server,nowait ${eol}"
+ cmd+="-numa node,memdev=mem ${eol}"
+ cmd+="-pidfile $qemu_pid_file ${eol}"
+ cmd+="-serial file:$vm_dir/serial.log ${eol}"
+ cmd+="-D $vm_dir/qemu.log ${eol}"
+ cmd+="-net user,hostfwd=tcp::$ssh_socket-:22,hostfwd=tcp::$fio_socket-:8765 ${eol}"
+ cmd+="-net nic ${eol}"
+ if [[ -z "$boot_from" ]]; then
+ cmd+="-drive file=$os,if=none,id=os_disk ${eol}"
+ cmd+="-device ide-hd,drive=os_disk,bootindex=0 ${eol}"
+ fi
+
+ if ( [[ $disks == '' ]] && [[ $disk_type_g == virtio* ]] ); then
+ disks=1
+ fi
+
+ for disk in ${disks//:/ }; do
+ if [[ $disk = *","* ]]; then
+ disk_type=${disk#*,}
+ disk=${disk%,*}
+ else
+ disk_type=$disk_type_g
+ fi
+
+ case $disk_type in
+ virtio)
+ local raw_name="RAWSCSI"
+ local raw_disk=$vm_dir/test.img
+
+ if [[ ! -z $disk ]]; then
+ [[ ! -b $disk ]] && touch $disk
+ local raw_disk=$(readlink -f $disk)
+ fi
+
+ # Create disk file if it not exist or it is smaller than 1G
+ if ( [[ -f $raw_disk ]] && [[ $(stat --printf="%s" $raw_disk) -lt $((1024 * 1024 * 1024)) ]] ) || \
+ [[ ! -e $raw_disk ]]; then
+ if [[ $raw_disk =~ /dev/.* ]]; then
+ error \
+ "ERROR: Virtio disk point to missing device ($raw_disk) -\n" \
+ " this is probably not what you want."
+ return 1
+ fi
+
+ notice "Creating Virtio disc $raw_disk"
+ dd if=/dev/zero of=$raw_disk bs=1024k count=1024
+ else
+ notice "Using existing image $raw_disk"
+ fi
+
+ cmd+="-device virtio-scsi-pci,num_queues=$queue_number ${eol}"
+ cmd+="-device scsi-hd,drive=hd$i,vendor=$raw_name ${eol}"
+ cmd+="-drive if=none,id=hd$i,file=$raw_disk,format=raw$raw_cache ${eol}"
+ ;;
+ spdk_vhost_scsi)
+ notice "using socket $vhost_dir/naa.$disk.$vm_num"
+ cmd+="-chardev socket,id=char_$disk,path=$vhost_dir/naa.$disk.$vm_num ${eol}"
+ cmd+="-device vhost-user-scsi-pci,id=scsi_$disk,num_queues=$queue_number,chardev=char_$disk"
+ if [[ "$disk" == "$boot_from" ]]; then
+ cmd+=",bootindex=0"
+ boot_disk_present=true
+ fi
+ cmd+=" ${eol}"
+ ;;
+ spdk_vhost_blk)
+ notice "using socket $vhost_dir/naa.$disk.$vm_num"
+ cmd+="-chardev socket,id=char_$disk,path=$vhost_dir/naa.$disk.$vm_num ${eol}"
+ cmd+="-device vhost-user-blk-pci,num-queues=$queue_number,chardev=char_$disk"
+ if [[ "$disk" == "$boot_from" ]]; then
+ cmd+=",bootindex=0"
+ boot_disk_present=true
+ fi
+ cmd+=" ${eol}"
+ ;;
+ kernel_vhost)
+ if [[ -z $disk ]]; then
+ error "need WWN for $disk_type"
+ return 1
+ elif [[ ! $disk =~ ^[[:alpha:]]{3}[.][[:xdigit:]]+$ ]]; then
+ error "$disk_type - disk(wnn)=$disk does not look like WNN number"
+ return 1
+ fi
+ notice "Using kernel vhost disk wwn=$disk"
+ cmd+=" -device vhost-scsi-pci,wwpn=$disk,num_queues=$queue_number ${eol}"
+ ;;
+ *)
+ error "unknown mode '$disk_type', use: virtio, spdk_vhost_scsi, spdk_vhost_blk or kernel_vhost"
+ return 1
+ esac
+ done
+
+ if [[ -n $boot_from ]] && [[ $boot_disk_present == false ]]; then
+ error "Boot from $boot_from is selected but device is not present"
+ return 1
+ fi
+
+ [[ ! -z $qemu_args ]] && cmd+=" $qemu_args ${eol}"
+ # remove last $eol
+ cmd="${cmd%\\\\\\n }"
+
+ notice "Saving to $vm_dir/run.sh"
+ (
+ echo '#!/bin/bash'
+ echo 'if [[ $EUID -ne 0 ]]; then '
+ echo ' echo "Go away user come back as root"'
+ echo ' exit 1'
+ echo 'fi';
+ echo
+ echo -e "qemu_cmd=\"$cmd\"";
+ echo
+ echo "echo 'Running VM in $vm_dir'"
+ echo "rm -f $qemu_pid_file"
+ echo '$qemu_cmd'
+ echo "echo 'Waiting for QEMU pid file'"
+ echo "sleep 1"
+ echo "[[ ! -f $qemu_pid_file ]] && sleep 1"
+ echo "[[ ! -f $qemu_pid_file ]] && echo 'ERROR: no qemu pid file found' && exit 1"
+ echo
+ echo "chmod +r $vm_dir/*"
+ echo
+ echo "echo '=== qemu.log ==='"
+ echo "cat $vm_dir/qemu.log"
+ echo "echo '=== qemu.log ==='"
+ echo '# EOF'
+ ) > $vm_dir/run.sh
+ chmod +x $vm_dir/run.sh
+
+ # Save generated sockets redirection
+ echo $ssh_socket > $vm_dir/ssh_socket
+ echo $fio_socket > $vm_dir/fio_socket
+ echo $monitor_port > $vm_dir/monitor_port
+
+ rm -f $vm_dir/migration_port
+ [[ -z $vm_incoming ]] || echo $migration_port > $vm_dir/migration_port
+
+ echo $gdbserver_socket > $vm_dir/gdbserver_socket
+ echo $vnc_socket >> $vm_dir/vnc_socket
+
+ [[ -z $vm_incoming ]] || ln -fs $VM_BASE_DIR/$vm_incoming $vm_dir/vm_incoming
+ [[ -z $vm_migrate_to ]] || ln -fs $VM_BASE_DIR/$vm_migrate_to $vm_dir/vm_migrate_to
+}
+
+function vm_run()
+{
+ local OPTIND optchar vm
+ local run_all=false
+ local vms_to_run=""
+
+ while getopts 'a-:' optchar; do
+ case "$optchar" in
+ a) run_all=true ;;
+ *)
+ error "Unknown param $OPTARG"
+ return 1
+ ;;
+ esac
+ done
+
+ if $run_all; then
+ vms_to_run="$(vm_list_all)"
+ else
+ shift $((OPTIND-1))
+ for vm in $@; do
+ vm_num_is_valid $1 || return 1
+ if [[ ! -x $VM_BASE_DIR/$vm/run.sh ]]; then
+ error "VM$vm not defined - setup it first"
+ return 1
+ fi
+ vms_to_run+=" $vm"
+ done
+ fi
+
+ for vm in $vms_to_run; do
+ if vm_is_running $vm; then
+ warning "VM$vm ($VM_BASE_DIR/$vm) already running"
+ continue
+ fi
+
+ notice "running $VM_BASE_DIR/$vm/run.sh"
+ if ! $VM_BASE_DIR/$vm/run.sh; then
+ error "FAILED to run vm $vm"
+ return 1
+ fi
+ done
+}
+
+# Wait for all created VMs to boot.
+# param $1 max wait time
+function vm_wait_for_boot()
+{
+ assert_number $1
+
+ local shell_restore_x="$( [[ "$-" =~ x ]] && echo 'set -x' )"
+ set +x
+
+ local all_booted=false
+ local timeout_time=$1
+ [[ $timeout_time -lt 10 ]] && timeout_time=10
+ local timeout_time=$(date -d "+$timeout_time seconds" +%s)
+
+ notice "Waiting for VMs to boot"
+ shift
+ if [[ "$@" == "" ]]; then
+ local vms_to_check="$VM_BASE_DIR/[0-9]*"
+ else
+ local vms_to_check=""
+ for vm in $@; do
+ vms_to_check+=" $VM_BASE_DIR/$vm"
+ done
+ fi
+
+ for vm in $vms_to_check; do
+ local vm_num=$(basename $vm)
+ local i=0
+ notice "waiting for VM$vm_num ($vm)"
+ while ! vm_os_booted $vm_num; do
+ if ! vm_is_running $vm_num; then
+
+ warning "VM $vm_num is not running"
+ warning "================"
+ warning "QEMU LOG:"
+ if [[ -r $vm/qemu.log ]]; then
+ cat $vm/qemu.log
+ else
+ warning "LOG not found"
+ fi
+
+ warning "VM LOG:"
+ if [[ -r $vm/serial.log ]]; then
+ cat $vm/serial.log
+ else
+ warning "LOG not found"
+ fi
+ warning "================"
+ $shell_restore_x
+ return 1
+ fi
+
+ if [[ $(date +%s) -gt $timeout_time ]]; then
+ warning "timeout waiting for machines to boot"
+ $shell_restore_x
+ return 1
+ fi
+ if (( i > 30 )); then
+ local i=0
+ echo
+ fi
+ echo -n "."
+ sleep 1
+ done
+ echo ""
+ notice "VM$vm_num ready"
+ #Change Timeout for stopping services to prevent lengthy powerdowns
+ vm_ssh $vm_num "echo 'DefaultTimeoutStopSec=10' >> /etc/systemd/system.conf; systemctl daemon-reexec"
+ done
+
+ notice "all VMs ready"
+ $shell_restore_x
+ return 0
+}
+
+function vm_start_fio_server()
+{
+ local OPTIND optchar
+ local readonly=''
+ while getopts ':-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ fio-bin=*) local fio_bin="${OPTARG#*=}" ;;
+ readonly) local readonly="--readonly" ;;
+ *) error "Invalid argument '$OPTARG'" && return 1;;
+ esac
+ ;;
+ *) error "Invalid argument '$OPTARG'" && return 1;;
+ esac
+ done
+
+ shift $(( OPTIND - 1 ))
+ for vm_num in $@; do
+ notice "Starting fio server on VM$vm_num"
+ if [[ $fio_bin != "" ]]; then
+ cat $fio_bin | vm_ssh $vm_num 'cat > /root/fio; chmod +x /root/fio'
+ vm_ssh $vm_num /root/fio $readonly --eta=never --server --daemonize=/root/fio.pid
+ else
+ vm_ssh $vm_num fio $readonly --eta=never --server --daemonize=/root/fio.pid
+ fi
+ done
+}
+
+function vm_check_scsi_location()
+{
+ # Script to find wanted disc
+ local script='shopt -s nullglob; \
+ for entry in /sys/block/sd*; do \
+ disk_type="$(cat $entry/device/vendor)"; \
+ if [[ $disk_type == INTEL* ]] || [[ $disk_type == RAWSCSI* ]] || [[ $disk_type == LIO-ORG* ]]; then \
+ fname=$(basename $entry); \
+ echo -n " $fname"; \
+ fi; \
+ done'
+
+ SCSI_DISK="$(echo "$script" | vm_ssh $1 bash -s)"
+
+ if [[ -z "$SCSI_DISK" ]]; then
+ error "no test disk found!"
+ return 1
+ fi
+}
+
+# Script to perform scsi device reset on all disks in VM
+# param $1 VM num
+# param $2..$n Disks to perform reset on
+function vm_reset_scsi_devices()
+{
+ for disk in "${@:2}"; do
+ notice "VM$1 Performing device reset on disk $disk"
+ vm_ssh $1 sg_reset /dev/$disk -vNd
+ done
+}
+
+function vm_check_blk_location()
+{
+ local script='shopt -s nullglob; cd /sys/block; echo vd*'
+ SCSI_DISK="$(echo "$script" | vm_ssh $1 bash -s)"
+
+ if [[ -z "$SCSI_DISK" ]]; then
+ error "no blk test disk found!"
+ return 1
+ fi
+}
+
+function run_fio()
+{
+ local arg
+ local job_file=""
+ local fio_bin=""
+ local vms=()
+ local out=""
+ local fio_disks=""
+ local vm
+ local run_server_mode=true
+
+ for arg in $@; do
+ case "$arg" in
+ --job-file=*) local job_file="${arg#*=}" ;;
+ --fio-bin=*) local fio_bin="${arg#*=}" ;;
+ --vm=*) vms+=( "${arg#*=}" ) ;;
+ --out=*)
+ local out="${arg#*=}"
+ mkdir -p $out
+ ;;
+ --local) run_server_mode=false ;;
+ --json) json="--json" ;;
+ *)
+ error "Invalid argument '$arg'"
+ return 1
+ ;;
+ esac
+ done
+
+ if [[ ! -z "$fio_bin" && ! -r "$fio_bin" ]]; then
+ error "FIO binary '$fio_bin' does not exist"
+ return 1
+ fi
+
+ if [[ ! -r "$job_file" ]]; then
+ error "Fio job '$job_file' does not exist"
+ return 1
+ fi
+
+ local job_fname=$(basename "$job_file")
+ # prepare job file for each VM
+ for vm in ${vms[@]}; do
+ local vm_num=${vm%%:*}
+ local vmdisks=${vm#*:}
+
+ sed "s@filename=@filename=$vmdisks@" $job_file | vm_ssh $vm_num "cat > /root/$job_fname"
+ fio_disks+="127.0.0.1:$(vm_fio_socket $vm_num):$vmdisks,"
+
+ vm_ssh $vm_num cat /root/$job_fname
+ if ! $run_server_mode; then
+ if [[ ! -z "$fio_bin" ]]; then
+ cat $fio_bin | vm_ssh $vm_num 'cat > /root/fio; chmod +x /root/fio'
+ fi
+
+ notice "Running local fio on VM $vm_num"
+ vm_ssh $vm_num "nohup /root/fio /root/$job_fname 1>/root/$job_fname.out 2>/root/$job_fname.out </dev/null & echo \$! > /root/fio.pid"
+ fi
+ done
+
+ if ! $run_server_mode; then
+ # Give FIO time to run
+ sleep 0.5
+ return 0
+ fi
+
+ $SPDK_BUILD_DIR/test/vhost/common/run_fio.py --job-file=/root/$job_fname \
+ $([[ ! -z "$fio_bin" ]] && echo "--fio-bin=$fio_bin") \
+ --out=$out $json ${fio_disks%,}
+}
+
+# Shutdown or kill any running VM and SPDK APP.
+#
+function at_app_exit()
+{
+ local vhost_num
+
+ notice "APP EXITING"
+ notice "killing all VMs"
+ vm_kill_all
+ # Kill vhost application
+ notice "killing vhost app"
+
+ for vhost_num in $(spdk_vhost_list_all); do
+ spdk_vhost_kill $vhost_num
+ done
+
+ notice "EXIT DONE"
+}
+
+function error_exit()
+{
+ trap - ERR
+ print_backtrace
+ set +e
+ error "Error on $1 $2"
+
+ at_app_exit
+ exit 1
+}
diff --git a/src/spdk/test/vhost/common/fio_jobs/default_initiator.job b/src/spdk/test/vhost/common/fio_jobs/default_initiator.job
new file mode 100644
index 00000000..43c1404b
--- /dev/null
+++ b/src/spdk/test/vhost/common/fio_jobs/default_initiator.job
@@ -0,0 +1,9 @@
+[global]
+thread=1
+group_reporting=1
+direct=1
+time_based=1
+do_verify=1
+verify=md5
+verify_backlog=1024
+fsync_on_close=1
diff --git a/src/spdk/test/vhost/common/fio_jobs/default_integrity.job b/src/spdk/test/vhost/common/fio_jobs/default_integrity.job
new file mode 100644
index 00000000..06398b50
--- /dev/null
+++ b/src/spdk/test/vhost/common/fio_jobs/default_integrity.job
@@ -0,0 +1,19 @@
+[global]
+blocksize_range=4k-512k
+iodepth=512
+iodepth_batch=128
+iodepth_low=256
+ioengine=libaio
+size=1G
+io_size=4G
+filename=
+group_reporting
+thread
+numjobs=1
+direct=1
+rw=randwrite
+do_verify=1
+verify=md5
+verify_backlog=1024
+fsync_on_close=1
+[nvme-host]
diff --git a/src/spdk/test/vhost/common/fio_jobs/default_integrity_nightly.job b/src/spdk/test/vhost/common/fio_jobs/default_integrity_nightly.job
new file mode 100644
index 00000000..09740178
--- /dev/null
+++ b/src/spdk/test/vhost/common/fio_jobs/default_integrity_nightly.job
@@ -0,0 +1,23 @@
+[global]
+ioengine=libaio
+runtime=10
+filename=
+group_reporting
+thread
+numjobs=1
+direct=1
+do_verify=1
+verify=md5
+verify_backlog=1024
+
+[randwrite]
+stonewall
+rw=randwrite
+bs=512k
+iodepth=256
+
+[randrw]
+stonewall
+rw=randrw
+bs=128k
+iodepth=64
diff --git a/src/spdk/test/vhost/common/fio_jobs/default_performance.job b/src/spdk/test/vhost/common/fio_jobs/default_performance.job
new file mode 100644
index 00000000..a51cb5ed
--- /dev/null
+++ b/src/spdk/test/vhost/common/fio_jobs/default_performance.job
@@ -0,0 +1,16 @@
+[global]
+blocksize_range=4k-512k
+iodepth=512
+iodepth_batch=128
+iodepth_low=256
+ioengine=libaio
+size=10G
+filename=
+ramp_time=10
+group_reporting
+thread
+numjobs=1
+direct=1
+rw=randread
+fsync_on_close=1
+[nvme-host]
diff --git a/src/spdk/test/vhost/common/run_fio.py b/src/spdk/test/vhost/common/run_fio.py
new file mode 100755
index 00000000..0760b018
--- /dev/null
+++ b/src/spdk/test/vhost/common/run_fio.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+import getopt
+import subprocess
+import signal
+import re
+
+fio_bin = "fio"
+
+
+def show_help():
+ print("""Usage: {} run_fio.py [options] [args]
+ Description:
+ Run FIO job file 'fio.job' on remote machines.
+ NOTE: The job file must exist on remote machines on '/root/' directory.
+ Args:
+ [VMs] (ex. vm1_IP:vm1_port:vm1_disk1:vm_disk2,vm2_IP:vm2_port:vm2_disk1,etc...)
+ Options:
+ -h, --help Show this message.
+ -j, --job-file Paths to file with FIO job configuration on remote host.
+ -f, --fio-bin Location of FIO binary on local host (Default "fio")
+ -o, --out Directory used to save generated job files and
+ files with test results
+ -J, --json Use JSON format for output
+ -p, --perf-vmex Enable aggregating statistic for VMEXITS for VMs
+ """.format(os.path.split(sys.executable)[-1]))
+
+
+def exec_cmd(cmd, blocking):
+ # Print result to STDOUT for now, we don't have json support yet.
+ p = subprocess.Popen(cmd.split(" "), stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT, stdin=subprocess.PIPE)
+ if blocking is True:
+ out, _ = p.communicate()
+ return p.returncode, out.decode()
+ return p
+
+
+def save_file(path, mode, contents):
+ with open(path, mode) as fh:
+ fh.write(contents)
+ fh.close()
+
+
+def run_fio(vms, fio_cfg_fname, out_path, perf_vmex=False, json=False):
+ global fio_bin
+ job_name = os.path.splitext(os.path.basename(fio_cfg_fname))[0]
+
+ # Build command for FIO
+ fio_cmd = " ".join([fio_bin, "--eta=never"])
+ if json:
+ fio_cmd = " ".join([fio_bin, "--output-format=json"])
+ for vm in vms:
+ # vm[0] = IP address, vm[1] = Port number
+ fio_cmd = " ".join([fio_cmd,
+ "--client={vm_ip},{vm_port}".format(vm_ip=vm[0], vm_port=vm[1]),
+ "--remote-config {cfg}".format(cfg=fio_cfg_fname)])
+ print(fio_cmd)
+
+ if perf_vmex:
+ perf_dir = os.path.join(out_path, "perf_stats")
+ try:
+ os.mkdir(perf_dir)
+ except OSError:
+ pass
+
+ # Start gathering perf statistics for host and VM guests
+ perf_rec_file = os.path.join(perf_dir, "perf.data.kvm")
+ perf_run_cmd = "perf kvm --host --guest " + \
+ "-o {0} stat record -a".format(perf_rec_file)
+ print(perf_run_cmd)
+ perf_p = exec_cmd(perf_run_cmd, blocking=False)
+
+ # Run FIO test on VMs
+ rc, out = exec_cmd(fio_cmd, blocking=True)
+
+ # if for some reason output contains lines with "eta" - remove them
+ out = re.sub(r'.+\[eta\s+\d{2}m:\d{2}s\]', '', out)
+
+ print(out)
+
+ if rc != 0:
+ print("ERROR! While executing FIO jobs - RC: {rc}".format(rc=rc, out=out))
+ sys.exit(rc)
+ else:
+ save_file(os.path.join(out_path, ".".join([job_name, "log"])), "w", out)
+
+ if perf_vmex:
+ # Stop gathering perf statistics and prepare some result files
+ perf_p.send_signal(signal.SIGINT)
+ perf_p.wait()
+
+ perf_stat_cmd = "perf kvm --host -i {perf_rec} stat report --event vmexit"\
+ .format(perf_rec=perf_rec_file)
+
+ rc, out = exec_cmd(" ".join([perf_stat_cmd, "--event vmexit"]),
+ blocking=True)
+ print("VMexit host stats:")
+ print("{perf_out}".format(perf_out=out))
+ save_file(os.path.join(perf_dir, "vmexit_stats_" + job_name),
+ "w", "{perf_out}".format(perf_out=out))
+ try:
+ os.remove(perf_rec_file)
+ except OSError:
+ pass
+
+
+def main():
+ global fio_bin
+
+ abspath = os.path.abspath(__file__)
+ dname = os.path.dirname(abspath)
+
+ vms = []
+ fio_cfg = None
+ out_dir = None
+ perf_vmex = False
+ json = False
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "hJj:f:o:p",
+ ["help", "job-file=", "fio-bin=",
+ "out=", "perf-vmex", "json"])
+ except getopt.GetoptError:
+ show_help()
+ sys.exit(1)
+
+ if len(args) < 1:
+ show_help()
+ sys.exit(1)
+
+ for o, a in opts:
+ if o in ("-j", "--job-file"):
+ fio_cfg = a
+ elif o in ("-h", "--help"):
+ show_help()
+ sys.exit(1)
+ elif o in ("-p", "--perf-vmex"):
+ perf_vmex = True
+ elif o in ("-o", "--out"):
+ out_dir = a
+ elif o in ("-f", "--fio-bin"):
+ fio_bin = a
+ elif o in ("-J", "--json"):
+ json = True
+
+ if fio_cfg is None:
+ print("ERROR! No FIO job provided!")
+ sys.exit(1)
+
+ if out_dir is None or not os.path.exists(out_dir):
+ print("ERROR! Folder {out_dir} does not exist ".format(out_dir=out_dir))
+ sys.exit(1)
+
+ # Get IP, port and fio 'filename' information from positional args
+ for arg in args[0].split(","):
+ _ = arg.split(":")
+ ip, port, filenames = _[0], _[1], ":".join(_[2:])
+ vms.append((ip, port, filenames))
+
+ print("Running job file: {0}".format(fio_cfg))
+ run_fio(vms, fio_cfg, out_dir, perf_vmex, json)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/src/spdk/test/vhost/common/run_vhost.sh b/src/spdk/test/vhost/common/run_vhost.sh
new file mode 100755
index 00000000..bd6c496a
--- /dev/null
+++ b/src/spdk/test/vhost/common/run_vhost.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $BASE_DIR/../common && pwd)"
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)"
+
+vhost_num=""
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for running vhost app."
+ echo "Usage: $(basename $1) [-x] [-h|--help] [--clean-build] [--work-dir=PATH]"
+ echo "-h, --help print help and exit"
+ echo "-x Set -x for script debug"
+ echo " --work-dir=PATH Where to find source/project. [default=$TEST_DIR]"
+ echo " --conf-dir=PATH Path to directory with configuration for vhost"
+ echo " --vhost-num=NUM Optional: vhost instance NUM to start. Default: 0"
+
+ exit 0
+}
+
+run_in_background=false
+while getopts 'xh-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 ;;
+ work-dir=*) TEST_DIR="${OPTARG#*=}" ;;
+ conf-dir=*) CONF_DIR="${OPTARG#*=}" ;;
+ vhost-num=*) vhost_num="${OPTARG}" ;;
+ *) usage $0 echo "Invalid argument '$OPTARG'" ;;
+ esac
+ ;;
+ h) usage $0 ;;
+ x) set -x ;;
+ *) usage $0 "Invalid argument '$optchar'" ;;
+ esac
+done
+
+if [[ $EUID -ne 0 ]]; then
+ fail "Go away user come back as root"
+fi
+
+notice "$0"
+notice ""
+
+. $COMMON_DIR/common.sh
+
+# Starting vhost with valid options
+spdk_vhost_run $vhost_num --conf-path=$CONF_DIR
diff --git a/src/spdk/test/vhost/common/vm_run.sh b/src/spdk/test/vhost/common/vm_run.sh
new file mode 100755
index 00000000..03938f8c
--- /dev/null
+++ b/src/spdk/test/vhost/common/vm_run.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+
+BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $BASE_DIR/../common && pwd)"
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)"
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for enabling VMs"
+ echo "Usage: $(basename $1) [OPTIONS] VM..."
+ echo
+ echo "-h, --help print help and exit"
+ echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: ./..]"
+ echo "-a Run all VMs in WORK_DIR"
+ echo "-x set -x for script debug"
+ exit 0
+}
+run_all=false
+while getopts 'xah-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 ;;
+ work-dir=*) TEST_DIR="${OPTARG#*=}" ;;
+ *) usage $0 "Invalid argument '$OPTARG'" ;;
+ esac
+ ;;
+ h) usage $0 ;;
+ a) run_all=true ;;
+ x) set -x ;;
+ *) usage $0 "Invalid argument '$OPTARG'"
+ esac
+done
+
+. $COMMON_DIR/common.sh
+
+if [[ $EUID -ne 0 ]]; then
+ fail "Go away user come back as root"
+fi
+
+if $run_all; then
+ vm_run -a
+else
+ shift $((OPTIND-1))
+ notice "running VMs: $@"
+ vm_run "$@"
+fi
diff --git a/src/spdk/test/vhost/common/vm_setup.sh b/src/spdk/test/vhost/common/vm_setup.sh
new file mode 100755
index 00000000..7e3599fd
--- /dev/null
+++ b/src/spdk/test/vhost/common/vm_setup.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $BASE_DIR/../common && pwd)"
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)"
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for setting up VMs for tests"
+ echo "Usage: $(basename $1) [OPTIONS] VM_NUM"
+ echo
+ echo "-h, --help Print help and exit"
+ echo " --work-dir=WORK_DIR Where to find build file. Must exit. (default: $TEST_DIR)"
+ echo " --force=VM_NUM Force VM_NUM reconfiguration if already exist"
+ echo " --disk-type=TYPE Perform specified test:"
+ echo " virtio - test host virtio-scsi-pci using file as disk image"
+ echo " kernel_vhost - use kernel driver vhost-scsi"
+ echo " spdk_vhost_scsi - use spdk vhost scsi"
+ echo " spdk_vhost_blk - use spdk vhost block"
+ echo " --read-only=true|false Enable/Disable read only for vhost_blk tests"
+ echo " --raw-cache=CACHE Use CACHE for virtio test: "
+ echo " writethrough, writeback, none, unsafe or directsyns"
+ echo " --disk=PATH[,disk_type] Disk to use in test. test specific meaning:"
+ echo " virtio - disk path (file or block device ex: /dev/nvme0n1)"
+ echo " kernel_vhost - the WWN number to be used"
+ echo " spdk_vhost_[scsi|blk] - the socket path."
+ echo " optional disk_type - set disk type for disk (overwrites test-type)"
+ echo " e.g. /dev/nvme0n1,spdk_vhost_scsi"
+ echo " --os=OS_QCOW2 Custom OS qcow2 image file"
+ echo " --os-mode=MODE MODE how to use provided image: default: backing"
+ echo " backing - create new image but use provided backing file"
+ echo " copy - copy provided image and use a copy"
+ echo " orginal - use file directly. Will modify the provided file"
+ echo " --incoming=VM_NUM Use VM_NUM as source migration VM."
+ echo " --migrate-to=VM_NUM Use VM_NUM as target migration VM."
+ echo " --vhost-num=NUM Optional: vhost instance NUM to be used by this VM. Default: 0"
+ echo "-x Turn on script debug (set -x)"
+ echo "-v Be more verbose"
+ exit 0
+}
+
+setup_params=()
+for param in "$@"; do
+ case "$param" in
+ --help|-h) usage $0 ;;
+ --work-dir=*)
+ TEST_DIR="${param#*=}"
+ continue
+ ;;
+ --raw-cache=*) ;;
+ --disk-type=*) ;;
+ --disks=*) ;;
+ --os=*) ;;
+ --os-mode=*) ;;
+ --force=*) ;;
+ --incoming=*) ;;
+ --migrate-to=*) ;;
+ --read-only=*) ;;
+ -x)
+ set -x
+ continue
+ ;;
+ -v)
+ SPDK_VHOST_VERBOSE=true
+ continue
+ ;;
+ *) usage $0 "Invalid argument '$param'" ;;
+ esac
+
+ setup_params+=( "$param" )
+done
+
+. $COMMON_DIR/common.sh
+
+vm_setup ${setup_params[@]}
+
+trap -- ERR
diff --git a/src/spdk/test/vhost/common/vm_shutdown.sh b/src/spdk/test/vhost/common/vm_shutdown.sh
new file mode 100755
index 00000000..1de1170f
--- /dev/null
+++ b/src/spdk/test/vhost/common/vm_shutdown.sh
@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+
+BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $BASE_DIR/../common && pwd)"
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)"
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for shutting down VMs"
+ echo "Usage: $(basename $1) [OPTIONS] [VMs]"
+ echo
+ echo "-h, --help print help and exit"
+ echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: ./..]"
+ echo "-a kill/shutdown all running VMs"
+ echo "-k kill instead of shutdown"
+ exit 0
+}
+optspec='akh-:'
+do_kill=false
+all=false
+
+while getopts "$optspec" optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 ;;
+ work-dir=*) TEST_DIR="${OPTARG#*=}" ;;
+ *) usage $0 "Invalid argument '$OPTARG'" ;;
+ esac
+ ;;
+ h) usage $0 ;;
+ k) do_kill=true ;;
+ a) all=true ;;
+ *) usage $0 "Invalid argument '$OPTARG'"
+ esac
+done
+
+. $COMMON_DIR/common.sh
+
+if $do_kill && [[ $EUID -ne 0 ]]; then
+ echo "Go away user come back as root"
+ exit 1
+fi
+
+if $all; then
+ if do_kill; then
+ notice "killing all VMs"
+ vm_kill_all
+ else
+ notice "shutting down all VMs"
+ vm_shutdown_all
+ fi
+else
+ shift $((OPTIND-1))
+
+ if do_kill; then
+ notice "INFO: killing VMs: $@"
+ for vm in $@; do
+ vm_kill $vm
+ done
+ else
+ notice "shutting down all VMs"
+ vm_shutdown_all
+ fi
+fi
diff --git a/src/spdk/test/vhost/common/vm_ssh.sh b/src/spdk/test/vhost/common/vm_ssh.sh
new file mode 100755
index 00000000..abdc3322
--- /dev/null
+++ b/src/spdk/test/vhost/common/vm_ssh.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+
+BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $BASE_DIR/../common && pwd)"
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)"
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for connecting to or executing command on selected VM"
+ echo "Usage: $(basename $1) [OPTIONS] VM_NUMBER"
+ echo
+ echo "-h, --help print help and exit"
+ echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: $TEST_DIR]"
+ echo "-w Don't wait for vm to boot"
+ echo "-x set -x for script debug"
+ exit 0
+}
+
+boot_wait=true
+while getopts 'xwh-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 ;;
+ work-dir=*) TEST_DIR="${OPTARG#*=}" ;;
+ *) usage $0 "Invalid argument '$OPTARG'" ;;
+ esac ;;
+ h) usage $0 ;;
+ w) boot_wait=false ;;
+ x) set -x ;;
+ *) usage $0 "Invalid argument '$OPTARG'" ;;
+ esac
+done
+
+. $COMMON_DIR/common.sh
+
+shift $((OPTIND-1))
+vm_num="$1"
+shift
+
+
+if ! vm_num_is_valid $vm_num; then
+ usage $0 "Invalid VM num $vm_num"
+ exit 1
+fi
+
+if $boot_wait; then
+ while ! vm_os_booted $vm_num; do
+ if ! vm_is_running $vm_num; then
+ fail "VM$vm_num is not running"
+ fi
+ notice "waiting for VM$vm_num to boot"
+ sleep 1
+ done
+fi
+
+vm_ssh $vm_num "$@"
diff --git a/src/spdk/test/vhost/fiotest/autotest.sh b/src/spdk/test/vhost/fiotest/autotest.sh
new file mode 100755
index 00000000..466ac141
--- /dev/null
+++ b/src/spdk/test/vhost/fiotest/autotest.sh
@@ -0,0 +1,247 @@
+#!/usr/bin/env bash
+set -e
+AUTOTEST_BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $AUTOTEST_BASE_DIR/../common && pwd)"
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $AUTOTEST_BASE_DIR/../../../../ && pwd)"
+
+dry_run=false
+no_shutdown=false
+fio_bin=""
+remote_fio_bin=""
+fio_jobs=""
+test_type=spdk_vhost_scsi
+reuse_vms=false
+vms=()
+used_vms=""
+x=""
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for doing automated test"
+ echo "Usage: $(basename $1) [OPTIONS]"
+ echo
+ echo "-h, --help print help and exit"
+ echo " --test-type=TYPE Perform specified test:"
+ echo " virtio - test host virtio-scsi-pci using file as disk image"
+ echo " kernel_vhost - use kernel driver vhost-scsi"
+ echo " spdk_vhost_scsi - use spdk vhost scsi"
+ echo " spdk_vhost_blk - use spdk vhost block"
+ echo "-x set -x for script debug"
+ echo " --fio-bin=FIO Use specific fio binary (will be uploaded to VM)"
+ echo " --fio-job= Fio config to use for test."
+ echo " All VMs will run the same fio job when FIO executes."
+ echo " (no unique jobs for specific VMs)"
+ echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: $TEST_DIR]"
+ echo " --dry-run Don't perform any tests, run only and wait for enter to terminate"
+ echo " --no-shutdown Don't shutdown at the end but leave envirionment working"
+ echo " --vm=NUM[,OS][,DISKS] VM configuration. This parameter might be used more than once:"
+ echo " NUM - VM number (mandatory)"
+ echo " OS - VM os disk path (optional)"
+ echo " DISKS - VM os test disks/devices path (virtio - optional, kernel_vhost - mandatory)"
+ exit 0
+}
+
+#default raw file is NVMe drive
+
+while getopts 'xh-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 ;;
+ work-dir=*) TEST_DIR="${OPTARG#*=}" ;;
+ fio-bin=*) fio_bin="--fio-bin=${OPTARG#*=}" ;;
+ fio-job=*) fio_job="${OPTARG#*=}" ;;
+ dry-run) dry_run=true ;;
+ no-shutdown) no_shutdown=true ;;
+ test-type=*) test_type="${OPTARG#*=}" ;;
+ vm=*) vms+=("${OPTARG#*=}") ;;
+ *) usage $0 "Invalid argument '$OPTARG'" ;;
+ esac
+ ;;
+ h) usage $0 ;;
+ x) set -x
+ x="-x" ;;
+ *) usage $0 "Invalid argument '$OPTARG'"
+ esac
+done
+shift $(( OPTIND - 1 ))
+
+if [[ ! -r "$fio_job" ]]; then
+ fail "no fio job file specified"
+fi
+
+. $COMMON_DIR/common.sh
+
+trap 'error_exit "${FUNCNAME}" "${LINENO}"' ERR
+
+vm_kill_all
+
+if [[ $test_type =~ "spdk_vhost" ]]; then
+ notice "==============="
+ notice ""
+ notice "running SPDK"
+ notice ""
+ spdk_vhost_run --json-path=$AUTOTEST_BASE_DIR
+ notice ""
+fi
+
+notice "==============="
+notice ""
+notice "Setting up VM"
+notice ""
+
+rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock"
+
+for vm_conf in ${vms[@]}; do
+ IFS=',' read -ra conf <<< "$vm_conf"
+ if [[ x"${conf[0]}" == x"" ]] || ! assert_number ${conf[0]}; then
+ fail "invalid VM configuration syntax $vm_conf"
+ fi
+
+ # Sanity check if VM is not defined twice
+ for vm_num in $used_vms; do
+ if [[ $vm_num -eq ${conf[0]} ]]; then
+ fail "VM$vm_num defined more than twice ( $(printf "'%s' " "${vms[@]}"))!"
+ fi
+ done
+
+ used_vms+=" ${conf[0]}"
+
+ if [[ $test_type =~ "spdk_vhost" ]]; then
+
+ notice "Adding device via RPC ..."
+
+ while IFS=':' read -ra disks; do
+ for disk in "${disks[@]}"; do
+ if [[ "$test_type" == "spdk_vhost_blk" ]]; then
+ disk=${disk%%_*}
+ notice "Creating vhost block controller naa.$disk.${conf[0]} with device $disk"
+ $rpc_py construct_vhost_blk_controller naa.$disk.${conf[0]} $disk
+ else
+ notice "Creating controller naa.$disk.${conf[0]}"
+ $rpc_py construct_vhost_scsi_controller naa.$disk.${conf[0]}
+
+ notice "Adding device (0) to naa.$disk.${conf[0]}"
+ $rpc_py add_vhost_scsi_lun naa.$disk.${conf[0]} 0 $disk
+ fi
+ done
+ done <<< "${conf[2]}"
+ unset IFS;
+ $rpc_py get_vhost_controllers
+ fi
+
+ setup_cmd="vm_setup --force=${conf[0]} --disk-type=$test_type"
+ [[ x"${conf[1]}" != x"" ]] && setup_cmd+=" --os=${conf[1]}"
+ [[ x"${conf[2]}" != x"" ]] && setup_cmd+=" --disks=${conf[2]}"
+
+ $setup_cmd
+done
+
+# Run everything
+vm_run $used_vms
+vm_wait_for_boot 600 $used_vms
+
+if [[ $test_type == "spdk_vhost_scsi" ]]; then
+ for vm_conf in ${vms[@]}; do
+ IFS=',' read -ra conf <<< "$vm_conf"
+ while IFS=':' read -ra disks; do
+ for disk in "${disks[@]}"; do
+ notice "Hotdetach test. Trying to remove existing device from a controller naa.$disk.${conf[0]}"
+ $rpc_py remove_vhost_scsi_target naa.$disk.${conf[0]} 0
+
+ sleep 0.1
+
+ notice "Hotattach test. Re-adding device 0 to naa.$disk.${conf[0]}"
+ $rpc_py add_vhost_scsi_lun naa.$disk.${conf[0]} 0 $disk
+ done
+ done <<< "${conf[2]}"
+ unset IFS;
+ done
+fi
+
+sleep 0.1
+
+notice "==============="
+notice ""
+notice "Testing..."
+
+notice "Running fio jobs ..."
+
+# Check if all VM have disk in tha same location
+DISK=""
+
+fio_disks=""
+for vm_num in $used_vms; do
+ vm_dir=$VM_BASE_DIR/$vm_num
+
+ qemu_mask_param="VM_${vm_num}_qemu_mask"
+
+ host_name="VM-$vm_num"
+ notice "Setting up hostname: $host_name"
+ vm_ssh $vm_num "hostname $host_name"
+ vm_start_fio_server $fio_bin $readonly $vm_num
+
+ if [[ "$test_type" == "spdk_vhost_scsi" ]]; then
+ vm_check_scsi_location $vm_num
+ #vm_reset_scsi_devices $vm_num $SCSI_DISK
+ elif [[ "$test_type" == "spdk_vhost_blk" ]]; then
+ vm_check_blk_location $vm_num
+ fi
+
+ fio_disks+=" --vm=${vm_num}$(printf ':/dev/%s' $SCSI_DISK)"
+done
+
+if $dry_run; then
+ read -p "Enter to kill evething" xx
+ sleep 3
+ at_app_exit
+ exit 0
+fi
+
+run_fio $fio_bin --job-file="$fio_job" --out="$TEST_DIR/fio_results" $fio_disks
+
+if [[ "$test_type" == "spdk_vhost_scsi" ]]; then
+ for vm_num in $used_vms; do
+ vm_reset_scsi_devices $vm_num $SCSI_DISK
+ done
+fi
+
+if ! $no_shutdown; then
+ notice "==============="
+ notice "APP EXITING"
+ notice "killing all VMs"
+ vm_shutdown_all
+ notice "waiting 2 seconds to let all VMs die"
+ sleep 2
+ if [[ $test_type =~ "spdk_vhost" ]]; then
+ notice "Removing vhost devices & controllers via RPC ..."
+ for vm_conf in ${vms[@]}; do
+ IFS=',' read -ra conf <<< "$vm_conf"
+
+ while IFS=':' read -ra disks; do
+ for disk in "${disks[@]}"; do
+ disk=${disk%%_*}
+ notice "Removing all vhost devices from controller naa.$disk.${conf[0]}"
+ if [[ "$test_type" == "spdk_vhost_scsi" ]]; then
+ $rpc_py remove_vhost_scsi_target naa.$disk.${conf[0]} 0
+ fi
+
+ $rpc_py remove_vhost_controller naa.$disk.${conf[0]}
+ done
+ done <<< "${conf[2]}"
+ done
+ fi
+ notice "Testing done -> shutting down"
+ notice "killing vhost app"
+ spdk_vhost_kill
+
+ notice "EXIT DONE"
+ notice "==============="
+else
+ notice "==============="
+ notice ""
+ notice "Leaving environment working!"
+ notice ""
+ notice "==============="
+fi
diff --git a/src/spdk/test/vhost/fiotest/conf.json b/src/spdk/test/vhost/fiotest/conf.json
new file mode 100644
index 00000000..7a1594b2
--- /dev/null
+++ b/src/spdk/test/vhost/fiotest/conf.json
@@ -0,0 +1,80 @@
+{
+ "subsystems": [
+ {
+ "subsystem": "copy",
+ "config": null
+ },
+ {
+ "subsystem": "interface",
+ "config": null
+ },
+ {
+ "subsystem": "net_framework",
+ "config": null
+ },
+ {
+ "subsystem": "bdev",
+ "config": [
+ {
+ "params": {
+ "base_bdev": "Nvme0n1",
+ "split_size_mb": 0,
+ "split_count": 4
+ },
+ "method": "construct_split_vbdev"
+ },
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768
+ },
+ "method": "construct_malloc_bdev"
+ },
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768
+ },
+ "method": "construct_malloc_bdev"
+ }
+ ]
+ },
+ {
+ "subsystem": "nbd",
+ "config": []
+ },
+ {
+ "subsystem": "scsi",
+ "config": null
+ },
+ {
+ "subsystem": "vhost",
+ "config": [
+ {
+ "params": {
+ "cpumask": "0x1",
+ "ctrlr": "vhost.0"
+ },
+ "method": "construct_vhost_scsi_controller"
+ },
+ {
+ "params": {
+ "scsi_target_num": 0,
+ "bdev_name": "Malloc0",
+ "ctrlr": "vhost.0"
+ },
+ "method": "add_vhost_scsi_lun"
+ },
+ {
+ "params": {
+ "dev_name": "Malloc1",
+ "readonly": true,
+ "ctrlr": "vhost.1",
+ "cpumask": "0x1"
+ },
+ "method": "construct_vhost_blk_controller"
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/spdk/test/vhost/hotplug/blk_hotremove.sh b/src/spdk/test/vhost/hotplug/blk_hotremove.sh
new file mode 100644
index 00000000..a350d90d
--- /dev/null
+++ b/src/spdk/test/vhost/hotplug/blk_hotremove.sh
@@ -0,0 +1,236 @@
+# Vhost blk hot remove tests
+#
+# Objective
+# The purpose of these tests is to verify that SPDK vhost remains stable during
+# hot-remove operations performed on SCSI and BLK controllers devices.
+# Hot-remove is a scenario where a NVMe device is removed when already in use.
+#
+# Test cases description
+# 1. FIO I/O traffic is run during hot-remove operations.
+# By default FIO uses default_integrity*.job config files located in
+# test/vhost/hotplug/fio_jobs directory.
+# 2. FIO mode of operation is random write (randwrite) with verification enabled
+# which results in also performing read operations.
+# 3. In test cases fio status is checked after every run if any errors occurred.
+
+function prepare_fio_cmd_tc1() {
+ print_test_fio_header
+
+ run_fio="$fio_bin --eta=never "
+ for vm_num in $1; do
+ cp $fio_job $tmp_detach_job
+ vm_dir=$VM_BASE_DIR/$vm_num
+ vm_check_blk_location $vm_num
+ for disk in $SCSI_DISK; do
+ echo "[nvme-host$disk]" >> $tmp_detach_job
+ echo "filename=/dev/$disk" >> $tmp_detach_job
+ done
+ vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/default_integrity_2discs.job
+ run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity_2discs.job "
+ rm $tmp_detach_job
+ done
+}
+
+function remove_vhost_controllers() {
+ $rpc_py remove_vhost_controller naa.Nvme0n1p0.0
+ $rpc_py remove_vhost_controller naa.Nvme0n1p1.0
+ $rpc_py remove_vhost_controller naa.Nvme0n1p2.1
+ $rpc_py remove_vhost_controller naa.Nvme0n1p3.1
+}
+
+# Vhost blk hot remove test cases
+#
+# Test Case 1
+function blk_hotremove_tc1() {
+ echo "Blk hotremove test case 1"
+ traddr=""
+ # 1. Run the command to hot remove NVMe disk.
+ get_traddr "Nvme0"
+ delete_nvme "Nvme0"
+ # 2. If vhost had crashed then tests would stop running
+ sleep 1
+ add_nvme "HotInNvme0" "$traddr"
+ sleep 1
+}
+
+# Test Case 2
+function blk_hotremove_tc2() {
+ echo "Blk hotremove test case 2"
+ # 1. Use rpc command to create blk controllers.
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p0.0 HotInNvme0n1p0
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p1.0 Nvme1n1p0
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p2.1 Nvme1n1p1
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p3.1 Nvme1n1p2
+ # 2. Run two VMs and attach every VM to two blk controllers.
+ vm_run_with_arg "0 1"
+ vms_prepare "0"
+
+ traddr=""
+ get_traddr "Nvme0"
+ prepare_fio_cmd_tc1 "0"
+ # 3. Run FIO I/O traffic with verification enabled on NVMe disk.
+ $run_fio &
+ local last_pid=$!
+ sleep 3
+ # 4. Run the command to hot remove NVMe disk.
+ delete_nvme "HotInNvme0"
+ local retcode=0
+ wait_for_finish $last_pid || retcode=$?
+ # 5. Check that fio job run on hot-removed device stopped.
+ # Expected: Fio should return error message and return code != 0.
+ check_fio_retcode "Blk hotremove test case 2: Iteration 1." 1 $retcode
+
+ # 6. Reboot VM
+ reboot_all_and_prepare "0"
+ # 7. Run FIO I/O traffic with verification enabled on NVMe disk.
+ $run_fio &
+ local retcode=0
+ wait_for_finish $! || retcode=$?
+ # 8. Check that fio job run on hot-removed device stopped.
+ # Expected: Fio should return error message and return code != 0.
+ check_fio_retcode "Blk hotremove test case 2: Iteration 2." 1 $retcode
+ vm_shutdown_all
+ remove_vhost_controllers
+ add_nvme "HotInNvme1" "$traddr"
+ sleep 1
+}
+
+# ## Test Case 3
+function blk_hotremove_tc3() {
+ echo "Blk hotremove test case 3"
+ # 1. Use rpc command to create blk controllers.
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p0.0 HotInNvme1n1p0
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p1.0 Nvme1n1p0
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p2.1 HotInNvme1n1p1
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p3.1 Nvme1n1p1
+ # 2. Run two VMs and attach every VM to two blk controllers.
+ vm_run_with_arg "0 1"
+ vms_prepare "0 1"
+
+ traddr=""
+ get_traddr "Nvme0"
+ prepare_fio_cmd_tc1 "0"
+ # 3. Run FIO I/O traffic with verification enabled on first NVMe disk.
+ $run_fio &
+ local last_pid=$!
+ sleep 3
+ # 4. Run the command to hot remove of first NVMe disk.
+ delete_nvme "HotInNvme1"
+ local retcode=0
+ wait_for_finish $last_pid || retcode=$?
+ # 6. Check that fio job run on hot-removed device stopped.
+ # Expected: Fio should return error message and return code != 0.
+ check_fio_retcode "Blk hotremove test case 3: Iteration 1." 1 $retcode
+
+ # 7. Reboot VM
+ reboot_all_and_prepare "0"
+ local retcode=0
+ # 8. Run FIO I/O traffic with verification enabled on removed NVMe disk.
+ $run_fio &
+ wait_for_finish $! || retcode=$?
+ # 9. Check that fio job run on hot-removed device stopped.
+ # Expected: Fio should return error message and return code != 0.
+ check_fio_retcode "Blk hotremove test case 3: Iteration 2." 1 $retcode
+ vm_shutdown_all
+ remove_vhost_controllers
+ add_nvme "HotInNvme2" "$traddr"
+ sleep 1
+}
+
+# Test Case 4
+function blk_hotremove_tc4() {
+ echo "Blk hotremove test case 4"
+ # 1. Use rpc command to create blk controllers.
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p0.0 HotInNvme2n1p0
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p1.0 Nvme1n1p0
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p2.1 HotInNvme2n1p1
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p3.1 Nvme1n1p1
+ # 2. Run two VM, attached to blk controllers.
+ vm_run_with_arg "0 1"
+ vms_prepare "0 1"
+
+ prepare_fio_cmd_tc1 "0"
+ # 3. Run FIO I/O traffic on first VM with verification enabled on both NVMe disks.
+ $run_fio &
+ local last_pid_vm0=$!
+
+ prepare_fio_cmd_tc1 "1"
+ # 4. Run FIO I/O traffic on second VM with verification enabled on both NVMe disks.
+ $run_fio &
+ local last_pid_vm1=$!
+
+ sleep 3
+ prepare_fio_cmd_tc1 "0 1"
+ # 5. Run the command to hot remove of first NVMe disk.
+ delete_nvme "HotInNvme2"
+ local retcode_vm0=0
+ local retcode_vm1=0
+ wait_for_finish $last_pid_vm0 || retcode_vm0=$?
+ wait_for_finish $last_pid_vm1 || retcode_vm1=$?
+ # 6. Check that fio job run on hot-removed device stopped.
+ # Expected: Fio should return error message and return code != 0.
+ check_fio_retcode "Blk hotremove test case 4: Iteration 1." 1 $retcode_vm0
+ check_fio_retcode "Blk hotremove test case 4: Iteration 2." 1 $retcode_vm1
+
+ # 7. Reboot all VMs.
+ reboot_all_and_prepare "0 1"
+ # 8. Run FIO I/O traffic with verification enabled on removed NVMe disk.
+ $run_fio &
+ local retcode=0
+ wait_for_finish $! || retcode=$?
+ # 9. Check that fio job run on hot-removed device stopped.
+ # Expected: Fio should return error message and return code != 0.
+ check_fio_retcode "Blk hotremove test case 4: Iteration 3." 1 $retcode
+
+ vm_shutdown_all
+ remove_vhost_controllers
+ add_nvme "HotInNvme3" "$traddr"
+ sleep 1
+}
+
+# Test Case 5
+function blk_hotremove_tc5() {
+ echo "Blk hotremove test case 5"
+ # 1. Use rpc command to create blk controllers.
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p0.0 HotInNvme3n1p0
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p1.0 Nvme1n1p0
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p2.1 Nvme1n1p1
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p3.1 Nvme1n1p2
+ # 2. Run two VM, attached to blk controllers.
+ vm_run_with_arg "0 1"
+ vms_prepare "0 1"
+
+ prepare_fio_cmd_tc1 "0"
+ # 3. Run FIO I/O traffic on first VM with verification enabled on both NVMe disks.
+ $run_fio &
+ local last_pid=$!
+ sleep 3
+ # 4. Run the command to hot remove of first NVMe disk.
+ delete_nvme "HotInNvme3"
+ local retcode=0
+ wait_for_finish $last_pid || retcode=$?
+ # 5. Check that fio job run on hot-removed device stopped.
+ # Expected: Fio should return error message and return code != 0.
+ check_fio_retcode "Blk hotremove test case 5: Iteration 1." 1 $retcode
+
+ # 6. Reboot VM.
+ reboot_all_and_prepare "0"
+ local retcode=0
+ # 7. Run FIO I/O traffic with verification enabled on removed NVMe disk.
+ $run_fio &
+ wait_for_finish $! || retcode=$?
+ # 8. Check that fio job run on hot-removed device stopped.
+ # Expected: Fio should return error message and return code != 0.
+ check_fio_retcode "Blk hotremove test case 5: Iteration 2." 1 $retcode
+ vm_shutdown_all
+ remove_vhost_controllers
+ add_nvme "HotInNvme4" "$traddr"
+ sleep 1
+}
+
+vms_setup
+blk_hotremove_tc1
+blk_hotremove_tc2
+blk_hotremove_tc3
+blk_hotremove_tc4
+blk_hotremove_tc5
diff --git a/src/spdk/test/vhost/hotplug/common.sh b/src/spdk/test/vhost/hotplug/common.sh
new file mode 100644
index 00000000..a94b06cf
--- /dev/null
+++ b/src/spdk/test/vhost/hotplug/common.sh
@@ -0,0 +1,230 @@
+dry_run=false
+no_shutdown=false
+fio_bin="fio"
+fio_jobs="$BASE_DIR/fio_jobs/"
+test_type=spdk_vhost_scsi
+reuse_vms=false
+vms=()
+used_vms=""
+disk_split=""
+x=""
+scsi_hot_remove_test=0
+blk_hot_remove_test=0
+
+
+function usage() {
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for doing automated hotattach/hotdetach test"
+ echo "Usage: $(basename $1) [OPTIONS]"
+ echo
+ echo "-h, --help print help and exit"
+ echo " --test-type=TYPE Perform specified test:"
+ echo " virtio - test host virtio-scsi-pci using file as disk image"
+ echo " kernel_vhost - use kernel driver vhost-scsi"
+ echo " spdk_vhost_scsi - use spdk vhost scsi"
+ echo " spdk_vhost_blk - use spdk vhost block"
+ echo "-x set -x for script debug"
+ echo " --fio-bin=FIO Use specific fio binary (will be uploaded to VM)"
+ echo " --fio-jobs= Fio configs to use for tests. Can point to a directory or"
+ echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: $TEST_DIR]"
+ echo " --vm=NUM[,OS][,DISKS] VM configuration. This parameter might be used more than once:"
+ echo " NUM - VM number (mandatory)"
+ echo " OS - VM os disk path (optional)"
+ echo " DISKS - VM os test disks/devices path (virtio - optional, kernel_vhost - mandatory)"
+ echo " --scsi-hotremove-test Run scsi hotremove tests"
+ exit 0
+}
+
+while getopts 'xh-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 ;;
+ work-dir=*) TEST_DIR="${OPTARG#*=}" ;;
+ fio-bin=*) fio_bin="${OPTARG#*=}" ;;
+ fio-jobs=*) fio_jobs="${OPTARG#*=}" ;;
+ test-type=*) test_type="${OPTARG#*=}" ;;
+ vm=*) vms+=("${OPTARG#*=}") ;;
+ scsi-hotremove-test) scsi_hot_remove_test=1 ;;
+ blk-hotremove-test) blk_hot_remove_test=1 ;;
+ *) usage $0 "Invalid argument '$OPTARG'" ;;
+ esac
+ ;;
+ h) usage $0 ;;
+ x) set -x
+ x="-x" ;;
+ *) usage $0 "Invalid argument '$OPTARG'"
+ esac
+done
+shift $(( OPTIND - 1 ))
+
+fio_job=$BASE_DIR/fio_jobs/default_integrity.job
+tmp_attach_job=$BASE_DIR/fio_jobs/fio_attach.job.tmp
+tmp_detach_job=$BASE_DIR/fio_jobs/fio_detach.job.tmp
+. $BASE_DIR/../common/common.sh
+
+rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock"
+
+function print_test_fio_header() {
+ notice "==============="
+ notice ""
+ notice "Testing..."
+
+ notice "Running fio jobs ..."
+ if [ $# -gt 0 ]; then
+ echo $1
+ fi
+}
+
+function run_vhost() {
+ notice "==============="
+ notice ""
+ notice "running SPDK"
+ notice ""
+ spdk_vhost_run --conf-path=$BASE_DIR
+ notice ""
+}
+
+function vms_setup() {
+ for vm_conf in ${vms[@]}; do
+ IFS=',' read -ra conf <<< "$vm_conf"
+ if [[ x"${conf[0]}" == x"" ]] || ! assert_number ${conf[0]}; then
+ fail "invalid VM configuration syntax $vm_conf"
+ fi
+
+ # Sanity check if VM is not defined twice
+ for vm_num in $used_vms; do
+ if [[ $vm_num -eq ${conf[0]} ]]; then
+ fail "VM$vm_num defined more than twice ( $(printf "'%s' " "${vms[@]}"))!"
+ fi
+ done
+
+ used_vms+=" ${conf[0]}"
+
+ setup_cmd="vm_setup --disk-type=$test_type --force=${conf[0]}"
+ [[ x"${conf[1]}" != x"" ]] && setup_cmd+=" --os=${conf[1]}"
+ [[ x"${conf[2]}" != x"" ]] && setup_cmd+=" --disks=${conf[2]}"
+ $setup_cmd
+ done
+}
+
+function vm_run_with_arg() {
+ vm_run $@
+ vm_wait_for_boot 600 $@
+}
+
+function vms_setup_and_run() {
+ vms_setup
+ vm_run_with_arg $@
+}
+
+function vms_prepare() {
+ for vm_num in $1; do
+ vm_dir=$VM_BASE_DIR/$vm_num
+
+ qemu_mask_param="VM_${vm_num}_qemu_mask"
+
+ host_name="VM-${vm_num}-${!qemu_mask_param}"
+ notice "Setting up hostname: $host_name"
+ vm_ssh $vm_num "hostname $host_name"
+ vm_start_fio_server --fio-bin=$fio_bin $readonly $vm_num
+ done
+}
+
+function vms_reboot_all() {
+ notice "Rebooting all vms "
+ for vm_num in $1; do
+ vm_ssh $vm_num "reboot" || true
+ while vm_os_booted $vm_num; do
+ sleep 0.5
+ done
+ done
+
+ vm_wait_for_boot 300 $1
+}
+
+function check_fio_retcode() {
+ local fio_retcode=$3
+ echo $1
+ local retcode_expected=$2
+ if [ $retcode_expected == 0 ]; then
+ if [ $fio_retcode != 0 ]; then
+ error " Fio test ended with error."
+ else
+ notice " Fio test ended with success."
+ fi
+ else
+ if [ $fio_retcode != 0 ]; then
+ notice " Fio test ended with expected error."
+ else
+ error " Fio test ended with unexpected success."
+ fi
+ fi
+}
+
+function wait_for_finish() {
+ local wait_for_pid=$1
+ local sequence=${2:-30}
+ for i in `seq 1 $sequence`; do
+ if kill -0 $wait_for_pid; then
+ sleep 0.5
+ continue
+ else
+ break
+ fi
+ done
+ if kill -0 $wait_for_pid; then
+ error "Timeout for fio command"
+ fi
+
+ wait $wait_for_pid
+}
+
+
+function reboot_all_and_prepare() {
+ vms_reboot_all "$1"
+ vms_prepare "$1"
+}
+
+function post_test_case() {
+ vm_shutdown_all
+ spdk_vhost_kill
+}
+
+function on_error_exit() {
+ set +e
+ echo "Error on $1 - $2"
+ post_test_case
+ print_backtrace
+ exit 1
+}
+
+function check_disks() {
+ if [ "$1" == "$2" ]; then
+ echo "Disk has not been deleted"
+ exit 1
+ fi
+}
+
+function get_traddr() {
+ local nvme_name=$1
+ local nvme="$( $SPDK_BUILD_DIR/scripts/gen_nvme.sh )"
+ while read -r line; do
+ if [[ $line == *"TransportID"* ]] && [[ $line == *$nvme_name* ]]; then
+ local word_array=($line)
+ for word in "${word_array[@]}"; do
+ if [[ $word == *"traddr"* ]]; then
+ traddr=$( echo $word | sed 's/traddr://' | sed 's/"//' )
+ fi
+ done
+ fi
+ done <<< "$nvme"
+}
+
+function delete_nvme() {
+ $rpc_py delete_nvme_controller $1
+}
+
+function add_nvme() {
+ $rpc_py construct_nvme_bdev -b $1 -t PCIe -a $2
+}
diff --git a/src/spdk/test/vhost/hotplug/fio_jobs/default_integrity.job b/src/spdk/test/vhost/hotplug/fio_jobs/default_integrity.job
new file mode 100644
index 00000000..136fe902
--- /dev/null
+++ b/src/spdk/test/vhost/hotplug/fio_jobs/default_integrity.job
@@ -0,0 +1,16 @@
+[global]
+blocksize=4k
+iodepth=512
+iodepth_batch=128
+iodepth_low=256
+ioengine=libaio
+group_reporting
+thread
+numjobs=1
+direct=1
+rw=randwrite
+do_verify=1
+verify=md5
+verify_backlog=1024
+time_based=1
+runtime=10
diff --git a/src/spdk/test/vhost/hotplug/scsi_hotattach.sh b/src/spdk/test/vhost/hotplug/scsi_hotattach.sh
new file mode 100755
index 00000000..e9d851f6
--- /dev/null
+++ b/src/spdk/test/vhost/hotplug/scsi_hotattach.sh
@@ -0,0 +1,104 @@
+#!/usr/bin/env bash
+set -e
+BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)"
+
+. $BASE_DIR/common.sh
+
+function prepare_fio_cmd_tc1() {
+ print_test_fio_header
+
+ run_fio="$fio_bin --eta=never "
+ for vm_num in $1; do
+ cp $fio_job $tmp_attach_job
+ vm_dir=$VM_BASE_DIR/$vm_num
+ vm_check_scsi_location $vm_num
+ for disk in $SCSI_DISK; do
+ echo "[nvme-host$disk]" >> $tmp_attach_job
+ echo "filename=/dev/$disk" >> $tmp_attach_job
+ done
+ vm_scp $vm_num $tmp_attach_job 127.0.0.1:/root/default_integrity_discs.job
+ run_fio+="--client=127.0.0.1,$(vm_fio_socket ${vm_num}) --remote-config /root/default_integrity_discs.job "
+ rm $tmp_attach_job
+ done
+}
+
+# Check if fio test passes on device attached to first controller.
+function hotattach_tc1() {
+ notice "Hotattach test case 1"
+
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 0 Nvme0n1p0
+
+ sleep 3
+ prepare_fio_cmd_tc1 "0"
+ $run_fio
+ check_fio_retcode "Hotattach test case 1: Iteration 1." 0 $?
+}
+
+# Run fio test for previously attached device.
+# During test attach another device to first controller and check fio status.
+function hotattach_tc2() {
+ notice "Hotattach test case 2"
+ prepare_fio_cmd_tc1 "0"
+
+ $run_fio &
+ last_pid=$!
+ sleep 3
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 1 Nvme0n1p1
+ wait $last_pid
+ check_fio_retcode "Hotattach test case 2: Iteration 1." 0 $?
+}
+
+# Run fio test for previously attached devices.
+# During test attach another device to second controller and check fio status.
+function hotattach_tc3() {
+ notice "Hotattach test case 3"
+ prepare_fio_cmd_tc1 "0"
+
+ $run_fio &
+ last_pid=$!
+ sleep 3
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p1.0 0 Nvme0n1p2
+ wait $last_pid
+ check_fio_retcode "Hotattach test case 3: Iteration 1." 0 $?
+}
+
+# Run fio test for previously attached devices.
+# During test attach another device to third controller(VM2) and check fio status.
+# At the end after rebooting VMs run fio test for all devices and check fio status.
+function hotattach_tc4() {
+ notice "Hotattach test case 4"
+
+ prepare_fio_cmd_tc1 "0"
+
+ $run_fio &
+ last_pid=$!
+ sleep 3
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p2.1 0 Nvme0n1p3
+ wait $last_pid
+ check_fio_retcode "Hotattach test case 4: Iteration 1." 0 $?
+
+ prepare_fio_cmd_tc1 "0 1"
+ $run_fio
+ check_fio_retcode "Hotattach test case 4: Iteration 2." 0 $?
+
+ reboot_all_and_prepare "0 1"
+
+ prepare_fio_cmd_tc1 "0 1"
+ $run_fio
+ check_fio_retcode "Hotattach test case 4: Iteration 3." 0 $?
+}
+
+function cleanup_after_tests() {
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p0.0 0
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p0.0 1
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p1.0 0
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p2.1 0
+ $rpc_py delete_nvme_controller Nvme0
+}
+
+hotattach_tc1
+hotattach_tc2
+hotattach_tc3
+hotattach_tc4
+cleanup_after_tests
diff --git a/src/spdk/test/vhost/hotplug/scsi_hotdetach.sh b/src/spdk/test/vhost/hotplug/scsi_hotdetach.sh
new file mode 100755
index 00000000..45c948d9
--- /dev/null
+++ b/src/spdk/test/vhost/hotplug/scsi_hotdetach.sh
@@ -0,0 +1,241 @@
+#!/usr/bin/env bash
+
+set -e
+BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $BASE_DIR/../../../../ && pwd)"
+
+. $BASE_DIR/common.sh
+
+function get_first_disk() {
+ vm_check_scsi_location $1
+ disk_array=( $SCSI_DISK )
+ eval "$2=${disk_array[0]}"
+}
+
+function check_disks() {
+ if [ "$1" == "$2" ]; then
+ fail "Disk has not been deleted"
+ fi
+}
+
+function prepare_fio_cmd_tc1_iter1() {
+ print_test_fio_header
+
+ run_fio="$fio_bin --eta=never "
+ for vm_num in $1; do
+ cp $fio_job $tmp_detach_job
+ vm_dir=$VM_BASE_DIR/$vm_num
+ vm_check_scsi_location $vm_num
+ for disk in $SCSI_DISK; do
+ echo "[nvme-host$disk]" >> $tmp_detach_job
+ echo "filename=/dev/$disk" >> $tmp_detach_job
+ done
+ vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/default_integrity_4discs.job
+ run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity_4discs.job "
+ rm $tmp_detach_job
+ done
+}
+
+function prepare_fio_cmd_tc1_iter2() {
+ print_test_fio_header
+
+ for vm_num in 2; do
+ cp $fio_job $tmp_detach_job
+ vm_dir=$VM_BASE_DIR/$vm_num
+ vm_check_scsi_location $vm_num
+ for disk in $SCSI_DISK; do
+ echo "[nvme-host$disk]" >> $tmp_detach_job
+ echo "filename=/dev/$disk" >> $tmp_detach_job
+ done
+ vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/default_integrity_3discs.job
+ rm $tmp_detach_job
+ done
+ run_fio="$fio_bin --eta=never "
+ for vm_num in $used_vms; do
+ if [ $vm_num == 2 ]; then
+ run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity_3discs.job "
+ continue
+ fi
+ run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity_4discs.job "
+ done
+}
+
+function prepare_fio_cmd_tc2_iter1() {
+ print_test_fio_header
+
+ run_fio="$fio_bin --eta=never "
+ for vm_num in $1; do
+ cp $fio_job $tmp_detach_job
+ vm_dir=$VM_BASE_DIR/$vm_num
+ vm_check_scsi_location $vm_num
+ disk_array=($SCSI_DISK)
+ disk=${disk_array[0]}
+ echo "[nvme-host$disk]" >> $tmp_detach_job
+ echo "filename=/dev/$disk" >> $tmp_detach_job
+ vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/default_integrity.job
+ run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity.job "
+ rm $tmp_detach_job
+ done
+}
+
+function prepare_fio_cmd_tc2_iter2() {
+ print_test_fio_header
+
+ run_fio="$fio_bin --eta=never "
+ for vm_num in $1; do
+ cp $fio_job $tmp_detach_job
+ if [ $vm_num == 2 ]; then
+ vm_job_name=default_integrity_3discs.job
+ else
+ vm_job_name=default_integrity_4discs.job
+ fi
+ vm_dir=$VM_BASE_DIR/$vm_num
+ vm_check_scsi_location $vm_num
+ for disk in $SCSI_DISK; do
+ echo "[nvme-host$disk]" >> $tmp_detach_job
+ echo "filename=/dev/$disk" >> $tmp_detach_job
+ done
+ vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/$vm_job_name
+ run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/${vm_job_name} "
+ rm $tmp_detach_job
+ done
+}
+
+
+function prepare_fio_cmd_tc3_iter1() {
+ print_test_fio_header
+
+ run_fio="$fio_bin --eta=never "
+ for vm_num in $1; do
+ cp $fio_job $tmp_detach_job
+ if [ $vm_num == 2 ]; then
+ vm_job_name=default_integrity_3discs.job
+ else
+ vm_job_name=default_integrity_4discs.job
+ fi
+ vm_dir=$VM_BASE_DIR/$vm_num
+ vm_check_scsi_location $vm_num
+ j=1
+ for disk in $SCSI_DISK; do
+ if [ $vm_num == 2 ]; then
+ if [ $j == 1 ]; then
+ (( j++ ))
+ continue
+ fi
+ fi
+ echo "[nvme-host$disk]" >> $tmp_detach_job
+ echo "filename=/dev/$disk" >> $tmp_detach_job
+ (( j++ ))
+ done
+ vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/$vm_job_name
+ run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/$vm_job_name "
+ rm $tmp_detach_job
+ done
+}
+
+# During fio test for all devices remove first device from fifth controller and check if fio fails.
+# Also check if disc has been removed from VM.
+function hotdetach_tc1() {
+ notice "Hotdetach test case 1"
+ first_disk=""
+ get_first_disk "2" first_disk
+ prepare_fio_cmd_tc1_iter1 "2 3"
+ $run_fio &
+ last_pid=$!
+ sleep 3
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 0
+ set +xe
+ wait $last_pid
+ check_fio_retcode "Hotdetach test case 1: Iteration 1." 1 $?
+ set -xe
+ second_disk=""
+ get_first_disk "2" second_disk
+ check_disks $first_disk $second_disk
+ clear_after_tests
+}
+
+# During fio test for device from third VM remove first device from fifth controller and check if fio fails.
+# Also check if disc has been removed from VM.
+function hotdetach_tc2() {
+ notice "Hotdetach test case 2"
+ sleep 2
+ first_disk=""
+ get_first_disk "2" first_disk
+ prepare_fio_cmd_tc2_iter1 "2"
+ $run_fio &
+ last_pid=$!
+ sleep 3
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 0
+ set +xe
+ wait $last_pid
+ check_fio_retcode "Hotdetach test case 2: Iteration 1." 1 $?
+ set -xe
+ second_disk=""
+ get_first_disk "2" second_disk
+ check_disks $first_disk $second_disk
+ clear_after_tests
+}
+
+# Run fio test for all devices except one, then remove this device and check if fio passes.
+# Also check if disc has been removed from VM.
+function hotdetach_tc3() {
+ notice "Hotdetach test case 3"
+ sleep 2
+ first_disk=""
+ get_first_disk "2" first_disk
+ prepare_fio_cmd_tc3_iter1 "2 3"
+ $run_fio &
+ last_pid=$!
+ sleep 3
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 0
+ wait $last_pid
+ check_fio_retcode "Hotdetach test case 3: Iteration 1." 0 $?
+ second_disk=""
+ get_first_disk "2" second_disk
+ check_disks $first_disk $second_disk
+ clear_after_tests
+}
+
+# Run fio test for all devices except one and run separate fio test for this device.
+# Check if first fio test passes and second fio test fails.
+# Also check if disc has been removed from VM.
+# After reboot run fio test for remaining devices and check if fio passes.
+function hotdetach_tc4() {
+ notice "Hotdetach test case 4"
+ sleep 2
+ first_disk=""
+ get_first_disk "2" first_disk
+ prepare_fio_cmd_tc2_iter1 "2"
+ $run_fio &
+ first_fio_pid=$!
+ prepare_fio_cmd_tc3_iter1 "2 3"
+ $run_fio &
+ second_fio_pid=$!
+ sleep 3
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 0
+ set +xe
+ wait $first_fio_pid
+ check_fio_retcode "Hotdetach test case 4: Iteration 1." 1 $?
+ set -xe
+ wait $second_fio_pid
+ check_fio_retcode "Hotdetach test case 4: Iteration 2." 0 $?
+ second_disk=""
+ get_first_disk "2" second_disk
+ check_disks $first_disk $second_disk
+
+ reboot_all_and_prepare "2 3"
+ sleep 2
+ prepare_fio_cmd_tc2_iter2 "2 3"
+ $run_fio
+ check_fio_retcode "Hotdetach test case 4: Iteration 3." 0 $?
+ clear_after_tests
+}
+
+function clear_after_tests() {
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p4.2 0 Nvme0n1p8
+}
+
+hotdetach_tc1
+hotdetach_tc2
+hotdetach_tc3
+hotdetach_tc4
diff --git a/src/spdk/test/vhost/hotplug/scsi_hotplug.sh b/src/spdk/test/vhost/hotplug/scsi_hotplug.sh
new file mode 100755
index 00000000..ab429c1e
--- /dev/null
+++ b/src/spdk/test/vhost/hotplug/scsi_hotplug.sh
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+set -e
+BASE_DIR=$(readlink -f $(dirname $0))
+. $BASE_DIR/common.sh
+
+if [[ $scsi_hot_remove_test == 1 ]] && [[ $blk_hot_remove_test == 1 ]]; then
+ notice "Vhost-scsi and vhost-blk hotremove tests cannot be run together"
+fi
+
+# Add split section into vhost config
+function gen_config() {
+ cp $BASE_DIR/vhost.conf.base $BASE_DIR/vhost.conf.in
+ cat << END_OF_CONFIG >> $BASE_DIR/vhost.conf.in
+[Split]
+ Split Nvme0n1 16
+ Split Nvme1n1 20
+ Split HotInNvme0n1 2
+ Split HotInNvme1n1 2
+ Split HotInNvme2n1 2
+ Split HotInNvme3n1 2
+END_OF_CONFIG
+}
+
+# Run spdk by calling run_vhost from hotplug/common.sh.
+# Then prepare vhost with rpc calls and setup and run 4 VMs.
+function pre_hot_attach_detach_test_case() {
+ used_vms=""
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p0.0
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p1.0
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p2.1
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p3.1
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p4.2
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p5.2
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p6.3
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p7.3
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p4.2 0 Nvme0n1p8
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p4.2 1 Nvme0n1p9
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p5.2 0 Nvme0n1p10
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p5.2 1 Nvme0n1p11
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p6.3 0 Nvme0n1p12
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p6.3 1 Nvme0n1p13
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p7.3 0 Nvme0n1p14
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p7.3 1 Nvme0n1p15
+ vms_setup_and_run "0 1 2 3"
+ vms_prepare "0 1 2 3"
+}
+
+function clear_vhost_config() {
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 0
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p4.2 1
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p5.2 0
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p5.2 1
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p6.3 0
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p6.3 1
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p7.3 0
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p7.3 1
+ $rpc_py remove_vhost_controller naa.Nvme0n1p0.0
+ $rpc_py remove_vhost_controller naa.Nvme0n1p1.0
+ $rpc_py remove_vhost_controller naa.Nvme0n1p2.1
+ $rpc_py remove_vhost_controller naa.Nvme0n1p3.1
+ $rpc_py remove_vhost_controller naa.Nvme0n1p4.2
+ $rpc_py remove_vhost_controller naa.Nvme0n1p5.2
+ $rpc_py remove_vhost_controller naa.Nvme0n1p6.3
+ $rpc_py remove_vhost_controller naa.Nvme0n1p7.3
+}
+
+trap 'error_exit "${FUNCNAME}" "${LINENO}"' ERR
+gen_config
+# Hotremove/hotattach/hotdetach test cases prerequisites
+# 1. Run vhost with 2 NVMe disks.
+run_vhost
+rm $BASE_DIR/vhost.conf.in
+if [[ $scsi_hot_remove_test == 0 ]] && [[ $blk_hot_remove_test == 0 ]]; then
+ pre_hot_attach_detach_test_case
+ $BASE_DIR/scsi_hotattach.sh --fio-bin=$fio_bin &
+ first_script=$!
+ $BASE_DIR/scsi_hotdetach.sh --fio-bin=$fio_bin &
+ second_script=$!
+ wait $first_script
+ wait $second_script
+ vm_shutdown_all
+ clear_vhost_config
+fi
+if [[ $scsi_hot_remove_test == 1 ]]; then
+ source $BASE_DIR/scsi_hotremove.sh
+fi
+if [[ $blk_hot_remove_test == 1 ]]; then
+ source $BASE_DIR/blk_hotremove.sh
+fi
+post_test_case
diff --git a/src/spdk/test/vhost/hotplug/scsi_hotremove.sh b/src/spdk/test/vhost/hotplug/scsi_hotremove.sh
new file mode 100644
index 00000000..829eb3f6
--- /dev/null
+++ b/src/spdk/test/vhost/hotplug/scsi_hotremove.sh
@@ -0,0 +1,232 @@
+set -xe
+
+# Vhost SCSI hotremove tests
+#
+# # Objective
+# The purpose of these tests is to verify that SPDK vhost remains stable during
+# hot-remove operations performed on SCSI controllers devices.
+# Hot-remove is a scenario where a NVMe device is removed when already in use.
+# Tests consist of 4 test cases.
+#
+# # Test cases description
+# 1. FIO I/O traffic is run during hot-remove operations.
+# By default FIO uses default_integrity*.job config files located in
+# test/vhost/hotplug/fio_jobs directory.
+# 2. FIO mode of operation is random write (randwrite) with verification enabled
+# which results in also performing read operations.
+
+function prepare_fio_cmd_tc1() {
+ print_test_fio_header
+
+ run_fio="$fio_bin --eta=never "
+ for vm_num in $1; do
+ cp $fio_job $tmp_detach_job
+ vm_dir=$VM_BASE_DIR/$vm_num
+ vm_check_scsi_location $vm_num
+ for disk in $SCSI_DISK; do
+ echo "[nvme-host$disk]" >> $tmp_detach_job
+ echo "filename=/dev/$disk" >> $tmp_detach_job
+ echo "size=100%" >> $tmp_detach_job
+ done
+ vm_scp "$vm_num" $tmp_detach_job 127.0.0.1:/root/default_integrity_2discs.job
+ run_fio+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/default_integrity_2discs.job "
+ rm $tmp_detach_job
+ done
+}
+
+# Vhost SCSI hot-remove test cases.
+
+# Test Case 1
+function scsi_hotremove_tc1() {
+ echo "Scsi hotremove test case 1"
+ traddr=""
+ get_traddr "Nvme0"
+ # 1. Run the command to hot remove NVMe disk.
+ delete_nvme "Nvme0"
+ # 2. If vhost had crashed then tests would stop running
+ sleep 1
+ add_nvme "HotInNvme0" "$traddr"
+}
+
+# Test Case 2
+function scsi_hotremove_tc2() {
+ echo "Scsi hotremove test case 2"
+ # 1. Attach split NVMe bdevs to scsi controller.
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 0 HotInNvme0n1p0
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p1.0 0 Nvme1n1p0
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p2.1 0 HotInNvme0n1p1
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p3.1 0 Nvme1n1p1
+
+ # 2. Run two VMs, attached to scsi controllers.
+ vms_setup
+ vm_run_with_arg 0 1
+ vms_prepare "0 1"
+
+ vm_check_scsi_location "0"
+ local disks="$SCSI_DISK"
+
+ traddr=""
+ get_traddr "Nvme0"
+ prepare_fio_cmd_tc1 "0 1"
+ # 3. Run FIO I/O traffic with verification enabled on on both NVMe disks in VM.
+ $run_fio &
+ local last_pid=$!
+ sleep 3
+ # 4. Run the command to hot remove NVMe disk.
+ delete_nvme "HotInNvme0"
+
+ # 5. Check that fio job run on hot-remove device stopped on VM.
+ # Expected: Fio should return error message and return code != 0.
+ wait_for_finish $last_pid || retcode=$?
+ check_fio_retcode "Scsi hotremove test case 2: Iteration 1." 1 $retcode
+
+ # 6. Check if removed devices are gone from VM.
+ vm_check_scsi_location "0"
+ local new_disks="$SCSI_DISK"
+ check_disks "$disks" "$new_disks"
+ # 7. Reboot both VMs.
+ reboot_all_and_prepare "0 1"
+ # 8. Run FIO I/O traffic with verification enabled on on both VMs.
+ local retcode=0
+ $run_fio &
+ wait_for_finish $! || retcode=$?
+ # 9. Check that fio job run on hot-remove device stopped on both VMs.
+ # Expected: Fio should return error message and return code != 0.
+ check_fio_retcode "Scsi hotremove test case 2: Iteration 2." 1 $retcode
+ vm_shutdown_all
+ add_nvme "HotInNvme1" "$traddr"
+ sleep 1
+}
+
+# Test Case 3
+function scsi_hotremove_tc3() {
+ echo "Scsi hotremove test case 3"
+ # 1. Attach added NVMe bdev to scsi controller.
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 0 HotInNvme1n1p0
+ # 2. Run two VM, attached to scsi controllers.
+ vm_run_with_arg 0 1
+ vms_prepare "0 1"
+ vm_check_scsi_location "0"
+ local disks="$SCSI_DISK"
+ traddr=""
+ get_traddr "Nvme0"
+ # 3. Run FIO I/O traffic with verification enabled on on both NVMe disks in VMs.
+ prepare_fio_cmd_tc1 "0"
+ $run_fio &
+ local last_pid=$!
+ sleep 3
+ # 4. Run the command to hot remove NVMe disk.
+ delete_nvme "HotInNvme1"
+ # 5. Check that fio job run on hot-remove device stopped on first VM.
+ # Expected: Fio should return error message and return code != 0.
+ wait_for_finish $last_pid || retcode=$?
+ check_fio_retcode "Scsi hotremove test case 3: Iteration 1." 1 $retcode
+ # 6. Check if removed devices are gone from lsblk.
+ vm_check_scsi_location "0"
+ local new_disks="$SCSI_DISK"
+ check_disks "$disks" "$new_disks"
+ # 7. Reboot both VMs.
+ reboot_all_and_prepare "0 1"
+ # 8. Run FIO I/O traffic with verification enabled on on both VMs.
+ local retcode=0
+ $run_fio &
+ wait_for_finish $! || retcode=$?
+ # 9. Check that fio job run on hot-remove device stopped on both VMs.
+ # Expected: Fio should return error message and return code != 0.
+ check_fio_retcode "Scsi hotremove test case 3: Iteration 2." 1 $retcode
+ vm_shutdown_all
+ add_nvme "HotInNvme2" "$traddr"
+ sleep 1
+}
+
+# Test Case 4
+function scsi_hotremove_tc4() {
+ echo "Scsi hotremove test case 4"
+ # 1. Attach NVMe bdevs to scsi controllers.
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 0 HotInNvme2n1p0
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p2.1 0 HotInNvme2n1p1
+ # 2. Run two VMs, attach to scsi controller.
+ vm_run_with_arg 0 1
+ vms_prepare "0 1"
+
+ # 3. Run FIO I/O traffic with verification enabled on first VM.
+ vm_check_scsi_location "0"
+ local disks_vm0="$SCSI_DISK"
+ # 4. Run FIO I/O traffic with verification enabled on second VM.
+ prepare_fio_cmd_tc1 "0"
+ $run_fio &
+ last_pid_vm0=$!
+
+ vm_check_scsi_location "1"
+ local disks_vm1="$SCSI_DISK"
+ prepare_fio_cmd_tc1 "1"
+ $run_fio &
+ local last_pid_vm1=$!
+ prepare_fio_cmd_tc1 "0 1"
+ sleep 3
+ # 5. Run the command to hot remove NVMe disk.
+ traddr=""
+ get_traddr "Nvme0"
+ delete_nvme "HotInNvme2"
+ # 6. Check that fio job run on hot-removed devices stopped.
+ # Expected: Fio should return error message and return code != 0.
+ local retcode_vm0=0
+ wait_for_finish $last_pid_vm0 || retcode_vm0=$?
+ local retcode_vm1=0
+ wait_for_finish $last_pid_vm1 || retcode_vm1=$?
+ check_fio_retcode "Scsi hotremove test case 4: Iteration 1." 1 $retcode_vm0
+ check_fio_retcode "Scsi hotremove test case 4: Iteration 2." 1 $retcode_vm1
+
+ # 7. Check if removed devices are gone from lsblk.
+ vm_check_scsi_location "0"
+ local new_disks_vm0="$SCSI_DISK"
+ check_disks "$disks_vm0" "$new_disks_vm0"
+ vm_check_scsi_location "1"
+ local new_disks_vm1="$SCSI_DISK"
+ check_disks "$disks_vm1" "$new_disks_vm1"
+
+ # 8. Reboot both VMs.
+ reboot_all_and_prepare "0 1"
+ # 9. Run FIO I/O traffic with verification enabled on on not-removed NVMe disk.
+ local retcode=0
+ $run_fio &
+ wait_for_finish $! || retcode=$?
+ # 10. Check that fio job run on hot-removed device stopped.
+ # Expect: Fio should return error message and return code != 0.
+ check_fio_retcode "Scsi hotremove test case 4: Iteration 3." 1 $retcode
+ prepare_fio_cmd_tc1 "0 1"
+ # 11. Run FIO I/O traffic with verification enabled on on not-removed NVMe disk.
+ local retcode=0
+ $run_fio &
+ wait_for_finish $! || retcode=$?
+ # 12. Check finished status FIO. Write and read in the not-removed.
+ # NVMe disk should be successful.
+ # Expected: Fio should return return code == 0.
+ check_fio_retcode "Scsi hotremove test case 4: Iteration 4." 0 $retcode
+ vm_shutdown_all
+ add_nvme "HotInNvme3" "$traddr"
+ sleep 1
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p1.0 0
+ $rpc_py remove_vhost_scsi_target naa.Nvme0n1p3.1 0
+}
+
+function pre_scsi_hotremove_test_case() {
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p0.0
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p1.0
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p2.1
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p3.1
+}
+
+function post_scsi_hotremove_test_case() {
+ $rpc_py remove_vhost_controller naa.Nvme0n1p0.0
+ $rpc_py remove_vhost_controller naa.Nvme0n1p1.0
+ $rpc_py remove_vhost_controller naa.Nvme0n1p2.1
+ $rpc_py remove_vhost_controller naa.Nvme0n1p3.1
+}
+
+pre_scsi_hotremove_test_case
+scsi_hotremove_tc1
+scsi_hotremove_tc2
+scsi_hotremove_tc3
+scsi_hotremove_tc4
+post_scsi_hotremove_test_case
diff --git a/src/spdk/test/vhost/hotplug/test_plan.md b/src/spdk/test/vhost/hotplug/test_plan.md
new file mode 100644
index 00000000..0cbc5042
--- /dev/null
+++ b/src/spdk/test/vhost/hotplug/test_plan.md
@@ -0,0 +1,86 @@
+#Vhost hotattach and hotdetach test plan
+
+## Objective
+The purpose of these tests is to verify that SPDK vhost remains stable during
+hot-attach and hot-detach operations performed on SCSI controllers devices.
+Hot-attach is a scenario where a device is added to controller already in use by
+guest VM, while in hot-detach device is removed from controller when already in use.
+
+## Test Cases Description
+1. FIO I/O traffic is run during hot-attach and detach operations.
+By default FIO uses default_integrity*.job config files located in
+test/vhost/hotfeatures/fio_jobs directory.
+2. FIO mode of operation in random write (randwrite) with verification enabled
+which results in also performing read operations.
+3. Test case descriptions below contain manual steps for testing.
+Automated tests are located in test/vhost/hotfeatures.
+
+### Hotattach, Hotdetach Test Cases prerequisites
+1. Run vhost with 8 empty controllers. Prepare 16 nvme disks.
+If you don't have 16 disks use split.
+2. In test cases fio status is checked after every run if there are any errors.
+
+### Hotattach Test Cases prerequisites
+1. Run vms, first with ctrlr-1 and ctrlr-2 and second one with ctrlr-3 and ctrlr-4.
+
+## Test Case 1
+1. Attach NVMe to Ctrlr 1
+2. Run fio integrity on attached device
+
+## Test Case 2
+1. Run fio integrity on attached device from test case 1
+2. During fio attach another NVMe to Ctrlr 1
+3. Run fio integrity on both devices
+
+## Test Case 3
+1. Run fio integrity on attached devices from previous test cases
+2. During fio attach NVMe to Ctrl2
+3. Run fio integrity on all devices
+
+## Test Case 4
+2. Run fio integrity on attached device from previous test cases
+3. During fio attach NVMe to Ctrl3/VM2
+4. Run fio integrity on all devices
+5. Reboot VMs
+6. Run fio integrity again on all devices
+
+
+### Hotdetach Test Cases prerequisites
+1. Run vms, first with ctrlr-5 and ctrlr-6 and second with ctrlr-7 and ctrlr-8.
+
+## Test Case 1
+1. Run fio on all devices
+2. Detatch NVMe from Ctrl5 during fio
+3. Check vhost or VMs did not crash
+4. Check that detatched device is gone from VM
+5. Check that fio job run on detached device stopped and failed
+
+## Test Case 2
+1. Attach NVMe to Ctrlr 5
+2. Run fio on 1 device from Ctrl 5
+3. Detatch NVMe from Ctrl5 during fio traffic
+4. Check vhost or VMs did not crash
+5. Check that fio job run on detached device stopped and failed
+6. Check that detatched device is gone from VM
+
+## Test Case 3
+1. Attach NVMe to Ctrlr 5
+2. Run fio with integrity on all devices, except one
+3. Detatch NVMe without traffic during fio running on other devices
+4. Check vhost or VMs did not crash
+5. Check that fio jobs did not fail
+6. Check that detatched device is gone from VM
+
+## Test Case 4
+1. Attach NVMe to Ctrlr 5
+2. Run fio on 1 device from Ctrl 5
+3. Run separate fio with integrity on all other devices (all VMs)
+4. Detatch NVMe from Ctrl1 during fio traffic
+5. Check vhost or VMs did not crash
+6. Check that fio job run on detached device stopped and failed
+7. Check that other fio jobs did not fail
+8. Check that detatched device is gone from VM
+9. Reboot VMs
+10. Check that detatched device is gone from VM
+11. Check that all other devices are in place
+12. Run fio integrity on all remianing devices
diff --git a/src/spdk/test/vhost/hotplug/vhost.conf.base b/src/spdk/test/vhost/hotplug/vhost.conf.base
new file mode 100644
index 00000000..4fa801d9
--- /dev/null
+++ b/src/spdk/test/vhost/hotplug/vhost.conf.base
@@ -0,0 +1,4 @@
+[Global]
+
+[Nvme]
+ HotplugEnable Yes
diff --git a/src/spdk/test/vhost/initiator/autotest.config b/src/spdk/test/vhost/initiator/autotest.config
new file mode 100644
index 00000000..61a1a242
--- /dev/null
+++ b/src/spdk/test/vhost/initiator/autotest.config
@@ -0,0 +1,5 @@
+vhost_0_reactor_mask=["0"]
+vhost_0_master_core=0
+
+VM_0_qemu_mask=1-10
+VM_0_qemu_numa_node=0
diff --git a/src/spdk/test/vhost/initiator/bdev.conf b/src/spdk/test/vhost/initiator/bdev.conf
new file mode 100644
index 00000000..7ea01a82
--- /dev/null
+++ b/src/spdk/test/vhost/initiator/bdev.conf
@@ -0,0 +1,21 @@
+[VirtioUser0]
+ Path naa.Nvme0n1_scsi0.0
+ Queues 8
+
+[VirtioUser1]
+ Path naa.Malloc0.0
+ Queues 8
+
+[VirtioUser2]
+ Path naa.Malloc1.0
+ Queues 8
+
+[VirtioUser3]
+ Path naa.Nvme0n1_blk0.0
+ Type Blk
+ Queues 8
+
+[VirtioUser4]
+ Path naa.Nvme0n1_blk1.0
+ Type Blk
+ Queues 8
diff --git a/src/spdk/test/vhost/initiator/bdev.fio b/src/spdk/test/vhost/initiator/bdev.fio
new file mode 100644
index 00000000..40520228
--- /dev/null
+++ b/src/spdk/test/vhost/initiator/bdev.fio
@@ -0,0 +1,51 @@
+[global]
+thread=1
+group_reporting=1
+direct=1
+norandommap=1
+time_based=1
+do_verify=1
+verify=md5
+verify_backlog=1024
+iodepth=128
+bs=4K
+runtime=10
+size=13%
+
+[job_randwrite]
+rw=randwrite
+name=randwrite
+
+[job_randrw]
+offset=13%
+rw=randrw
+name=randrw
+
+[job_write]
+offset=26%
+rw=write
+name=write
+
+[job_rw]
+offset=39%
+rw=rw
+name=rw
+
+[job_unmap_trim_sequential]
+offset=52%
+rw=trim
+trim_verify_zero=1
+name=unmap_trim_sequential
+
+[job_unmap_trim_random]
+offset=65%
+rw=randtrim
+trim_verify_zero=1
+name=unmap_trim_random
+
+[job_unmap_write]
+stonewall
+offset=52%
+size=26%
+rw=randwrite
+name=unmap_write
diff --git a/src/spdk/test/vhost/initiator/bdev_pci.conf b/src/spdk/test/vhost/initiator/bdev_pci.conf
new file mode 100644
index 00000000..0e47e88a
--- /dev/null
+++ b/src/spdk/test/vhost/initiator/bdev_pci.conf
@@ -0,0 +1,2 @@
+[VirtioPci]
+ Enable Yes
diff --git a/src/spdk/test/vhost/initiator/blockdev.sh b/src/spdk/test/vhost/initiator/blockdev.sh
new file mode 100755
index 00000000..b5ec3015
--- /dev/null
+++ b/src/spdk/test/vhost/initiator/blockdev.sh
@@ -0,0 +1,200 @@
+#!/usr/bin/env bash
+
+set -e
+INITIATOR_DIR=$(readlink -f $(dirname $0))
+[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $INITIATOR_DIR/../common && pwd)"
+ROOT_DIR=$(readlink -f $INITIATOR_DIR/../../..)
+
+PLUGIN_DIR=$ROOT_DIR/examples/bdev/fio_plugin
+FIO_PATH="/usr/src/fio"
+virtio_bdevs=""
+virtio_with_unmap=""
+os_image="/home/sys_sgsw/vhost_vm_image.qcow2"
+#different linux distributions have different versions of targetcli that have different names for ramdisk option
+targetcli_rd_name=""
+kernel_vhost_disk="naa.5012345678901234"
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Script for running vhost initiator tests."
+ echo "Usage: $(basename $1) [-h|--help] [--fiobin=PATH]"
+ echo "-h, --help Print help and exit"
+ echo " --vm_image=PATH Path to VM image used in these tests [default=$os_image]"
+ echo " --fiopath=PATH Path to fio directory on host [default=$FIO_PATH]"
+}
+
+while getopts 'h-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 && exit 0 ;;
+ fiopath=*) FIO_PATH="${OPTARG#*=}" ;;
+ vm_image=*) os_image="${OPTARG#*=}" ;;
+ *) usage $0 echo "Invalid argument '$OPTARG'" && exit 1 ;;
+ esac
+ ;;
+ h) usage $0 && exit 0 ;;
+ *) usage $0 "Invalid argument '$optchar'" && exit 1 ;;
+ esac
+done
+
+source $COMMON_DIR/common.sh
+source $INITIATOR_DIR/autotest.config
+PLUGIN_DIR=$ROOT_DIR/examples/bdev/fio_plugin
+RPC_PY="$ROOT_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock"
+
+if [ ! -x $FIO_PATH ]; then
+ error "Invalid path of fio binary"
+fi
+
+if [[ $EUID -ne 0 ]]; then
+ echo "INFO: Go away user come back as root"
+ exit 1
+fi
+
+if targetcli ls backstores | grep ramdisk ; then
+ targetcli_rd_name="ramdisk"
+elif targetcli ls backstores | grep rd_mcp ; then
+ targetcli_rd_name="rd_mcp"
+else
+ error "targetcli: cannot create a ramdisk.\
+ Neither backstores/ramdisk nor backstores/rd_mcp is available"
+fi
+
+function remove_kernel_vhost()
+{
+ targetcli "/vhost delete $kernel_vhost_disk"
+ targetcli "/backstores/$targetcli_rd_name delete ramdisk"
+}
+
+trap 'rm -f *.state $ROOT_DIR/spdk.tar.gz $ROOT_DIR/fio.tar.gz $(get_vhost_dir)/Virtio0;\
+ error_exit "${FUNCNAME}""${LINENO}"' ERR SIGTERM SIGABRT
+function run_spdk_fio() {
+ LD_PRELOAD=$PLUGIN_DIR/fio_plugin $FIO_PATH/fio --ioengine=spdk_bdev\
+ "$@" --spdk_mem=1024 --spdk_single_seg=1
+}
+
+function create_bdev_config()
+{
+ local vbdevs
+
+ if [ -z "$($RPC_PY get_bdevs | jq '.[] | select(.name=="Nvme0n1")')" ]; then
+ error "Nvme0n1 bdev not found!"
+ fi
+
+ $RPC_PY construct_split_vbdev Nvme0n1 6
+
+ $RPC_PY construct_vhost_scsi_controller naa.Nvme0n1_scsi0.0
+ $RPC_PY add_vhost_scsi_lun naa.Nvme0n1_scsi0.0 0 Nvme0n1p0
+ $RPC_PY add_vhost_scsi_lun naa.Nvme0n1_scsi0.0 1 Nvme0n1p1
+ $RPC_PY add_vhost_scsi_lun naa.Nvme0n1_scsi0.0 2 Nvme0n1p2
+ $RPC_PY add_vhost_scsi_lun naa.Nvme0n1_scsi0.0 3 Nvme0n1p3
+
+ $RPC_PY construct_vhost_blk_controller naa.Nvme0n1_blk0.0 Nvme0n1p4
+ $RPC_PY construct_vhost_blk_controller naa.Nvme0n1_blk1.0 Nvme0n1p5
+
+ $RPC_PY construct_malloc_bdev 128 512 --name Malloc0
+ $RPC_PY construct_vhost_scsi_controller naa.Malloc0.0
+ $RPC_PY add_vhost_scsi_lun naa.Malloc0.0 0 Malloc0
+
+ $RPC_PY construct_malloc_bdev 128 4096 --name Malloc1
+ $RPC_PY construct_vhost_scsi_controller naa.Malloc1.0
+ $RPC_PY add_vhost_scsi_lun naa.Malloc1.0 0 Malloc1
+
+ vbdevs=$(discover_bdevs $ROOT_DIR $INITIATOR_DIR/bdev.conf)
+ virtio_bdevs=$(jq -r '[.[].name] | join(":")' <<< $vbdevs)
+ virtio_with_unmap=$(jq -r '[.[] | select(.supported_io_types.unmap==true).name]
+ | join(":")' <<< $vbdevs)
+}
+
+timing_enter spdk_vhost_run
+spdk_vhost_run
+timing_exit spdk_vhost_run
+
+timing_enter create_bdev_config
+create_bdev_config
+timing_exit create_bdev_config
+
+timing_enter run_spdk_fio
+run_spdk_fio $INITIATOR_DIR/bdev.fio --filename=$virtio_bdevs --section=job_randwrite --section=job_randrw \
+ --section=job_write --section=job_rw --spdk_conf=$INITIATOR_DIR/bdev.conf
+report_test_completion "vhost_run_spdk_fio"
+timing_exit run_spdk_fio
+
+timing_enter run_spdk_fio_unmap
+run_spdk_fio $INITIATOR_DIR/bdev.fio --filename=$virtio_with_unmap --spdk_conf=$INITIATOR_DIR/bdev.conf \
+ --spdk_conf=$INITIATOR_DIR/bdev.conf
+timing_exit run_spdk_fio_unmap
+
+timing_enter create_kernel_vhost
+targetcli "/backstores/$targetcli_rd_name create name=ramdisk size=1GB"
+targetcli "/vhost create $kernel_vhost_disk"
+targetcli "/vhost/$kernel_vhost_disk/tpg1/luns create /backstores/$targetcli_rd_name/ramdisk"
+timing_exit create_kernel_vhost
+
+timing_enter setup_vm
+vm_no="0"
+vm_setup --disk-type=spdk_vhost_scsi --force=$vm_no --os=$os_image \
+ --disks="Nvme0n1_scsi0:Malloc0:Malloc1:$kernel_vhost_disk,kernel_vhost:Virtio0,virtio:\
+ Nvme0n1_blk0,spdk_vhost_blk:Nvme0n1_blk1,spdk_vhost_blk" \
+ --queue_num=8 --memory=6144
+vm_run $vm_no
+
+timing_enter vm_wait_for_boot
+vm_wait_for_boot 600 $vm_no
+timing_exit vm_wait_for_boot
+
+timing_enter vm_scp_spdk
+touch $ROOT_DIR/spdk.tar.gz
+tar --exclude="spdk.tar.gz" --exclude="*.o" --exclude="*.d" --exclude=".git" -C $ROOT_DIR -zcf $ROOT_DIR/spdk.tar.gz .
+vm_scp $vm_no $ROOT_DIR/spdk.tar.gz "127.0.0.1:/root"
+vm_ssh $vm_no "mkdir -p /root/spdk; tar -zxf /root/spdk.tar.gz -C /root/spdk --strip-components=1"
+
+touch $ROOT_DIR/fio.tar.gz
+tar --exclude="fio.tar.gz" --exclude="*.o" --exclude="*.d" --exclude=".git" -C $FIO_PATH -zcf $ROOT_DIR/fio.tar.gz .
+vm_scp $vm_no $ROOT_DIR/fio.tar.gz "127.0.0.1:/root"
+vm_ssh $vm_no "rm -rf /root/fio_src; mkdir -p /root/fio_src; tar -zxf /root/fio.tar.gz -C /root/fio_src --strip-components=1"
+timing_exit vm_scp_spdk
+
+timing_enter vm_build_spdk
+nproc=$(vm_ssh $vm_no "nproc")
+vm_ssh $vm_no " cd /root/fio_src ; make clean ; make -j${nproc} ; make install"
+vm_ssh $vm_no " cd spdk ; ./configure --with-fio=/root/fio_src ; make clean ; make -j${nproc}"
+timing_exit vm_build_spdk
+
+vm_ssh $vm_no "/root/spdk/scripts/setup.sh"
+vbdevs=$(vm_ssh $vm_no ". /root/spdk/test/common/autotest_common.sh && discover_bdevs /root/spdk \
+ /root/spdk/test/vhost/initiator/bdev_pci.conf")
+virtio_bdevs=$(jq -r '[.[].name] | join(":")' <<< $vbdevs)
+virtio_with_unmap=$(jq -r '[.[] | select(.supported_io_types.unmap==true).name]
+ | join(":")' <<< $vbdevs)
+timing_exit setup_vm
+
+timing_enter run_spdk_fio_pci
+vm_ssh $vm_no "LD_PRELOAD=/root/spdk/examples/bdev/fio_plugin/fio_plugin /root/fio_src/fio --ioengine=spdk_bdev \
+ /root/spdk/test/vhost/initiator/bdev.fio --filename=$virtio_bdevs --section=job_randwrite \
+ --section=job_randrw --section=job_write --section=job_rw \
+ --spdk_conf=/root/spdk/test/vhost/initiator/bdev_pci.conf --spdk_mem=1024 --spdk_single_seg=1"
+timing_exit run_spdk_fio_pci
+
+timing_enter run_spdk_fio_pci_unmap
+vm_ssh $vm_no "LD_PRELOAD=/root/spdk/examples/bdev/fio_plugin/fio_plugin /root/fio_src/fio --ioengine=spdk_bdev \
+ /root/spdk/test/vhost/initiator/bdev.fio --filename=$virtio_with_unmap \
+ --spdk_conf=/root/spdk/test/vhost/initiator/bdev_pci.conf --spdk_mem=1024 --spdk_single_seg=1"
+timing_exit run_spdk_fio_pci_unmap
+
+timing_enter vm_shutdown_all
+vm_shutdown_all
+timing_exit vm_shutdown_all
+
+rm -f *.state $ROOT_DIR/spdk.tar.gz $ROOT_DIR/fio.tar.gz $(get_vhost_dir)/Virtio0
+timing_enter remove_kernel_vhost
+remove_kernel_vhost
+timing_exit remove_kernel_vhost
+
+$RPC_PY delete_nvme_controller Nvme0
+
+timing_enter spdk_vhost_kill
+spdk_vhost_kill
+timing_exit spdk_vhost_kill
diff --git a/src/spdk/test/vhost/initiator/json_config.sh b/src/spdk/test/vhost/initiator/json_config.sh
new file mode 100755
index 00000000..86078c9a
--- /dev/null
+++ b/src/spdk/test/vhost/initiator/json_config.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+set -ex
+INITIATOR_JSON_DIR=$(readlink -f $(dirname $0))
+. $INITIATOR_JSON_DIR/../../json_config/common.sh
+
+# Load spdk_tgt with controllers used by virtio initiator
+# Test also virtio_pci bdevs
+function construct_vhost_devices() {
+ $rpc_py construct_split_vbdev Nvme0n1 4
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p0.0
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1p1.1
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p0.0 0 Nvme0n1p0
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1p1.1 0 Nvme0n1p1
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p2.0 Nvme0n1p2
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1p3.1 Nvme0n1p3
+ pci_scsi=$(lspci -nn -D | grep '1af4:1004' | head -1 | awk '{print $1;}')
+ pci_blk=$(lspci -nn -D | grep '1af4:1001' | head -1 | awk '{print $1;}')
+ if [ ! -z $pci_scsi ]; then
+ $rpc_py construct_virtio_dev -t pci -a $pci_scsi -d scsi Virtio0
+ fi
+ if [ ! -z $pci_blk ]; then
+ $rpc_py construct_virtio_dev -t pci -a $pci_blk -d blk Virtio1
+ fi
+}
+
+# Load virtio initiator with bdevs
+function connect_to_vhost_devices_from_initiator() {
+ $rpc_py construct_virtio_dev -t user -a naa.Nvme0n1p0.0 -d scsi Nvme0n1p0
+ $rpc_py construct_virtio_dev -t user -a naa.Nvme0n1p2.0 -d blk Nvme0n1p2
+}
+
+function disconnect_and_clear_vhost_devices() {
+ $clear_config_py clear_config
+}
+
+function test_subsystems() {
+ run_spdk_tgt
+ rootdir=$(readlink -f $INITIATOR_JSON_DIR/../../..)
+
+ rpc_py="$spdk_rpc_py"
+ clear_config_py="$spdk_clear_config_py"
+ load_nvme
+
+ construct_vhost_devices
+ test_json_config
+ run_initiator
+ rpc_py="$initiator_rpc_py"
+ clear_config_py="$initiator_clear_config_py"
+ $rpc_py start_subsystem_init
+ connect_to_vhost_devices_from_initiator
+ test_json_config
+ disconnect_and_clear_vhost_devices
+ test_global_params "virtio_initiator"
+ clear_config_py="$spdk_clear_config_py"
+ $clear_config_py clear_config
+ kill_targets
+}
+
+trap 'on_error_exit "${FUNCNAME}" "${LINENO}"' ERR
+timing_enter json_config_virtio_initiator
+
+test_subsystems
+timing_exit json_config_virtio_initiator
+report_test_completion json_config_virtio_initiator
diff --git a/src/spdk/test/vhost/integrity/integrity_start.sh b/src/spdk/test/vhost/integrity/integrity_start.sh
new file mode 100755
index 00000000..a9899e9f
--- /dev/null
+++ b/src/spdk/test/vhost/integrity/integrity_start.sh
@@ -0,0 +1,97 @@
+#!/usr/bin/env bash
+set -e
+
+INTEGRITY_BASE_DIR=$(readlink -f $(dirname $0))
+ctrl_type="spdk_vhost_scsi"
+vm_fs="ext4"
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for doing automated test"
+ echo "Usage: $(basename $1) [OPTIONS]"
+ echo
+ echo "-h, --help Print help and exit"
+ echo " --work-dir=WORK_DIR Workspace for the test to run"
+ echo " --ctrl-type=TYPE Controller type to use for test:"
+ echo " spdk_vhost_scsi - use spdk vhost scsi"
+ echo " --fs=FS_LIST Filesystems to use for test in VM:"
+ echo " Example: --fs=\"ext4 ntfs ext2\""
+ echo " Default: ext4"
+ echo " spdk_vhost_blk - use spdk vhost block"
+ echo "-x set -x for script debug"
+ exit 0
+}
+
+function clean_lvol_cfg()
+{
+ notice "Removing lvol bdev and lvol store"
+ $rpc_py destroy_lvol_bdev lvol_store/lvol_bdev
+ $rpc_py destroy_lvol_store -l lvol_store
+}
+
+while getopts 'xh-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 ;;
+ ctrl-type=*) ctrl_type="${OPTARG#*=}" ;;
+ fs=*) vm_fs="${OPTARG#*=}" ;;
+ *) usage $0 "Invalid argument '$OPTARG'" ;;
+ esac
+ ;;
+ h) usage $0 ;;
+ x) set -x
+ x="-x" ;;
+ *) usage $0 "Invalid argument '$OPTARG'"
+ esac
+done
+
+. $(readlink -e "$(dirname $0)/../common/common.sh") || exit 1
+rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock"
+
+trap 'error_exit "${FUNCNAME}" "${LINENO}"' SIGTERM SIGABRT ERR
+
+# Try to kill if any VM remains from previous runs
+vm_kill_all
+
+notice "Starting SPDK vhost"
+spdk_vhost_run
+notice "..."
+
+# Set up lvols and vhost controllers
+trap 'clean_lvol_cfg; error_exit "${FUNCNAME}" "${LINENO}"' SIGTERM SIGABRT ERR
+notice "Constructing lvol store and lvol bdev on top of Nvme0n1"
+lvs_uuid=$($rpc_py construct_lvol_store Nvme0n1 lvol_store)
+$rpc_py construct_lvol_bdev lvol_bdev 10000 -l lvol_store
+
+if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then
+ $rpc_py construct_vhost_scsi_controller naa.Nvme0n1.0
+ $rpc_py add_vhost_scsi_lun naa.Nvme0n1.0 0 lvol_store/lvol_bdev
+elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then
+ $rpc_py construct_vhost_blk_controller naa.Nvme0n1.0 lvol_store/lvol_bdev
+fi
+
+# Set up and run VM
+setup_cmd="vm_setup --disk-type=$ctrl_type --force=0"
+setup_cmd+=" --os=/home/sys_sgsw/vhost_vm_image.qcow2"
+setup_cmd+=" --disks=Nvme0n1"
+$setup_cmd
+
+# Run VM
+vm_run 0
+vm_wait_for_boot 600 0
+
+# Run tests on VM
+vm_scp 0 $INTEGRITY_BASE_DIR/integrity_vm.sh root@127.0.0.1:/root/integrity_vm.sh
+vm_ssh 0 "~/integrity_vm.sh $ctrl_type \"$vm_fs\""
+
+notice "Shutting down virtual machine..."
+vm_shutdown_all
+
+clean_lvol_cfg
+
+$rpc_py delete_nvme_controller Nvme0
+
+notice "Shutting down SPDK vhost app..."
+spdk_vhost_kill
diff --git a/src/spdk/test/vhost/integrity/integrity_vm.sh b/src/spdk/test/vhost/integrity/integrity_vm.sh
new file mode 100755
index 00000000..ccb01cea
--- /dev/null
+++ b/src/spdk/test/vhost/integrity/integrity_vm.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+set -xe
+
+basedir=$(readlink -f $(dirname $0))
+MAKE="make -j$(( $(nproc) * 2 ))"
+
+if [[ $1 == "spdk_vhost_scsi" ]]; then
+ devs=""
+ for entry in /sys/block/sd*; do
+ if grep -Eq '(INTEL|RAWSCSI|LIO-ORG)' $entry/device/vendor; then
+ devs+="$(basename $entry) "
+ fi
+ done
+elif [[ $1 == "spdk_vhost_blk" ]]; then
+ devs=$(cd /sys/block; echo vd*)
+fi
+
+fs=$2
+
+trap "exit 1" SIGINT SIGTERM EXIT
+
+for fs in $fs; do
+ for dev in $devs; do
+ parted_cmd="parted -s /dev/${dev}"
+
+ echo "INFO: Creating partition table on disk using: $parted_cmd mklabel gpt"
+ $parted_cmd mklabel gpt
+ $parted_cmd mkpart primary 2048s 100%
+ sleep 2
+
+ mkfs_cmd="mkfs.$fs"
+ if [[ $fs == "ntfs" ]]; then
+ mkfs_cmd+=" -f"
+ fi
+ mkfs_cmd+=" /dev/${dev}1"
+ echo "INFO: Creating filesystem using: $mkfs_cmd"
+ wipefs -a /dev/${dev}1
+ $mkfs_cmd
+
+ mkdir -p /mnt/${dev}dir
+ mount -o sync /dev/${dev}1 /mnt/${dev}dir
+
+ fio --name="integrity" --bsrange=4k-512k --iodepth=128 --numjobs=1 --direct=1 \
+ --thread=1 --group_reporting=1 --rw=randrw --rwmixread=70 \
+ --filename=/mnt/${dev}dir/test_file --verify=md5 --do_verify=1 \
+ --verify_backlog=1024 --fsync_on_close=1 --runtime=20 --time_based=1 --size=512m
+
+ # Print out space consumed on target device
+ df -h /dev/$dev
+ done
+
+ for dev in $devs; do
+ umount /mnt/${dev}dir
+ rm -rf /mnt/${dev}dir
+
+ stats=( $(cat /sys/block/$dev/stat) )
+ echo ""
+ echo "$dev stats"
+ printf "READ IO cnt: % 8u merges: % 8u sectors: % 8u ticks: % 8u\n" \
+ ${stats[0]} ${stats[1]} ${stats[2]} ${stats[3]}
+ printf "WRITE IO cnt: % 8u merges: % 8u sectors: % 8u ticks: % 8u\n" \
+ ${stats[4]} ${stats[5]} ${stats[6]} ${stats[7]}
+ printf "in flight: % 8u io ticks: % 8u time in queue: % 8u\n" \
+ ${stats[8]} ${stats[9]} ${stats[10]}
+ echo ""
+ done
+done
+
+trap - SIGINT SIGTERM EXIT
diff --git a/src/spdk/test/vhost/json_config/json_config.sh b/src/spdk/test/vhost/json_config/json_config.sh
new file mode 100755
index 00000000..d5683f1d
--- /dev/null
+++ b/src/spdk/test/vhost/json_config/json_config.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+set -ex
+VHOST_JSON_DIR=$(readlink -f $(dirname $0))
+. $VHOST_JSON_DIR/../../json_config/common.sh
+
+function test_subsystems() {
+ run_spdk_tgt
+
+ rpc_py="$spdk_rpc_py"
+ clear_config_py="$spdk_clear_config_py"
+ load_nvme
+
+ upload_vhost
+ test_json_config
+ $clear_config_py clear_config
+
+ kill_targets
+}
+
+trap 'on_error_exit "${FUNCNAME}" "${LINENO}"' ERR
+timing_enter json_config_vhost
+
+test_subsystems
+timing_exit json_config_vhost
+report_test_completion json_config_vhost
diff --git a/src/spdk/test/vhost/lvol/autotest.config b/src/spdk/test/vhost/lvol/autotest.config
new file mode 100644
index 00000000..9b653cd7
--- /dev/null
+++ b/src/spdk/test/vhost/lvol/autotest.config
@@ -0,0 +1,74 @@
+vhost_0_reactor_mask="[0-31]"
+vhost_0_master_core=0
+
+VM_0_qemu_mask=1
+VM_0_qemu_numa_node=0
+
+VM_1_qemu_mask=2
+VM_1_qemu_numa_node=0
+
+VM_2_qemu_mask=3
+VM_2_qemu_numa_node=0
+
+VM_3_qemu_mask=4
+VM_3_qemu_numa_node=0
+
+VM_4_qemu_mask=5
+VM_4_qemu_numa_node=0
+
+VM_5_qemu_mask=6
+VM_5_qemu_numa_node=0
+
+VM_6_qemu_mask=7
+VM_6_qemu_numa_node=0
+
+VM_7_qemu_mask=8
+VM_7_qemu_numa_node=0
+
+VM_8_qemu_mask=9
+VM_8_qemu_numa_node=0
+
+VM_9_qemu_mask=10
+VM_9_qemu_numa_node=0
+
+VM_10_qemu_mask=11
+VM_10_qemu_numa_node=0
+
+VM_11_qemu_mask=12
+VM_11_qemu_numa_node=0
+
+VM_12_qemu_mask=13
+VM_12_qemu_numa_node=1
+
+VM_13_qemu_mask=14
+VM_13_qemu_numa_node=1
+
+VM_14_qemu_mask=15
+VM_14_qemu_numa_node=1
+
+VM_15_qemu_mask=16
+VM_15_qemu_numa_node=1
+
+VM_16_qemu_mask=17
+VM_16_qemu_numa_node=1
+
+VM_17_qemu_mask=18
+VM_17_qemu_numa_node=1
+
+VM_18_qemu_mask=19
+VM_18_qemu_numa_node=1
+
+VM_19_qemu_mask=20
+VM_19_qemu_numa_node=1
+
+VM_20_qemu_mask=21
+VM_20_qemu_numa_node=1
+
+VM_21_qemu_mask=22
+VM_21_qemu_numa_node=1
+
+VM_22_qemu_mask=23
+VM_22_qemu_numa_node=1
+
+VM_23_qemu_mask=24
+VM_23_qemu_numa_node=1
diff --git a/src/spdk/test/vhost/lvol/lvol_test.sh b/src/spdk/test/vhost/lvol/lvol_test.sh
new file mode 100755
index 00000000..5190b5f2
--- /dev/null
+++ b/src/spdk/test/vhost/lvol/lvol_test.sh
@@ -0,0 +1,286 @@
+#!/usr/bin/env bash
+set -e
+
+rootdir=$(readlink -f $(dirname $0))/../../..
+source "$rootdir/scripts/common.sh"
+
+LVOL_TEST_DIR=$(readlink -f $(dirname $0))
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $LVOL_TEST_DIR/../../../../ && pwd)"
+[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $LVOL_TEST_DIR/../common && pwd)"
+
+. $COMMON_DIR/common.sh
+rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock"
+
+vm_count=1
+max_disks=""
+ctrl_type="spdk_vhost_scsi"
+use_fs=false
+nested_lvol=false
+distribute_cores=false
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for doing automated test"
+ echo "Usage: $(basename $1) [OPTIONS]"
+ echo
+ echo "-h, --help Print help and exit"
+ echo " --fio-bin=PATH Path to FIO binary.;"
+ echo " --vm-count=INT Virtual machines to use in test;"
+ echo " Each VM will get one lvol bdev on each NVMe."
+ echo " Default: 1"
+ echo " --max-disks=INT Maximum number of NVMe drives to use in test."
+ echo " Default: will use all available NVMes."
+ echo " --ctrl-type=TYPE Controller type to use for test:"
+ echo " spdk_vhost_scsi - use spdk vhost scsi"
+ echo " spdk_vhost_blk - use spdk vhost block"
+ echo " --nested-lvol If enabled will create additional lvol bdev"
+ echo " on each NVMe for use as base device for next"
+ echo " lvol store and lvol bdevs."
+ echo " (NVMe->lvol_store->lvol_bdev->lvol_store->lvol_bdev)"
+ echo " Default: False"
+ echo " --thin-provisioning Create lvol bdevs thin provisioned instead of"
+ echo " allocating space up front"
+ echo " --distribute-cores Use custom config file and run vhost controllers"
+ echo " on different CPU cores instead of single core."
+ echo " Default: False"
+ echo "-x set -x for script debug"
+ echo " --multi-os Run tests on different os types in VMs"
+ echo " Default: False"
+ exit 0
+}
+
+function clean_lvol_cfg()
+{
+ notice "Removing nested lvol bdevs"
+ for lvol_bdev in "${nest_lvol_bdevs[@]}"; do
+ $rpc_py destroy_lvol_bdev $lvol_bdev
+ notice "nested lvol bdev $lvol_bdev removed"
+ done
+
+ notice "Removing nested lvol stores"
+ for lvol_store in "${nest_lvol_stores[@]}"; do
+ $rpc_py destroy_lvol_store -u $lvol_store
+ notice "nested lvol store $lvol_store removed"
+ done
+
+ notice "Removing lvol bdevs"
+ for lvol_bdev in "${lvol_bdevs[@]}"; do
+ $rpc_py destroy_lvol_bdev $lvol_bdev
+ notice "lvol bdev $lvol_bdev removed"
+ done
+
+ notice "Removing lvol stores"
+ for lvol_store in "${lvol_stores[@]}"; do
+ $rpc_py destroy_lvol_store -u $lvol_store
+ notice "lvol store $lvol_store removed"
+ done
+}
+
+while getopts 'xh-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 ;;
+ fio-bin=*) fio_bin="--fio-bin=${OPTARG#*=}" ;;
+ vm-count=*) vm_count="${OPTARG#*=}" ;;
+ max-disks=*) max_disks="${OPTARG#*=}" ;;
+ ctrl-type=*) ctrl_type="${OPTARG#*=}" ;;
+ nested-lvol) nested_lvol=true ;;
+ distribute-cores) distribute_cores=true ;;
+ thin-provisioning) thin=" -t " ;;
+ multi-os) multi_os=true ;;
+ *) usage $0 "Invalid argument '$OPTARG'" ;;
+ esac
+ ;;
+ h) usage $0 ;;
+ x) set -x
+ x="-x" ;;
+ *) usage $0 "Invalid argument '$OPTARG'"
+ esac
+done
+
+notice "Get NVMe disks:"
+nvmes=($(iter_pci_class_code 01 08 02))
+
+if [[ -z $max_disks ]]; then
+ max_disks=${#nvmes[@]}
+fi
+
+if [[ ${#nvmes[@]} -lt max_disks ]]; then
+ fail "Number of NVMe drives (${#nvmes[@]}) is lower than number of requested disks for test ($max_disks)"
+fi
+
+if $distribute_cores; then
+ # FIXME: this need to be handled entirely in common.sh
+ source $LVOL_TEST_DIR/autotest.config
+fi
+
+trap 'error_exit "${FUNCNAME}" "${LINENO}"' SIGTERM SIGABRT ERR
+
+vm_kill_all
+
+notice "running SPDK vhost"
+spdk_vhost_run
+notice "..."
+
+trap 'clean_lvol_cfg; error_exit "${FUNCNAME}" "${LINENO}"' SIGTERM SIGABRT ERR
+
+lvol_stores=()
+lvol_bdevs=()
+nest_lvol_stores=()
+nest_lvol_bdevs=()
+used_vms=""
+
+# On each NVMe create one lvol store
+for (( i=0; i<$max_disks; i++ ));do
+
+ # Create base lvol store on NVMe
+ notice "Creating lvol store on device Nvme${i}n1"
+ ls_guid=$($rpc_py construct_lvol_store Nvme${i}n1 lvs_$i -c 4194304)
+ lvol_stores+=("$ls_guid")
+
+ if $nested_lvol; then
+ free_mb=$(get_lvs_free_mb "$ls_guid")
+ size=$((free_mb / (vm_count+1) ))
+
+ notice "Creating lvol bdev on lvol store: $ls_guid"
+ lb_name=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_nest $size $thin)
+
+ notice "Creating nested lvol store on lvol bdev: $lb_name"
+ nest_ls_guid=$($rpc_py construct_lvol_store $lb_name lvs_n_$i -c 4194304)
+ nest_lvol_stores+=("$nest_ls_guid")
+
+ for (( j=0; j<$vm_count; j++)); do
+ notice "Creating nested lvol bdev for VM $i on lvol store $nest_ls_guid"
+ free_mb=$(get_lvs_free_mb "$nest_ls_guid")
+ nest_size=$((free_mb / (vm_count-j) ))
+ lb_name=$($rpc_py construct_lvol_bdev -u $nest_ls_guid lbd_vm_$j $nest_size $thin)
+ nest_lvol_bdevs+=("$lb_name")
+ done
+ fi
+
+ # Create base lvol bdevs
+ for (( j=0; j<$vm_count; j++)); do
+ notice "Creating lvol bdev for VM $i on lvol store $ls_guid"
+ free_mb=$(get_lvs_free_mb "$ls_guid")
+ size=$((free_mb / (vm_count-j) ))
+ lb_name=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_vm_$j $size $thin)
+ lvol_bdevs+=("$lb_name")
+ done
+done
+
+bdev_info=$($rpc_py get_bdevs)
+notice "Configuration after initial set-up:"
+$rpc_py get_lvol_stores
+echo "$bdev_info"
+
+# Set up VMs
+for (( i=0; i<$vm_count; i++)); do
+ vm="vm_$i"
+
+ # Get all lvol bdevs associated with this VM number
+ bdevs=$(jq -r "map(select(.aliases[] | contains(\"$vm\")) | \
+ .aliases[]) | join(\" \")" <<< "$bdev_info")
+ bdevs=($bdevs)
+
+ setup_cmd="vm_setup --disk-type=$ctrl_type --force=$i"
+ if [[ $i%2 -ne 0 ]] && [[ $multi_os ]]; then
+ setup_cmd+=" --os=/home/sys_sgsw/spdk_vhost_CentOS_vm_image.qcow2"
+ else
+ setup_cmd+=" --os=/home/sys_sgsw/vhost_vm_image.qcow2"
+ fi
+
+ # Create single SCSI controller or multiple BLK controllers for this VM
+ if $distribute_cores; then
+ mask="VM_${i}_qemu_mask"
+ mask_arg="--cpumask ${!mask}"
+ fi
+
+ if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then
+ $rpc_py construct_vhost_scsi_controller naa.0.$i $mask_arg
+ for (( j=0; j<${#bdevs[@]}; j++)); do
+ $rpc_py add_vhost_scsi_lun naa.0.$i $j ${bdevs[$j]}
+ done
+ setup_cmd+=" --disks=0"
+ elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then
+ disk=""
+ for (( j=0; j<${#bdevs[@]}; j++)); do
+ $rpc_py construct_vhost_blk_controller naa.$j.$i ${bdevs[$j]} $mask_arg
+ disk+="${j}:"
+ done
+ disk="${disk::-1}"
+ setup_cmd+=" --disks=$disk"
+ fi
+
+ $setup_cmd
+ used_vms+=" $i"
+done
+
+$rpc_py get_vhost_controllers
+
+# Run VMs
+vm_run $used_vms
+vm_wait_for_boot 600 $used_vms
+
+# Get disk names from VMs and run FIO traffic
+
+fio_disks=""
+for vm_num in $used_vms; do
+ vm_dir=$VM_BASE_DIR/$vm_num
+ qemu_mask_param="VM_${vm_num}_qemu_mask"
+
+ host_name="VM-$vm_num-${!qemu_mask_param}"
+ vm_ssh $vm_num "hostname $host_name"
+ vm_start_fio_server $fio_bin $vm_num
+
+ if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then
+ vm_check_scsi_location $vm_num
+ elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then
+ vm_check_blk_location $vm_num
+ fi
+
+ fio_disks+=" --vm=${vm_num}$(printf ':/dev/%s' $SCSI_DISK)"
+done
+
+if [[ $RUN_NIGHTLY -eq 1 ]]; then
+ job_file="default_integrity_nightly.job"
+else
+ job_file="default_integrity.job"
+fi
+# Run FIO traffic
+run_fio $fio_bin --job-file=$COMMON_DIR/fio_jobs/$job_file --out="$TEST_DIR/fio_results" $fio_disks
+
+notice "Shutting down virtual machines..."
+vm_shutdown_all
+sleep 2
+
+notice "Cleaning up vhost - remove LUNs, controllers, lvol bdevs and lvol stores"
+if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then
+ for (( i=0; i<$vm_count; i++)); do
+ notice "Removing devices from vhost SCSI controller naa.0.$i"
+ for (( j=0; j<${#bdevs[@]}; j++)); do
+ $rpc_py remove_vhost_scsi_target naa.0.$i $j
+ notice "Removed device $j"
+ done
+ notice "Removing vhost SCSI controller naa.0.$i"
+ $rpc_py remove_vhost_controller naa.0.$i
+ done
+elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then
+ for (( i=0; i<$vm_count; i++)); do
+ for (( j=0; j<${#bdevs[@]}; j++)); do
+ notice "Removing vhost BLK controller naa.$j.$i"
+ $rpc_py remove_vhost_controller naa.$j.$i
+ notice "Removed naa.$j.$i"
+ done
+ done
+fi
+
+clean_lvol_cfg
+
+$rpc_py get_lvol_stores
+$rpc_py get_bdevs
+$rpc_py get_vhost_controllers
+
+notice "Shutting down SPDK vhost app..."
+spdk_vhost_kill
diff --git a/src/spdk/test/vhost/migration/autotest.config b/src/spdk/test/vhost/migration/autotest.config
new file mode 100644
index 00000000..ccda306e
--- /dev/null
+++ b/src/spdk/test/vhost/migration/autotest.config
@@ -0,0 +1,14 @@
+vhost_0_reactor_mask=["0"]
+vhost_0_master_core=0
+
+vhost_1_reactor_mask=["0"]
+vhost_1_master_core=0
+
+VM_0_qemu_mask=1
+VM_0_qemu_numa_node=0
+
+VM_1_qemu_mask=1
+VM_1_qemu_numa_node=0
+
+VM_2_qemu_mask=1
+VM_2_qemu_numa_node=0
diff --git a/src/spdk/test/vhost/migration/migration-tc1.job b/src/spdk/test/vhost/migration/migration-tc1.job
new file mode 100644
index 00000000..5383b243
--- /dev/null
+++ b/src/spdk/test/vhost/migration/migration-tc1.job
@@ -0,0 +1,25 @@
+[global]
+blocksize_range=4k-512k
+#bs=512k
+iodepth=128
+ioengine=libaio
+filename=
+group_reporting
+thread
+numjobs=1
+direct=1
+do_verify=1
+verify=md5
+verify_fatal=1
+verify_dump=1
+size=100%
+
+[write]
+rw=write
+stonewall
+
+[randread]
+rw=randread
+runtime=10
+time_based
+stonewall
diff --git a/src/spdk/test/vhost/migration/migration-tc1.sh b/src/spdk/test/vhost/migration/migration-tc1.sh
new file mode 100644
index 00000000..ec89545d
--- /dev/null
+++ b/src/spdk/test/vhost/migration/migration-tc1.sh
@@ -0,0 +1,123 @@
+function migration_tc1_clean_vhost_config()
+{
+ # Restore trap
+ trap 'error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+
+ notice "Removing vhost devices & controllers via RPC ..."
+ # Delete bdev first to remove all LUNs and SCSI targets
+ $rpc delete_malloc_bdev Malloc0
+
+ # Delete controllers
+ $rpc remove_vhost_controller $incoming_vm_ctrlr
+ $rpc remove_vhost_controller $target_vm_ctrlr
+
+ unset -v incoming_vm target_vm incoming_vm_ctrlr target_vm_ctrlr rpc
+}
+
+function migration_tc1_configure_vhost()
+{
+ # Those are global intentionally - they will be unset in cleanup handler
+ incoming_vm=0
+ target_vm=1
+ incoming_vm_ctrlr=naa.Malloc0.$incoming_vm
+ target_vm_ctrlr=naa.Malloc0.$target_vm
+ rpc="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock"
+
+ trap 'migration_tc1_error_handler; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+
+ # Construct shared Malloc Bdev
+ $rpc construct_malloc_bdev -b Malloc0 128 4096
+
+ # And two controllers - one for each VM. Both are using the same Malloc Bdev as LUN 0
+ $rpc construct_vhost_scsi_controller $incoming_vm_ctrlr
+ $rpc add_vhost_scsi_lun $incoming_vm_ctrlr 0 Malloc0
+
+ $rpc construct_vhost_scsi_controller $target_vm_ctrlr
+ $rpc add_vhost_scsi_lun $target_vm_ctrlr 0 Malloc0
+}
+
+function migration_tc1_error_handler()
+{
+ trap - SIGINT ERR EXIT
+ warning "Migration TC1 ERROR HANDLER"
+ print_backtrace
+ set -x
+
+ vm_kill_all
+ migration_tc1_clean_vhost_config
+
+ warning "Migration TC1 FAILED"
+}
+
+function migration_tc1()
+{
+ # Use 2 VMs:
+ # incoming VM - the one we want to migrate
+ # targe VM - the one which will accept migration
+ local job_file="$MIGRATION_DIR/migration-tc1.job"
+
+ # Run vhost
+ spdk_vhost_run
+ migration_tc1_configure_vhost
+
+ notice "Setting up VMs"
+ vm_setup --os="$os_image" --force=$incoming_vm --disk-type=spdk_vhost_scsi --disks=Malloc0 --migrate-to=$target_vm
+ vm_setup --force=$target_vm --disk-type=spdk_vhost_scsi --disks=Malloc0 --incoming=$incoming_vm
+
+ # Run everything
+ vm_run $incoming_vm $target_vm
+
+ # Wait only for incoming VM, as target is waiting for migration
+ vm_wait_for_boot 600 $incoming_vm
+
+ # Run fio before migration
+ notice "Starting FIO"
+
+ vm_check_scsi_location $incoming_vm
+ run_fio $fio_bin --job-file="$job_file" --local --vm="${incoming_vm}$(printf ':/dev/%s' $SCSI_DISK)"
+
+ # Wait a while to let the FIO time to issue some IO
+ sleep 5
+
+ # Check if fio is still running before migration
+ if ! is_fio_running $incoming_vm; then
+ vm_ssh $incoming_vm "cat /root/$(basename ${job_file}).out"
+ error "FIO is not running before migration: process crashed or finished too early"
+ fi
+
+ vm_migrate $incoming_vm
+ sleep 3
+
+ # Check if fio is still running after migration
+ if ! is_fio_running $target_vm; then
+ vm_ssh $target_vm "cat /root/$(basename ${job_file}).out"
+ error "FIO is not running after migration: process crashed or finished too early"
+ fi
+
+ notice "Waiting for fio to finish"
+ local timeout=40
+ while is_fio_running $target_vm; do
+ sleep 1
+ echo -n "."
+ if (( timeout-- == 0 )); then
+ error "timeout while waiting for FIO!"
+ fi
+ done
+
+ notice "Fio result is:"
+ vm_ssh $target_vm "cat /root/$(basename ${job_file}).out"
+
+ notice "Migration DONE"
+
+ notice "Shutting down all VMs"
+ vm_shutdown_all
+
+ migration_tc1_clean_vhost_config
+
+ notice "killing vhost app"
+ spdk_vhost_kill
+
+ notice "Migration TC1 SUCCESS"
+}
+
+migration_tc1
diff --git a/src/spdk/test/vhost/migration/migration-tc2.job b/src/spdk/test/vhost/migration/migration-tc2.job
new file mode 100644
index 00000000..df78a3cd
--- /dev/null
+++ b/src/spdk/test/vhost/migration/migration-tc2.job
@@ -0,0 +1,20 @@
+[global]
+blocksize_range=4k-512k
+iodepth=128
+ioengine=libaio
+filename=
+group_reporting
+thread
+numjobs=1
+direct=1
+do_verify=1
+verify=md5
+verify_fatal=1
+verify_dump=1
+verify_backlog=8
+
+[randwrite]
+rw=randwrite
+runtime=15
+time_based
+stonewall
diff --git a/src/spdk/test/vhost/migration/migration-tc2.sh b/src/spdk/test/vhost/migration/migration-tc2.sh
new file mode 100644
index 00000000..bc4a0f53
--- /dev/null
+++ b/src/spdk/test/vhost/migration/migration-tc2.sh
@@ -0,0 +1,209 @@
+source $SPDK_BUILD_DIR/test/nvmf/common.sh
+
+function migration_tc2_cleanup_nvmf_tgt()
+{
+ local i
+
+ if [[ ! -r "$nvmf_dir/nvmf_tgt.pid" ]]; then
+ warning "Pid file '$nvmf_dir/nvmf_tgt.pid' does not exist. "
+ return
+ fi
+
+ if [[ ! -z "$1" ]]; then
+ trap 'error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+ pkill --signal $1 -F $nvmf_dir/nvmf_tgt.pid || true
+ sleep 5
+ if ! pkill -F $nvmf_dir/nvmf_tgt.pid; then
+ fail "failed to kill nvmf_tgt app"
+ fi
+ else
+ pkill --signal SIGTERM -F $nvmf_dir/nvmf_tgt.pid || true
+ for (( i=0; i<20; i++ )); do
+ if ! pkill --signal 0 -F $nvmf_dir/nvmf_tgt.pid; then
+ break
+ fi
+ sleep 0.5
+ done
+
+ if pkill --signal 0 -F $nvmf_dir/nvmf_tgt.pid; then
+ error "nvmf_tgt failed to shutdown"
+ fi
+ fi
+
+ rm $nvmf_dir/nvmf_tgt.pid
+ unset -v nvmf_dir rpc_nvmf
+}
+
+function migration_tc2_cleanup_vhost_config()
+{
+ timing_enter migration_tc2_cleanup_vhost_config
+
+ trap 'migration_tc2_cleanup_nvmf_tgt SIGKILL; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+
+ notice "Shutting down all VMs"
+ vm_shutdown_all
+
+ notice "Removing vhost devices & controllers via RPC ..."
+ # Delete bdev first to remove all LUNs and SCSI targets
+ $rpc_0 delete_nvme_controller Nvme0
+ $rpc_0 remove_vhost_controller $incoming_vm_ctrlr
+
+ $rpc_1 delete_nvme_controller Nvme0
+ $rpc_1 remove_vhost_controller $target_vm_ctrlr
+
+ notice "killing vhost app"
+ spdk_vhost_kill 0
+ spdk_vhost_kill 1
+
+ unset -v incoming_vm target_vm incoming_vm_ctrlr target_vm_ctrlr
+ unset -v rpc_0 rpc_1
+
+ trap 'error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+ migration_tc2_cleanup_nvmf_tgt
+
+ timing_exit migration_tc2_cleanup_vhost_config
+}
+
+function migration_tc2_configure_vhost()
+{
+ timing_enter migration_tc2_configure_vhost
+
+ # Those are global intentionally - they will be unset in cleanup handler
+ nvmf_dir="$TEST_DIR/nvmf_tgt"
+
+ incoming_vm=1
+ target_vm=2
+ incoming_vm_ctrlr=naa.VhostScsi0.$incoming_vm
+ target_vm_ctrlr=naa.VhostScsi0.$target_vm
+
+ rpc_nvmf="$SPDK_BUILD_DIR/scripts/rpc.py -s $nvmf_dir/rpc.sock"
+ rpc_0="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir 0)/rpc.sock"
+ rpc_1="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir 1)/rpc.sock"
+
+ # Default cleanup/error handlers will not shutdown nvmf_tgt app so setup it
+ # here to teardown in cleanup function
+ trap 'migration_tc2_error_cleanup; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+
+ # Run nvmf_tgt and two vhost instances:
+ # nvmf_tgt uses core id 2 (-m 0x4)
+ # First uses core id 0 (vhost_0_reactor_mask=0x1)
+ # Second uses core id 1 (vhost_1_reactor_mask=0x2)
+ # This force to use VM 1 and 2.
+ timing_enter start_nvmf_tgt
+ notice "Running nvmf_tgt..."
+ mkdir -p $nvmf_dir
+ rm -f $nvmf_dir/*
+ $SPDK_BUILD_DIR/app/nvmf_tgt/nvmf_tgt -s 512 -m 0x4 -r $nvmf_dir/rpc.sock --wait-for-rpc &
+ local nvmf_tgt_pid=$!
+ echo $nvmf_tgt_pid > $nvmf_dir/nvmf_tgt.pid
+ waitforlisten "$nvmf_tgt_pid" "$nvmf_dir/rpc.sock"
+ $rpc_nvmf start_subsystem_init
+ $rpc_nvmf nvmf_create_transport -t RDMA -u 8192 -p 4
+ $SPDK_BUILD_DIR/scripts/gen_nvme.sh --json | $rpc_nvmf load_subsystem_config
+ timing_exit start_nvmf_tgt
+
+ spdk_vhost_run --memory=512 --vhost-num=0 --no-pci
+ # Those are global intentionally
+ vhost_1_reactor_mask=0x2
+ vhost_1_master_core=1
+ spdk_vhost_run --memory=512 --vhost-num=1 --no-pci
+
+ local rdma_ip_list=$(get_available_rdma_ips)
+ local nvmf_target_ip=$(echo "$rdma_ip_list" | head -n 1)
+
+ if [[ -z "$nvmf_target_ip" ]]; then
+ fail "no NIC for nvmf target"
+ fi
+
+ notice "Configuring nvmf_tgt, vhost devices & controllers via RPC ..."
+
+ # Construct shared bdevs and controllers
+ $rpc_nvmf nvmf_subsystem_create nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
+ $rpc_nvmf nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 Nvme0n1
+ $rpc_nvmf nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a $nvmf_target_ip -s 4420
+
+ $rpc_0 construct_nvme_bdev -b Nvme0 -t rdma -f ipv4 -a $nvmf_target_ip -s 4420 -n "nqn.2016-06.io.spdk:cnode1"
+ $rpc_0 construct_vhost_scsi_controller $incoming_vm_ctrlr
+ $rpc_0 add_vhost_scsi_lun $incoming_vm_ctrlr 0 Nvme0n1
+
+ $rpc_1 construct_nvme_bdev -b Nvme0 -t rdma -f ipv4 -a $nvmf_target_ip -s 4420 -n "nqn.2016-06.io.spdk:cnode1"
+ $rpc_1 construct_vhost_scsi_controller $target_vm_ctrlr
+ $rpc_1 add_vhost_scsi_lun $target_vm_ctrlr 0 Nvme0n1
+
+ notice "Setting up VMs"
+ vm_setup --os="$os_image" --force=$incoming_vm --disk-type=spdk_vhost_scsi --disks=VhostScsi0 \
+ --migrate-to=$target_vm --memory=1024 --vhost-num=0
+ vm_setup --force=$target_vm --disk-type=spdk_vhost_scsi --disks=VhostScsi0 --incoming=$incoming_vm --memory=1024 \
+ --vhost-num=1
+
+ # Run everything
+ vm_run $incoming_vm $target_vm
+
+ # Wait only for incoming VM, as target is waiting for migration
+ vm_wait_for_boot 600 $incoming_vm
+
+ notice "Configuration done"
+
+ timing_exit migration_tc2_configure_vhost
+}
+
+function migration_tc2_error_cleanup()
+{
+ trap - SIGINT ERR EXIT
+ set -x
+
+ vm_kill_all
+ migration_tc2_cleanup_vhost_config
+ notice "Migration TC2 FAILED"
+}
+
+function migration_tc2()
+{
+ # Use 2 VMs:
+ # incoming VM - the one we want to migrate
+ # targe VM - the one which will accept migration
+ local job_file="$MIGRATION_DIR/migration-tc2.job"
+
+ migration_tc2_configure_vhost
+
+ # Run fio before migration
+ notice "Starting FIO"
+ vm_check_scsi_location $incoming_vm
+ run_fio $fio_bin --job-file="$job_file" --local --vm="${incoming_vm}$(printf ':/dev/%s' $SCSI_DISK)"
+
+ # Wait a while to let the FIO time to issue some IO
+ sleep 5
+
+ # Check if fio is still running before migration
+ if ! is_fio_running $incoming_vm; then
+ vm_ssh $incoming_vm "cat /root/$(basename ${job_file}).out"
+ error "FIO is not running before migration: process crashed or finished too early"
+ fi
+
+ vm_migrate $incoming_vm
+ sleep 3
+
+ # Check if fio is still running after migration
+ if ! is_fio_running $target_vm; then
+ vm_ssh $target_vm "cat /root/$(basename ${job_file}).out"
+ error "FIO is not running after migration: process crashed or finished too early"
+ fi
+
+ notice "Waiting for fio to finish"
+ local timeout=40
+ while is_fio_running $target_vm; do
+ sleep 1
+ echo -n "."
+ if (( timeout-- == 0 )); then
+ error "timeout while waiting for FIO!"
+ fi
+ done
+
+ notice "Fio result is:"
+ vm_ssh $target_vm "cat /root/$(basename ${job_file}).out"
+
+ migration_tc2_cleanup_vhost_config
+ notice "Migration TC2 SUCCESS"
+}
+
+migration_tc2
diff --git a/src/spdk/test/vhost/migration/migration-tc3.job b/src/spdk/test/vhost/migration/migration-tc3.job
new file mode 100644
index 00000000..fe192966
--- /dev/null
+++ b/src/spdk/test/vhost/migration/migration-tc3.job
@@ -0,0 +1,20 @@
+[global]
+blocksize=4k-512k
+iodepth=128
+ioengine=libaio
+filename=
+group_reporting
+thread
+numjobs=1
+direct=1
+do_verify=1
+verify=md5
+verify_fatal=1
+verify_dump=1
+verify_backlog=8
+
+[randwrite]
+rw=randwrite
+runtime=15
+time_based
+stonewall
diff --git a/src/spdk/test/vhost/migration/migration-tc3a.sh b/src/spdk/test/vhost/migration/migration-tc3a.sh
new file mode 100644
index 00000000..0f20b994
--- /dev/null
+++ b/src/spdk/test/vhost/migration/migration-tc3a.sh
@@ -0,0 +1,227 @@
+source $SPDK_BUILD_DIR/test/nvmf/common.sh
+source $MIGRATION_DIR/autotest.config
+
+incoming_vm=1
+target_vm=2
+incoming_vm_ctrlr=naa.VhostScsi0.$incoming_vm
+target_vm_ctrlr=naa.VhostScsi0.$target_vm
+share_dir=$TEST_DIR/share
+spdk_repo_share_dir=$TEST_DIR/share_spdk
+job_file=$MIGRATION_DIR/migration-tc3.job
+
+if [ -z "$MGMT_TARGET_IP" ]; then
+ error "No IP address of target is given"
+fi
+
+if [ -z "$MGMT_INITIATOR_IP" ]; then
+ error "No IP address of initiator is given"
+fi
+
+if [ -z "$RDMA_TARGET_IP" ]; then
+ error "No IP address of targets RDMA capable NIC is given"
+fi
+
+if [ -z "$RDMA_INITIATOR_IP" ]; then
+ error "No IP address of initiators RDMA capable NIC is given"
+fi
+
+function ssh_remote()
+{
+ local ssh_cmd="ssh -i $SPDK_VHOST_SSH_KEY_FILE \
+ -o UserKnownHostsFile=/dev/null \
+ -o StrictHostKeyChecking=no -o ControlMaster=auto \
+ root@$1"
+
+ shift
+ $ssh_cmd "$@"
+}
+
+function wait_for_remote()
+{
+ local timeout=40
+ set +x
+ while [[ ! -f $share_dir/DONE ]]; do
+ echo -n "."
+ if (( timeout-- == 0 )); then
+ error "timeout while waiting for FIO!"
+ fi
+ sleep 1
+ done
+ set -x
+ rm -f $share_dir/DONE
+}
+
+function check_rdma_connection()
+{
+ local nic_name=$(ip -4 -o addr show to $RDMA_TARGET_IP up | cut -d' ' -f2)
+ if [[ -z $nic_name ]]; then
+ error "There is no NIC with IP address $RDMA_TARGET_IP configured"
+ fi
+
+ if ! ls /sys/class/infiniband/*/device/net/$nic_name &> /dev/null; then
+ error "$nic_name with IP $RDMA_TARGET_IP is not a RDMA capable NIC"
+ fi
+
+}
+
+function host1_cleanup_nvmf()
+{
+ notice "Shutting down nvmf_tgt on local server"
+ if [[ ! -z "$1" ]]; then
+ pkill --signal $1 -F $nvmf_dir/nvmf_tgt.pid
+ else
+ pkill -F $nvmf_dir/nvmf_tgt.pid
+ fi
+ rm -f $nvmf_dir/nvmf_tgt.pid
+}
+
+function host1_cleanup_vhost()
+{
+ trap 'host1_cleanup_nvmf SIGKILL; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+ notice "Shutting down VM $incoming_vm"
+ vm_kill $incoming_vm
+
+ notice "Removing bdev & controller from vhost on local server"
+ $rpc_0 delete_nvme_controller Nvme0
+ $rpc_0 remove_vhost_controller $incoming_vm_ctrlr
+
+ notice "Shutting down vhost app"
+ spdk_vhost_kill 0
+
+ host1_cleanup_nvmf
+}
+
+function host1_start_nvmf()
+{
+ nvmf_dir="$TEST_DIR/nvmf_tgt"
+ rpc_nvmf="$SPDK_BUILD_DIR/scripts/rpc.py -s $nvmf_dir/nvmf_rpc.sock"
+
+ notice "Starting nvmf_tgt instance on local server"
+ mkdir -p $nvmf_dir
+ rm -rf $nvmf_dir/*
+
+ trap 'host1_cleanup_nvmf SIGKILL; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+ $SPDK_BUILD_DIR/app/nvmf_tgt/nvmf_tgt -s 512 -m 0xF -r $nvmf_dir/nvmf_rpc.sock --wait-for-rpc &
+ nvmf_tgt_pid=$!
+ echo $nvmf_tgt_pid > $nvmf_dir/nvmf_tgt.pid
+ waitforlisten "$nvmf_tgt_pid" "$nvmf_dir/nvmf_rpc.sock"
+ $rpc_nvmf start_subsystem_init
+ $rpc_nvmf nvmf_create_transport -t RDMA -u 8192 -p 4
+ $SPDK_BUILD_DIR/scripts/gen_nvme.sh --json | $rpc_nvmf load_subsystem_config
+
+ $rpc_nvmf nvmf_subsystem_create nqn.2018-02.io.spdk:cnode1 -a -s SPDK01
+ $rpc_nvmf nvmf_subsystem_add_ns nqn.2018-02.io.spdk:cnode1 Nvme0n1
+ $rpc_nvmf nvmf_subsystem_add_listener nqn.2018-02.io.spdk:cnode1 -t rdma -a $RDMA_TARGET_IP -s 4420
+}
+
+function host1_start_vhost()
+{
+ rpc_0="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir 0)/rpc.sock"
+
+ notice "Starting vhost0 instance on local server"
+ trap 'host1_cleanup_vhost; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+ spdk_vhost_run --vhost-num=0 --no-pci
+ $rpc_0 construct_nvme_bdev -b Nvme0 -t rdma -f ipv4 -a $RDMA_TARGET_IP -s 4420 -n "nqn.2018-02.io.spdk:cnode1"
+ $rpc_0 construct_vhost_scsi_controller $incoming_vm_ctrlr
+ $rpc_0 add_vhost_scsi_lun $incoming_vm_ctrlr 0 Nvme0n1
+
+ vm_setup --os="$share_dir/migration.qcow2" --force=$incoming_vm --disk-type=spdk_vhost_scsi --disks=VhostScsi0 \
+ --migrate-to=$target_vm --memory=512 --queue_num=1
+
+ # TODO: Fix loop calculating cpu_num in common.sh
+ # We need -smp 1 and -queue_num 1 for this test to work, and this loop
+ # in some cases calculates wrong cpu_num.
+ sed -i "s#smp 2#smp 1#g" $VM_BASE_DIR/$incoming_vm/run.sh
+ vm_run $incoming_vm
+ vm_wait_for_boot 300 $incoming_vm
+}
+
+function cleanup_share()
+{
+ set +e
+ notice "Cleaning up share directory on remote and local server"
+ ssh_remote $MGMT_INITIATOR_IP "umount $VM_BASE_DIR"
+ ssh_remote $MGMT_INITIATOR_IP "umount $share_dir; rm -f $share_dir/* rm -rf $spdk_repo_share_dir"
+ rm -f $share_dir/migration.qcow2
+ rm -f $share_dir/spdk.tar.gz
+ set -e
+}
+
+function host_1_create_share()
+{
+ notice "Creating share directory on local server to re-use on remote"
+ mkdir -p $share_dir
+ mkdir -p $VM_BASE_DIR # This dir would've been created later but we need it now
+ rm -rf $share_dir/spdk.tar.gz $share_dir/spdk || true
+ cp $os_image $share_dir/migration.qcow2
+ tar --exclude="*.o"--exclude="*.d" --exclude="*.git" -C $SPDK_BUILD_DIR -zcf $share_dir/spdk.tar.gz .
+}
+
+function host_2_create_share()
+{
+ # Copy & compile the sources for later use on remote server.
+ ssh_remote $MGMT_INITIATOR_IP "uname -a"
+ ssh_remote $MGMT_INITIATOR_IP "mkdir -p $share_dir"
+ ssh_remote $MGMT_INITIATOR_IP "mkdir -p $spdk_repo_share_dir"
+ ssh_remote $MGMT_INITIATOR_IP "mkdir -p $VM_BASE_DIR"
+ ssh_remote $MGMT_INITIATOR_IP "sshfs -o\
+ ssh_command=\"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ControlMaster=auto\
+ -i $SPDK_VHOST_SSH_KEY_FILE\" root@$MGMT_TARGET_IP:$VM_BASE_DIR $VM_BASE_DIR"
+ ssh_remote $MGMT_INITIATOR_IP "sshfs -o\
+ ssh_command=\"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ControlMaster=auto\
+ -i $SPDK_VHOST_SSH_KEY_FILE\" root@$MGMT_TARGET_IP:$share_dir $share_dir"
+ ssh_remote $MGMT_INITIATOR_IP "mkdir -p $spdk_repo_share_dir/spdk"
+ ssh_remote $MGMT_INITIATOR_IP "tar -zxf $share_dir/spdk.tar.gz -C $spdk_repo_share_dir/spdk --strip-components=1"
+ ssh_remote $MGMT_INITIATOR_IP "cd $spdk_repo_share_dir/spdk; make clean; ./configure --with-rdma --enable-debug; make -j40"
+}
+
+function host_2_start_vhost()
+{
+ ssh_remote $MGMT_INITIATOR_IP "nohup $spdk_repo_share_dir/spdk/test/vhost/migration/migration.sh\
+ --test-cases=3b --work-dir=$TEST_DIR --os=$share_dir/migration.qcow2\
+ --rdma-tgt-ip=$RDMA_TARGET_IP &>$share_dir/output.log &"
+ notice "Waiting for remote to be done with vhost & VM setup..."
+ wait_for_remote
+}
+
+function setup_share()
+{
+ trap 'cleanup_share; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+ host_1_create_share
+ host_2_create_share
+}
+
+function migration_tc3()
+{
+ check_rdma_connection
+ setup_share
+ host1_start_nvmf
+ host1_start_vhost
+ host_2_start_vhost
+
+ # Do migration
+ notice "Starting fio on local VM"
+ vm_check_scsi_location $incoming_vm
+
+ run_fio $fio_bin --job-file="$job_file" --local --vm="${incoming_vm}$(printf ':/dev/%s' $SCSI_DISK)"
+ sleep 5
+
+ if ! is_fio_running $incoming_vm; then
+ vh_ssh $incoming_vm "cat /root/$(basename ${job_file}).out"
+ error "Fio not running on local VM before starting migration!"
+ fi
+
+ vm_migrate $incoming_vm $RDMA_INITIATOR_IP
+ sleep 1
+
+ # Verify migration on remote host and clean up vhost
+ ssh_remote $MGMT_INITIATOR_IP "pkill -CONT -F $TEST_DIR/tc3b.pid"
+ notice "Waiting for remote to finish FIO on VM and clean up..."
+ wait_for_remote
+
+ # Clean up local stuff
+ host1_cleanup_vhost
+ cleanup_share
+}
+
+migration_tc3
diff --git a/src/spdk/test/vhost/migration/migration-tc3b.sh b/src/spdk/test/vhost/migration/migration-tc3b.sh
new file mode 100644
index 00000000..babba0dc
--- /dev/null
+++ b/src/spdk/test/vhost/migration/migration-tc3b.sh
@@ -0,0 +1,79 @@
+# Set -m option is needed to be able to use "suspend" command
+# as we are usin non-interactive session to connect to remote.
+# Without -m it would be not possible to suspend the process.
+set -m
+source $MIGRATION_DIR/autotest.config
+
+incoming_vm=1
+target_vm=2
+target_vm_ctrl=naa.VhostScsi0.$target_vm
+rpc="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir 1)/rpc.sock"
+share_dir=$TEST_DIR/share
+
+function host_2_cleanup_vhost()
+{
+ notice "Shutting down VM $target_vm"
+ vm_kill $target_vm
+
+ notice "Removing bdev & controller from vhost 1 on remote server"
+ $rpc delete_nvme_controller Nvme0
+ $rpc remove_vhost_controller $target_vm_ctrl
+
+ notice "Shutting down vhost app"
+ spdk_vhost_kill 1
+ sleep 1
+}
+
+function host_2_start_vhost()
+{
+ echo "BASE DIR $TEST_DIR"
+ vhost_work_dir=$TEST_DIR/vhost1
+ mkdir -p $vhost_work_dir
+ rm -f $vhost_work_dir/*
+
+ notice "Starting vhost 1 instance on remote server"
+ trap 'host_2_cleanup_vhost; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+ spdk_vhost_run --vhost-num=1 --no-pci
+
+ $rpc construct_nvme_bdev -b Nvme0 -t rdma -f ipv4 -a $RDMA_TARGET_IP -s 4420 -n "nqn.2018-02.io.spdk:cnode1"
+ $rpc construct_vhost_scsi_controller $target_vm_ctrl
+ $rpc add_vhost_scsi_lun $target_vm_ctrl 0 Nvme0n1
+
+ vm_setup --os="$os_image" --force=$target_vm --disk-type=spdk_vhost_scsi --disks=VhostScsi0 \
+ --memory=512 --vhost-num=1 --incoming=$incoming_vm
+ vm_run $target_vm
+ sleep 1
+
+ # Use this file as a flag to notify main script
+ # that setup on remote server is done
+ echo "DONE" > $share_dir/DONE
+}
+
+echo $$ > $TEST_DIR/tc3b.pid
+host_2_start_vhost
+suspend -f
+
+if ! vm_os_booted $target_vm; then
+ fail "VM$target_vm is not running!"
+fi
+
+if ! is_fio_running $target_vm; then
+ vm_ssh $target_vm "cat /root/migration-tc3.job.out"
+ error "FIO is not running on remote server after migration!"
+fi
+
+notice "Waiting for FIO to finish on remote server VM"
+timeout=40
+while is_fio_running $target_vm; do
+ sleep 1
+ echo -n "."
+ if (( timeout-- == 0 )); then
+ error "timeout while waiting for FIO!"
+ fi
+done
+
+notice "FIO result after migration:"
+vm_ssh $target_vm "cat /root/migration-tc3.job.out"
+
+host_2_cleanup_vhost
+echo "DONE" > $share_dir/DONE
diff --git a/src/spdk/test/vhost/migration/migration.sh b/src/spdk/test/vhost/migration/migration.sh
new file mode 100755
index 00000000..bdfcd845
--- /dev/null
+++ b/src/spdk/test/vhost/migration/migration.sh
@@ -0,0 +1,153 @@
+#!/usr/bin/env bash
+
+set -e
+
+vms=()
+declare -A vms_os
+declare -A vms_raw_disks
+declare -A vms_ctrlrs
+declare -A vms_ctrlrs_disks
+
+# By default use Guest fio
+fio_bin=""
+test_cases=""
+MGMT_TARGET_IP=""
+MGMT_INITIATOR_IP=""
+RDMA_TARGET_IP=""
+RDMA_INITIATOR_IP=""
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for doing automated test of live migration."
+ echo "Usage: $(basename $1) [OPTIONS]"
+ echo
+ echo " --work-dir=WORK_DIR Where to find build file. Must exist. [default: $TEST_DIR]"
+ echo " --os ARGS VM configuration. This parameter might be used more than once:"
+ echo " --fio-bin=FIO Use specific fio binary (will be uploaded to VM)"
+ echo " --test-cases=TESTS Coma-separated list of tests to run. Implemented test cases are: 1"
+ echo " See test/vhost/test_plan.md for more info."
+ echo " --mgmt-tgt-ip=IP IP address of target."
+ echo " --mgmt-init-ip=IP IP address of initiator."
+ echo " --rdma-tgt-ip=IP IP address of targets rdma capable NIC."
+ echo " --rdma-init-ip=IP IP address of initiators rdma capable NIC."
+ echo "-x set -x for script debug"
+}
+
+for param in "$@"; do
+ case "$param" in
+ --help|-h)
+ usage $0
+ exit 0
+ ;;
+ --work-dir=*) TEST_DIR="${param#*=}" ;;
+ --os=*) os_image="${param#*=}" ;;
+ --fio-bin=*) fio_bin="${param}" ;;
+ --test-cases=*) test_cases="${param#*=}" ;;
+ --mgmt-tgt-ip=*) MGMT_TARGET_IP="${param#*=}" ;;
+ --mgmt-init-ip=*) MGMT_INITIATOR_IP="${param#*=}" ;;
+ --rdma-tgt-ip=*) RDMA_TARGET_IP="${param#*=}" ;;
+ --rdma-init-ip=*) RDMA_INITIATOR_IP="${param#*=}" ;;
+ -x) set -x ;;
+ -v) SPDK_VHOST_VERBOSE=true ;;
+ *)
+ usage $0 "Invalid argument '$param'"
+ exit 1;;
+ esac
+done
+
+. $(readlink -e "$(dirname $0)/../common/common.sh") || exit 1
+MIGRATION_DIR=$(readlink -f $(dirname $0))
+
+[[ ! -z "$test_cases" ]] || fail "Need '--test-cases=' parameter"
+
+trap 'error_exit "${FUNCNAME}" "${LINENO}"' INT ERR EXIT
+
+function vm_monitor_send()
+{
+ local vm_num=$1
+ local cmd_result_file="$2"
+ local vm_dir="$VM_BASE_DIR/$1"
+ local vm_monitor_port=$(cat $vm_dir/monitor_port)
+
+ [[ ! -z "$vm_monitor_port" ]] || fail "No monitor port!"
+
+ shift 2
+ nc 127.0.0.1 $vm_monitor_port "$@" > $cmd_result_file
+}
+
+# Migrate VM $1
+function vm_migrate()
+{
+ local from_vm_dir="$VM_BASE_DIR/$1"
+ local target_vm_dir="$(readlink -e $from_vm_dir/vm_migrate_to)"
+ local target_vm="$(basename $target_vm_dir)"
+ local target_vm_migration_port="$(cat $target_vm_dir/migration_port)"
+ if [[ -n "$2" ]]; then
+ local target_ip=$2
+ else
+ local target_ip="127.0.0.1"
+ fi
+
+ # Sanity check if target VM (QEMU) is configured to accept source VM (QEMU) migration
+ if [[ "$(readlink -e ${target_vm_dir}/vm_incoming)" != "$(readlink -e ${from_vm_dir})" ]]; then
+ fail "source VM $1 or destination VM is not properly configured for live migration"
+ fi
+
+ timing_enter vm_migrate
+ notice "Migrating VM $1 to VM "$(basename $target_vm_dir)
+ echo -e \
+ "migrate_set_speed 1g\n" \
+ "migrate tcp:$target_ip:$target_vm_migration_port\n" \
+ "info migrate\n" \
+ "quit" | vm_monitor_send $1 "$from_vm_dir/migration_result"
+
+ # Post migration checks:
+ if ! grep "Migration status: completed" $from_vm_dir/migration_result -q; then
+ cat $from_vm_dir/migration_result
+ fail "Migration failed:\n"
+ fi
+
+ # Don't perform the following check if target VM is on remote server
+ # as we won't have access to it.
+ # If you need this check then perform it on your own.
+ if [[ "$target_ip" == "127.0.0.1" ]]; then
+ if ! vm_os_booted $target_vm; then
+ fail "VM$target_vm is not running"
+ cat $target_vm $target_vm_dir/cont_result
+ fi
+ fi
+
+ notice "Migration complete"
+ timing_exit vm_migrate
+}
+
+function is_fio_running()
+{
+ local shell_restore_x="$( [[ "$-" =~ x ]] && echo 'set -x' )"
+ set +x
+
+ if vm_ssh $1 'kill -0 $(cat /root/fio.pid)'; then
+ local ret=0
+ else
+ local ret=1
+ fi
+
+ $shell_restore_x
+ return $ret
+}
+
+for test_case in ${test_cases//,/ }; do
+ assert_number "$test_case"
+ notice "==============================="
+ notice "Running Migration test case ${test_case}"
+ notice "==============================="
+
+ timing_enter migration-tc${test_case}
+ source $MIGRATION_DIR/migration-tc${test_case}.sh
+ timing_exit migration-tc${test_case}
+done
+
+notice "Migration Test SUCCESS"
+notice "==============="
+
+trap - SIGINT ERR EXIT
diff --git a/src/spdk/test/vhost/other/conf.json b/src/spdk/test/vhost/other/conf.json
new file mode 100644
index 00000000..7a60c68c
--- /dev/null
+++ b/src/spdk/test/vhost/other/conf.json
@@ -0,0 +1,43 @@
+{
+ "subsystems": [
+ {
+ "subsystem": "copy",
+ "config": null
+ },
+ {
+ "subsystem": "interface",
+ "config": null
+ },
+ {
+ "subsystem": "net_framework",
+ "config": null
+ },
+ {
+ "subsystem": "bdev",
+ "config": [
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768
+ },
+ "method": "construct_malloc_bdev"
+ },
+ {
+ "params": {
+ "block_size": 4096,
+ "num_blocks": 32768
+ },
+ "method": "construct_malloc_bdev"
+ }
+ ]
+ },
+ {
+ "subsystem": "nbd",
+ "config": []
+ },
+ {
+ "subsystem": "scsi",
+ "config": null
+ }
+ ]
+}
diff --git a/src/spdk/test/vhost/other/negative.sh b/src/spdk/test/vhost/other/negative.sh
new file mode 100755
index 00000000..5728a283
--- /dev/null
+++ b/src/spdk/test/vhost/other/negative.sh
@@ -0,0 +1,144 @@
+#!/usr/bin/env bash
+
+NEGATIVE_BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $NEGATIVE_BASE_DIR/../common && pwd)"
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $NEGATIVE_BASE_DIR/../../../../ && pwd)"
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for running vhost app."
+ echo "Usage: $(basename $1) [-x] [-h|--help] [--clean-build] [--work-dir=PATH]"
+ echo "-h, --help print help and exit"
+ echo "-x Set -x for script debug"
+ echo " --work-dir=PATH Where to find source/project. [default=$TEST_DIR]"
+
+ exit 0
+}
+
+run_in_background=false
+while getopts 'xh-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 ;;
+ work-dir=*) TEST_DIR="${OPTARG#*=}" ;;
+ conf-dir=*) CONF_DIR="${OPTARG#*=}" ;;
+ *) usage $0 echo "Invalid argument '$OPTARG'" ;;
+ esac
+ ;;
+ h) usage $0 ;;
+ x) set -x ;;
+ *) usage $0 "Invalid argument '$optchar'" ;;
+ esac
+done
+
+
+. $COMMON_DIR/common.sh
+
+trap error_exit ERR
+
+VHOST_APP="$SPDK_BUILD_DIR/app/vhost/vhost"
+
+notice "Testing vhost command line arguments"
+# Printing help will force vhost to exit without error
+$VHOST_APP -c /path/to/non_existing_file/conf -S $NEGATIVE_BASE_DIR -e 0x0 -s 1024 -d -h --silence-noticelog
+
+# Testing vhost create pid file option. Vhost will exit with error as invalid config path is given
+if $VHOST_APP -c /path/to/non_existing_file/conf -f $SPDK_VHOST_SCSI_TEST_DIR/vhost.pid; then
+ fail "vhost started when specifying invalid config file"
+fi
+
+# Expecting vhost to fail if an incorrect argument is given
+if $VHOST_APP -x -h; then
+ fail "vhost started with invalid -x command line option"
+fi
+
+# Passing trace flags if spdk is build without CONFIG_DEBUG=y option make vhost exit with error
+if ! $VHOST_APP -t vhost_scsi -h; then
+ warning "vhost did not started with trace flags enabled but ignoring this as it might not be a debug build"
+fi
+
+if [[ $RUN_NIGHTLY -eq 1 ]]; then
+ # Run with valid config and try some negative rpc calls
+ notice "==============="
+ notice ""
+ notice "running SPDK"
+ notice ""
+ spdk_vhost_run --json-path=$NEGATIVE_BASE_DIR
+ notice ""
+
+ rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock"
+
+ # General commands
+ notice "Trying to remove nonexistent controller"
+ if $rpc_py remove_vhost_controller unk0 > /dev/null; then
+ error "Removing nonexistent controller succeeded, but it shouldn't"
+ fi
+
+ # SCSI
+ notice "Trying to create scsi controller with incorrect cpumask"
+ if $rpc_py construct_vhost_scsi_controller vhost.invalid.cpumask --cpumask 0x2; then
+ error "Creating scsi controller with incorrect cpumask succeeded, but it shouldn't"
+ fi
+
+ notice "Trying to remove device from nonexistent scsi controller"
+ if $rpc_py remove_vhost_scsi_target vhost.nonexistent.name 0; then
+ error "Removing device from nonexistent scsi controller succeeded, but it shouldn't"
+ fi
+
+ notice "Trying to add device to nonexistent scsi controller"
+ if $rpc_py add_vhost_scsi_lun vhost.nonexistent.name 0 Malloc0; then
+ error "Adding device to nonexistent scsi controller succeeded, but it shouldn't"
+ fi
+
+ notice "Trying to create scsi controller with incorrect name"
+ if $rpc_py construct_vhost_scsi_controller .; then
+ error "Creating scsi controller with incorrect name succeeded, but it shouldn't"
+ fi
+
+ notice "Creating controller naa.0"
+ $rpc_py construct_vhost_scsi_controller naa.0
+
+ notice "Adding initial device (0) to naa.0"
+ $rpc_py add_vhost_scsi_lun naa.0 0 Malloc0
+
+ notice "Trying to remove nonexistent device on existing controller"
+ if $rpc_py remove_vhost_scsi_target naa.0 1 > /dev/null; then
+ error "Removing nonexistent device (1) from controller naa.0 succeeded, but it shouldn't"
+ fi
+
+ notice "Trying to remove existing device from a controller"
+ $rpc_py remove_vhost_scsi_target naa.0 0
+
+ notice "Trying to remove a just-deleted device from a controller again"
+ if $rpc_py remove_vhost_scsi_target naa.0 0 > /dev/null; then
+ error "Removing device 0 from controller naa.0 succeeded, but it shouldn't"
+ fi
+
+ notice "Re-adding device 0 to naa.0"
+ $rpc_py add_vhost_scsi_lun naa.0 0 Malloc0
+
+ # BLK
+ notice "Trying to create block controller with incorrect cpumask"
+ if $rpc_py construct_vhost_blk_controller vhost.invalid.cpumask Malloc0 --cpumask 0x2; then
+ error "Creating block controller with incorrect cpumask succeeded, but it shouldn't"
+ fi
+
+ notice "Trying to remove nonexistent block controller"
+ if $rpc_py remove_vhost_controller vhost.nonexistent.name; then
+ error "Removing nonexistent block controller succeeded, but it shouldn't"
+ fi
+
+ notice "Trying to create block controller with incorrect name"
+ if $rpc_py construct_vhost_blk_controller . Malloc0; then
+ error "Creating block controller with incorrect name succeeded, but it shouldn't"
+ fi
+
+ notice "Testing done -> shutting down"
+ notice "killing vhost app"
+ spdk_vhost_kill
+
+ notice "EXIT DONE"
+ notice "==============="
+fi
diff --git a/src/spdk/test/vhost/perf_bench/vhost_perf.sh b/src/spdk/test/vhost/perf_bench/vhost_perf.sh
new file mode 100755
index 00000000..3789c8f1
--- /dev/null
+++ b/src/spdk/test/vhost/perf_bench/vhost_perf.sh
@@ -0,0 +1,229 @@
+#!/usr/bin/env bash
+set -e
+
+vm_count=1
+vm_memory=2048
+vm_image="/home/sys_sgsw/vhost_vm_image.qcow2"
+max_disks=""
+ctrl_type="spdk_vhost_scsi"
+use_split=false
+throttle=false
+
+lvol_stores=()
+lvol_bdevs=()
+used_vms=""
+
+fio_bin="--fio-bin=/home/sys_sgsw/fio_ubuntu"
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for doing automated test"
+ echo "Usage: $(basename $1) [OPTIONS]"
+ echo
+ echo "-h, --help Print help and exit"
+ echo " --fio-bin=PATH Path to FIO binary on host.;"
+ echo " Binary will be copied to VM, static compilation"
+ echo " of binary is recommended."
+ echo " --fio-job=PATH Fio config to use for test."
+ echo " --vm-count=INT Total number of virtual machines to launch in this test;"
+ echo " Each VM will get one bdev (lvol or split vbdev)"
+ echo " to run FIO test."
+ echo " Default: 1"
+ echo " --vm-memory=INT Amount of RAM memory (in MB) to pass to a single VM."
+ echo " Default: 2048 MB"
+ echo " --vm-image=PATH OS image to use for running the VMs."
+ echo " Default: /home/sys_sgsw/vhost_vm_image.qcow2"
+ echo " --max-disks=INT Maximum number of NVMe drives to use in test."
+ echo " Default: will use all available NVMes."
+ echo " --ctrl-type=TYPE Controller type to use for test:"
+ echo " spdk_vhost_scsi - use spdk vhost scsi"
+ echo " spdk_vhost_blk - use spdk vhost block"
+ echo " Default: spdk_vhost_scsi"
+ echo " --use-split Use split vbdevs instead of Logical Volumes"
+ echo " --throttle=INT I/Os throttle rate in IOPS for each device on the VMs."
+ echo " --custom-cpu-cfg=PATH Custom CPU config for test."
+ echo " Default: spdk/test/vhost/common/autotest.config"
+ echo "-x set -x for script debug"
+ exit 0
+}
+
+function cleanup_lvol_cfg()
+{
+ notice "Removing lvol bdevs"
+ for lvol_bdev in "${lvol_bdevs[@]}"; do
+ $rpc_py destroy_lvol_bdev $lvol_bdev
+ notice "lvol bdev $lvol_bdev removed"
+ done
+
+ notice "Removing lvol stores"
+ for lvol_store in "${lvol_stores[@]}"; do
+ $rpc_py destroy_lvol_store -u $lvol_store
+ notice "lvol store $lvol_store removed"
+ done
+}
+
+function cleanup_split_cfg()
+{
+ notice "Removing split vbdevs"
+ for (( i=0; i<$max_disks; i++ ));do
+ $rpc_py destruct_split_vbdev Nvme${i}n1
+ done
+}
+
+while getopts 'xh-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 ;;
+ fio-bin=*) fio_bin="--fio-bin=${OPTARG#*=}" ;;
+ fio-job=*) fio_job="${OPTARG#*=}" ;;
+ vm-count=*) vm_count="${OPTARG#*=}" ;;
+ vm-memory=*) vm_memory="${OPTARG#*=}" ;;
+ vm-image=*) vm_image="${OPTARG#*=}" ;;
+ max-disks=*) max_disks="${OPTARG#*=}" ;;
+ ctrl-type=*) ctrl_type="${OPTARG#*=}" ;;
+ use-split) use_split=true ;;
+ throttle) throttle=true ;;
+ custom-cpu-cfg=*) custom_cpu_cfg="${OPTARG#*=}" ;;
+ thin-provisioning) thin=" -t " ;;
+ multi-os) multi_os=true ;;
+ *) usage $0 "Invalid argument '$OPTARG'" ;;
+ esac
+ ;;
+ h) usage $0 ;;
+ x) set -x
+ x="-x" ;;
+ *) usage $0 "Invalid argument '$OPTARG'"
+ esac
+done
+
+. $(readlink -e "$(dirname $0)/../common/common.sh") || exit 1
+. $(readlink -e "$(dirname $0)/../../../scripts/common.sh") || exit 1
+COMMON_DIR="$(cd $(readlink -f $(dirname $0))/../common && pwd)"
+rpc_py="$SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock"
+
+if [[ -n $custom_cpu_cfg ]]; then
+ source $custom_cpu_cfg
+fi
+
+if [[ -z $fio_job ]]; then
+ warning "No FIO job specified! Will use default from common directory."
+ fio_job="$COMMON_DIR/fio_jobs/default_integrity.job"
+fi
+
+trap 'error_exit "${FUNCNAME}" "${LINENO}"' INT ERR
+notice "Get NVMe disks:"
+nvmes=($(iter_pci_class_code 01 08 02))
+
+if [[ -z $max_disks ]]; then
+ max_disks=${#nvmes[@]}
+fi
+
+if [[ ${#nvmes[@]} -lt max_disks ]]; then
+ fail "Number of NVMe drives (${#nvmes[@]}) is lower than number of requested disks for test ($max_disks)"
+fi
+
+notice "running SPDK vhost"
+spdk_vhost_run
+notice "..."
+
+# Calculate number of needed splits per NVMe
+# so that each VM gets it's own bdev during test
+splits=()
+
+#Calculate least minimum number of splits on each disks
+for i in `seq 0 $((max_disks - 1))`; do
+ splits+=( $((vm_count / max_disks)) )
+done
+
+# Split up the remainder
+for i in `seq 0 $((vm_count % max_disks - 1))`; do
+ (( splits[i]++ ))
+done
+
+notice "Preparing NVMe setup..."
+notice "Using $max_disks physical NVMe drives"
+notice "Nvme split list: ${splits[@]}"
+# Prepare NVMes - Lvols or Splits
+if [[ $use_split == true ]]; then
+ notice "Using split vbdevs"
+ trap 'cleanup_split_cfg; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR
+ split_bdevs=()
+ for (( i=0; i<$max_disks; i++ ));do
+ out=$($rpc_py construct_split_vbdev Nvme${i}n1 ${splits[$i]})
+ for s in $out; do
+ split_bdevs+=("$s")
+ done
+ done
+ bdevs=("${split_bdevs[@]}")
+else
+ notice "Using logical volumes"
+ trap 'cleanup_lvol_cfg; error_exit "${FUNCNAME}" "${LINENO}"' INT ERR
+ for (( i=0; i<$max_disks; i++ ));do
+ ls_guid=$($rpc_py construct_lvol_store Nvme${i}n1 lvs_$i)
+ lvol_stores+=("$ls_guid")
+ for (( j=0; j<${splits[$i]}; j++)); do
+ free_mb=$(get_lvs_free_mb "$ls_guid")
+ size=$((free_mb / (${splits[$i]}-j) ))
+ lb_name=$($rpc_py construct_lvol_bdev -u $ls_guid lbd_$j $size)
+ lvol_bdevs+=("$lb_name")
+ done
+ done
+ bdevs=("${lvol_bdevs[@]}")
+fi
+
+# Prepare VMs and controllers
+for (( i=0; i<$vm_count; i++)); do
+ vm="vm_$i"
+
+ setup_cmd="vm_setup --disk-type=$ctrl_type --force=$i"
+ setup_cmd+=" --os=$vm_image"
+
+ if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then
+ $rpc_py construct_vhost_scsi_controller naa.0.$i
+ $rpc_py add_vhost_scsi_lun naa.0.$i 0 ${bdevs[$i]}
+ setup_cmd+=" --disks=0"
+ elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then
+ $rpc_py construct_vhost_blk_controller naa.$i.$i ${bdevs[$i]}
+ setup_cmd+=" --disks=$i"
+ fi
+ $setup_cmd
+ used_vms+=" $i"
+done
+
+# Start VMs
+# Run VMs
+vm_run $used_vms
+vm_wait_for_boot 300 $used_vms
+
+# Run FIO
+fio_disks=""
+for vm_num in $used_vms; do
+ vm_dir=$VM_BASE_DIR/$vm_num
+ host_name="VM-$vm_num"
+ vm_ssh $vm_num "hostname $host_name"
+ vm_start_fio_server $fio_bin $vm_num
+
+ if [[ "$ctrl_type" == "spdk_vhost_scsi" ]]; then
+ vm_check_scsi_location $vm_num
+ elif [[ "$ctrl_type" == "spdk_vhost_blk" ]]; then
+ vm_check_blk_location $vm_num
+ fi
+
+ fio_disks+=" --vm=${vm_num}$(printf ':/dev/%s' $SCSI_DISK)"
+done
+
+# Run FIO traffic
+run_fio $fio_bin --job-file="$fio_job" --out="$TEST_DIR/fio_results" --json $fio_disks
+
+notice "Shutting down virtual machines..."
+vm_shutdown_all
+
+#notice "Shutting down SPDK vhost app..."
+if [[ $use_split == true ]]; then
+ cleanup_split_cfg
+else
+ cleanup_lvol_cfg
+fi
+spdk_vhost_kill
diff --git a/src/spdk/test/vhost/readonly/delete_partition_vm.sh b/src/spdk/test/vhost/readonly/delete_partition_vm.sh
new file mode 100755
index 00000000..18230896
--- /dev/null
+++ b/src/spdk/test/vhost/readonly/delete_partition_vm.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+
+set -xe
+BASE_DIR=$(readlink -f $(dirname $0))
+
+disk_name="vda"
+test_folder_name="readonly_test"
+test_file_name="some_test_file"
+
+function error()
+{
+ echo "==========="
+ echo -e "ERROR: $@"
+ echo "==========="
+ trap - ERR
+ set +e
+ umount "$test_folder_name"
+ rm -rf "$BASE_DIR/$test_folder_name"
+ exit 1
+}
+
+trap 'error "In delete_partition_vm.sh, line:" "${LINENO}"' ERR
+
+if [[ ! -d "/sys/block/$disk_name" ]]; then
+ error "No vhost-blk disk found!"
+fi
+
+if (( $(lsblk -r -n -o RO -d "/dev/$disk_name") == 1 )); then
+ error "Vhost-blk disk is set as readonly!"
+fi
+
+mkdir -p $test_folder_name
+
+echo "INFO: Mounting disk"
+mount /dev/$disk_name"1" $test_folder_name
+
+echo "INFO: Removing folder and unmounting $test_folder_name"
+umount "$test_folder_name"
+rm -rf "$BASE_DIR/$test_folder_name"
+
+echo "INFO: Deleting partition"
+echo -e "d\n1\nw" | fdisk /dev/$disk_name
diff --git a/src/spdk/test/vhost/readonly/disabled_readonly_vm.sh b/src/spdk/test/vhost/readonly/disabled_readonly_vm.sh
new file mode 100755
index 00000000..bd202433
--- /dev/null
+++ b/src/spdk/test/vhost/readonly/disabled_readonly_vm.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+set -xe
+BASE_DIR=$(readlink -f $(dirname $0))
+
+disk_name="vda"
+test_folder_name="readonly_test"
+test_file_name="some_test_file"
+
+function error()
+{
+ echo "==========="
+ echo -e "ERROR: $@"
+ echo "==========="
+ trap - ERR
+ set +e
+ umount "$test_folder_name"
+ rm -rf "$BASE_DIR/$test_folder_name"
+ exit 1
+}
+
+trap 'error "In disabled_readonly_vm.sh, line:" "${LINENO}"' ERR
+
+if [[ ! -d "/sys/block/$disk_name" ]]; then
+ error "No vhost-blk disk found!"
+fi
+
+if (( $(lsblk -r -n -o RO -d "/dev/$disk_name") == 1 )); then
+ error "Vhost-blk disk is set as readonly!"
+fi
+
+parted -s /dev/$disk_name mklabel gpt
+parted -s /dev/$disk_name mkpart primary 2048s 100%
+partprobe
+sleep 0.1
+
+echo "INFO: Creating file system"
+mkfs.ext4 -F /dev/$disk_name"1"
+
+echo "INFO: Mounting disk"
+mkdir -p $test_folder_name
+mount /dev/$disk_name"1" $test_folder_name
+
+echo "INFO: Creating a test file $test_file_name"
+truncate -s "200M" $test_folder_name/$test_file_name
+umount "$test_folder_name"
+rm -rf "$BASE_DIR/$test_folder_name"
diff --git a/src/spdk/test/vhost/readonly/enabled_readonly_vm.sh b/src/spdk/test/vhost/readonly/enabled_readonly_vm.sh
new file mode 100755
index 00000000..79cf1ae5
--- /dev/null
+++ b/src/spdk/test/vhost/readonly/enabled_readonly_vm.sh
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+
+set -x
+BASE_DIR=$(readlink -f $(dirname $0))
+
+disk_name="vda"
+test_folder_name="readonly_test"
+test_file_name="some_test_file"
+
+function error()
+{
+ echo "==========="
+ echo -e "ERROR: $@"
+ echo "==========="
+ umount "$test_folder_name"
+ rm -rf "$BASE_DIR/$test_folder_name"
+ exit 1
+}
+
+if [[ ! -d "/sys/block/$disk_name" ]]; then
+ error "No vhost-blk disk found!"
+fi
+
+if (( $(lsblk -r -n -o RO -d "/dev/$disk_name") == 0 )); then
+ error "Vhost-blk disk is not set as readonly!"
+fi
+
+echo "INFO: Found vhost-blk disk with readonly flag"
+if [[ ! -b "/dev/$disk_name"1"" ]]; then
+ error "Partition not found!"
+fi
+
+mkdir $BASE_DIR/$test_folder_name
+if [[ $? != 0 ]]; then
+ error "Failed to create test folder $test_folder_name"
+fi
+
+echo "INFO: Mounting partition"
+mount /dev/$disk_name"1" $BASE_DIR/$test_folder_name
+if [[ $? != 0 ]]; then
+ error "Failed to mount partition $disk_name""1"
+fi
+
+echo "INFO: Trying to create file on readonly disk"
+truncate -s "200M" $test_folder_name/$test_file_name"_on_readonly"
+if [[ $? == 0 ]]; then
+ error "Created a file on a readonly disk!"
+fi
+
+if [[ -f $test_folder_name/$test_file_name ]]; then
+ echo "INFO: Trying to delete previously created file"
+ rm $test_folder_name/$test_file_name
+ if [[ $? == 0 ]]; then
+ error "Deleted a file from a readonly disk!"
+ fi
+else
+ error "Previously created file not found!"
+fi
+
+echo "INFO: Copying file from readonly disk"
+cp $test_folder_name/$test_file_name $BASE_DIR
+if ! rm $BASE_DIR/$test_file_name; then
+ error "Copied file from a readonly disk was not found!"
+fi
+
+umount "$test_folder_name"
+rm -rf "$BASE_DIR/$test_folder_name"
+echo "INFO: Trying to create file system on a readonly disk"
+if mkfs.ext4 -F /dev/$disk_name"1"; then
+ error "Created file system on a readonly disk!"
+fi
+
+echo "INFO: Trying to delete partition from readonly disk"
+if echo -e "d\n1\nw" | fdisk /dev/$disk_name; then
+ error "Deleted partition from readonly disk!"
+fi
diff --git a/src/spdk/test/vhost/readonly/readonly.sh b/src/spdk/test/vhost/readonly/readonly.sh
new file mode 100755
index 00000000..d0b7968f
--- /dev/null
+++ b/src/spdk/test/vhost/readonly/readonly.sh
@@ -0,0 +1,132 @@
+#!/usr/bin/env bash
+
+set -e
+READONLY_BASE_DIR=$(readlink -f $(dirname $0))
+[[ -z "$TEST_DIR" ]] && TEST_DIR="$(cd $READONLY_BASE_DIR/../../../../ && pwd)"
+[[ -z "$COMMON_DIR" ]] && COMMON_DIR="$(cd $READONLY_BASE_DIR/../common && pwd)"
+source $COMMON_DIR/common.sh
+
+rpc_py="$READONLY_BASE_DIR/../../../scripts/rpc.py -s $(get_vhost_dir)/rpc.sock"
+
+vm_img=""
+disk="Nvme0n1"
+x=""
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Shortcut script for automated readonly test for vhost-block"
+ echo "For test details check test_plan.md"
+ echo
+ echo "Usage: $(basename $1) [OPTIONS]"
+ echo
+ echo "-h, --help Print help and exit"
+ echo " --vm_image= Path to VM image"
+ echo " --disk= Disk name."
+ echo " If disk=malloc, then creates malloc disk. For malloc disks, size is always 512M,"
+ echo " e.g. --disk=malloc. (Default: Nvme0n1)"
+ echo "-x set -x for script debug"
+}
+
+while getopts 'xh-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ help) usage $0 && exit 0;;
+ vm_image=*) vm_img="${OPTARG#*=}" ;;
+ disk=*) disk="${OPTARG#*=}" ;;
+ *) usage $0 "Invalid argument '$OPTARG'" && exit 1
+ esac
+ ;;
+ h) usage $0 && exit 0 ;;
+ x) set -x
+ x="-x" ;;
+ *) usage $0 "Invalid argument '$OPTARG'" && exit 1
+ esac
+done
+
+trap 'error_exit "${FUNCNAME}" "${LINENO}"' ERR
+
+if [[ $EUID -ne 0 ]]; then
+ fail "Go away user come back as root"
+fi
+
+function print_tc_name()
+{
+ notice ""
+ notice "==============================================================="
+ notice "Now running: $1"
+ notice "==============================================================="
+}
+
+function blk_ro_tc1()
+{
+ print_tc_name ${FUNCNAME[0]}
+ local vm_no="0"
+ local disk_name=$disk
+ local vhost_blk_name=""
+ local vm_dir="$TEST_DIR/vms/$vm_no"
+
+ if [[ $disk =~ .*malloc.* ]]; then
+ disk_name=$($rpc_py construct_malloc_bdev 512 4096)
+ if [ $? != 0 ]; then
+ fail "Failed to create malloc bdev"
+ fi
+
+ disk=$disk_name
+ else
+ disk_name=${disk%%_*}
+ if ! $rpc_py get_bdevs | jq -r '.[] .name' | grep -qi $disk_name$; then
+ fail "$disk_name bdev not found!"
+ fi
+ fi
+
+#Create controller and create file on disk for later test
+ notice "Creating vhost_blk controller"
+ vhost_blk_name="naa.$disk_name.$vm_no"
+ $rpc_py construct_vhost_blk_controller $vhost_blk_name $disk_name
+ vm_setup --disk-type=spdk_vhost_blk --force=$vm_no --os=$vm_img --disks=$disk --read-only=true
+
+ vm_run $vm_no
+ vm_wait_for_boot 600 $vm_no
+ notice "Preparing partition and file on guest VM"
+ vm_ssh $vm_no "bash -s" < $READONLY_BASE_DIR/disabled_readonly_vm.sh
+ sleep 1
+
+ vm_shutdown_all
+#Create readonly controller and test readonly feature
+ notice "Removing controller and creating new one with readonly flag"
+ $rpc_py remove_vhost_controller $vhost_blk_name
+ $rpc_py construct_vhost_blk_controller -r $vhost_blk_name $disk_name
+
+ vm_run $vm_no
+ vm_wait_for_boot 600 $vm_no
+ notice "Testing readonly feature on guest VM"
+ vm_ssh $vm_no "bash -s" < $READONLY_BASE_DIR/enabled_readonly_vm.sh
+ sleep 3
+
+ vm_shutdown_all
+#Delete file from disk and delete partition
+ echo "INFO: Removing controller and creating new one"
+ $rpc_py remove_vhost_controller $vhost_blk_name
+ $rpc_py construct_vhost_blk_controller $vhost_blk_name $disk_name
+
+ vm_run $vm_no
+ vm_wait_for_boot 600 $vm_no
+ notice "Removing partition and file from test disk on guest VM"
+ vm_ssh $vm_no "bash -s" < $READONLY_BASE_DIR/delete_partition_vm.sh
+ sleep 1
+
+ vm_shutdown_all
+}
+
+spdk_vhost_run
+if [[ -z $x ]]; then
+ set +x
+fi
+
+blk_ro_tc1
+
+$rpc_py delete_nvme_controller Nvme0
+
+spdk_vhost_kill
diff --git a/src/spdk/test/vhost/readonly/test_plan.md b/src/spdk/test/vhost/readonly/test_plan.md
new file mode 100644
index 00000000..957000e8
--- /dev/null
+++ b/src/spdk/test/vhost/readonly/test_plan.md
@@ -0,0 +1,30 @@
+# vhost-block readonly feature test plan
+
+## Objective
+Vhost block controllers can be created with readonly feature which prevents any write operations on this device.
+The purpose of this test is to verify proper operation of this feature.
+
+## Test cases description
+To test readonly feature, this test will create normal vhost-blk controller with NVMe device and on a VM it will
+create and mount a partition to which it will copy a file. Next it will poweroff a VM, remove vhost controller and
+create new readonly vhost-blk controller with the same device.
+
+## Test cases
+
+### blk_ro_tc1
+1. Start vhost
+2. Create vhost-blk controller with NVMe device and readonly feature disabled using RPC
+3. Run VM with attached vhost-blk controller
+4. Check visibility of readonly flag using lsblk, fdisk
+5. Create new partition
+6. Create new file on new partition
+7. Shutdown VM, remove vhost controller
+8. Create vhost-blk with previously used NVMe device and readonly feature now enabled using RPC
+9. Run VM with attached vhost-blk controller
+10. Check visibility of readonly flag using lsblk, fdisk
+11. Try to delete previous file
+12. Try to create new file
+13. Try to remove partition
+14. Repeat steps 2 to 4
+15. Remove file from disk, delete partition
+16. Shutdown VM, exit vhost
diff --git a/src/spdk/test/vhost/spdk_vhost.sh b/src/spdk/test/vhost/spdk_vhost.sh
new file mode 100755
index 00000000..a7c0a6ba
--- /dev/null
+++ b/src/spdk/test/vhost/spdk_vhost.sh
@@ -0,0 +1,210 @@
+#!/usr/bin/env bash
+rootdir=$(readlink -f $(dirname $0))/../..
+source "$rootdir/test/common/autotest_common.sh"
+
+set -e
+
+DEFAULT_VM_IMAGE="/home/sys_sgsw/vhost_vm_image.qcow2"
+CENTOS_VM_IMAGE="/home/sys_sgsw/spdk_vhost_CentOS_vm_image.qcow2"
+DEFAULT_FIO_BIN="/home/sys_sgsw/fio_ubuntu"
+CENTOS_FIO_BIN="/home/sys_sgsw/fio_ubuntu_bak"
+
+case $1 in
+ -h|--help)
+ echo "usage: $(basename $0) TEST_TYPE"
+ echo "Test type can be:"
+ echo " -i |--integrity for running an integrity test with vhost scsi"
+ echo " -fs|--fs-integrity-scsi for running an integrity test with filesystem"
+ echo " -fb|--fs-integrity-blk for running an integrity test with filesystem"
+ echo " -p |--performance for running a performance test with vhost scsi"
+ echo " -ib|--integrity-blk for running an integrity test with vhost blk"
+ echo " -pb|--performance-blk for running a performance test with vhost blk"
+ echo " -ils|--integrity-lvol-scsi for running an integrity test with vhost scsi and lvol backends"
+ echo " -ilb|--integrity-lvol-blk for running an integrity test with vhost blk and lvol backends"
+ echo " -ilsn|--integrity-lvol-scsi-nightly for running an nightly integrity test with vhost scsi and lvol backends"
+ echo " -ilbn|--integrity-lvol-blk-nightly for running an nightly integrity test with vhost blk and lvol backends"
+ echo " -hp|--hotplug for running hotplug tests"
+ echo " -shr|--scsi-hot-remove for running scsi hot remove tests"
+ echo " -bhr|--blk-hot-remove for running blk hot remove tests"
+ echo " -ro|--readonly for running readonly test for vhost blk"
+ echo " -b|--boot for booting vm from vhost controller"
+ echo " -h |--help prints this message"
+ echo ""
+ echo "Environment:"
+ echo " VM_IMAGE path to QCOW2 VM image used during test (default: $DEFAULT_VM_IMAGE)"
+ echo ""
+ echo "Tests are performed only on Linux machine. For other OS no action is performed."
+ echo ""
+ exit 0;
+ ;;
+esac
+
+echo "Running SPDK vhost fio autotest..."
+if [[ $(uname -s) != Linux ]]; then
+ echo ""
+ echo "INFO: Vhost tests are only for Linux machine."
+ echo ""
+ exit 0
+fi
+
+: ${VM_IMAGE="$DEFAULT_VM_IMAGE"}
+: ${FIO_BIN="$DEFAULT_FIO_BIN"}
+
+if [[ ! -r "${VM_IMAGE}" ]]; then
+ echo ""
+ echo "ERROR: VM image '${VM_IMAGE}' does not exist."
+ echo ""
+ exit 1
+fi
+
+DISKS_NUMBER=`lspci -mm -n | grep 0108 | tr -d '"' | awk -F " " '{print "0000:"$1}'| wc -l`
+
+WORKDIR=$(readlink -f $(dirname $0))
+
+case $1 in
+ -n|--negative)
+ echo 'Negative tests suite...'
+ run_test case $WORKDIR/other/negative.sh
+ report_test_completion "vhost_negative"
+ ;;
+ -p|--performance)
+ echo 'Running performance suite...'
+ run_test case $WORKDIR/fiotest/autotest.sh --fio-bin=$FIO_BIN \
+ --vm=0,$VM_IMAGE,Nvme0n1p0 \
+ --test-type=spdk_vhost_scsi \
+ --fio-job=$WORKDIR/common/fio_jobs/default_performance.job
+ report_test_completion "vhost_perf"
+ ;;
+ -pb|--performance-blk)
+ echo 'Running blk performance suite...'
+ run_test case $WORKDIR/fiotest/autotest.sh --fio-bin=$FIO_BIN \
+ --vm=0,$VM_IMAGE,Nvme0n1p0 \
+ --test-type=spdk_vhost_blk \
+ --fio-job=$WORKDIR/common/fio_jobs/default_performance.job
+ report_test_completion "vhost_perf_blk"
+ ;;
+ -m|--migration)
+ echo 'Running migration suite...'
+ run_test case $WORKDIR/migration/migration.sh -x \
+ --fio-bin=$FIO_BIN --os=$VM_IMAGE --test-cases=1,2
+ ;;
+ -i|--integrity)
+ echo 'Running SCSI integrity suite...'
+ run_test case $WORKDIR/fiotest/autotest.sh -x --fio-bin=$FIO_BIN \
+ --vm=0,$VM_IMAGE,Nvme0n1p0:Nvme0n1p1:Nvme0n1p2:Nvme0n1p3 \
+ --test-type=spdk_vhost_scsi \
+ --fio-job=$WORKDIR/common/fio_jobs/default_integrity.job
+ report_test_completion "nightly_vhost_integrity"
+ ;;
+ -ib|--integrity-blk)
+ echo 'Running blk integrity suite...'
+ run_test case $WORKDIR/fiotest/autotest.sh -x --fio-bin=$FIO_BIN \
+ --vm=0,$VM_IMAGE,Nvme0n1p0:Nvme0n1p1:Nvme0n1p2:Nvme0n1p3 \
+ --test-type=spdk_vhost_blk \
+ --fio-job=$WORKDIR/common/fio_jobs/default_integrity.job
+ report_test_completion "nightly_vhost_integrity_blk"
+ ;;
+ -fs|--fs-integrity-scsi)
+ echo 'Running filesystem integrity suite with SCSI...'
+ run_test case $WORKDIR/integrity/integrity_start.sh --ctrl-type=spdk_vhost_scsi --fs="xfs ntfs btrfs ext4"
+ report_test_completion "vhost_fs_integrity_scsi"
+ ;;
+ -fb|--fs-integrity-blk)
+ echo 'Running filesystem integrity suite with BLK...'
+ run_test case $WORKDIR/integrity/integrity_start.sh --ctrl-type=spdk_vhost_blk --fs="xfs ntfs btrfs ext4"
+ report_test_completion "vhost_fs_integrity_blk"
+ ;;
+ -ils|--integrity-lvol-scsi)
+ echo 'Running lvol integrity suite...'
+ run_test case $WORKDIR/lvol/lvol_test.sh -x --fio-bin=$FIO_BIN \
+ --ctrl-type=spdk_vhost_scsi --thin-provisioning
+ report_test_completion "vhost_integrity_lvol_scsi"
+ ;;
+ -ilb|--integrity-lvol-blk)
+ echo 'Running lvol integrity suite...'
+ run_test case $WORKDIR/lvol/lvol_test.sh -x --fio-bin=$FIO_BIN \
+ --ctrl-type=spdk_vhost_blk
+ report_test_completion "vhost_integrity_lvol_blk"
+ ;;
+ -ilsn|--integrity-lvol-scsi-nightly)
+ if [[ $DISKS_NUMBER -ge 2 ]]; then
+ echo 'Running lvol integrity nightly suite with two cores and two controllers'
+ run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \
+ --ctrl-type=spdk_vhost_scsi --max-disks=2 --distribute-cores --vm-count=2
+
+ echo 'Running lvol integrity nightly suite with one core and two controllers'
+ run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \
+ --ctrl-type=spdk_vhost_scsi --max-disks=2 --vm-count=2
+ fi
+ if [[ -e $CENTOS_VM_IMAGE ]]; then
+ echo 'Running lvol integrity nightly suite with different os types'
+ run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$CENTOS_FIO_BIN \
+ --ctrl-type=spdk_vhost_scsi --vm-count=2 --multi-os
+ fi
+ echo 'Running lvol integrity nightly suite with one core and one controller'
+ run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \
+ --ctrl-type=spdk_vhost_scsi --max-disks=1
+ ;;
+ -ilbn|--integrity-lvol-blk-nightly)
+ if [[ $DISKS_NUMBER -ge 2 ]]; then
+ echo 'Running lvol integrity nightly suite with two cores and two controllers'
+ run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \
+ --ctrl-type=spdk_vhost_blk --max-disks=2 --distribute-cores --vm-count=2
+
+ echo 'Running lvol integrity nightly suite with one core and two controllers'
+ run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \
+ --ctrl-type=spdk_vhost_blk --max-disks=2 --vm-count=2
+ fi
+ if [[ -e $CENTOS_VM_IMAGE ]]; then
+ echo 'Running lvol integrity nightly suite with different os types'
+ run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$CENTOS_FIO_BIN \
+ --ctrl-type=spdk_vhost_blk --vm-count=2 --multi-os
+ fi
+ echo 'Running lvol integrity nightly suite with one core and one controller'
+ run_test case $WORKDIR/lvol/lvol_test.sh --fio-bin=$FIO_BIN \
+ --ctrl-type=spdk_vhost_blk --max-disks=1
+ ;;
+ -hp|--hotplug)
+ echo 'Running hotplug tests suite...'
+ run_test case $WORKDIR/hotplug/scsi_hotplug.sh --fio-bin=$FIO_BIN \
+ --vm=0,$VM_IMAGE,Nvme0n1p0:Nvme0n1p1 \
+ --vm=1,$VM_IMAGE,Nvme0n1p2:Nvme0n1p3 \
+ --vm=2,$VM_IMAGE,Nvme0n1p4:Nvme0n1p5 \
+ --vm=3,$VM_IMAGE,Nvme0n1p6:Nvme0n1p7 \
+ --test-type=spdk_vhost_scsi \
+ --fio-jobs=$WORKDIR/hotplug/fio_jobs/default_integrity.job -x
+ report_test_completion "vhost_hotplug"
+ ;;
+ -shr|--scsi-hot-remove)
+ echo 'Running scsi hotremove tests suite...'
+ run_test case $WORKDIR/hotplug/scsi_hotplug.sh --fio-bin=$FIO_BIN \
+ --vm=0,$VM_IMAGE,Nvme0n1p0:Nvme0n1p1 \
+ --vm=1,$VM_IMAGE,Nvme0n1p2:Nvme0n1p3 \
+ --test-type=spdk_vhost_scsi \
+ --scsi-hotremove-test \
+ --fio-jobs=$WORKDIR/hotplug/fio_jobs/default_integrity.job
+ ;;
+ -bhr|--blk-hot-remove)
+ echo 'Running blk hotremove tests suite...'
+ run_test case $WORKDIR/hotplug/scsi_hotplug.sh --fio-bin=$FIO_BIN \
+ --vm=0,$VM_IMAGE,Nvme0n1p0:Nvme0n1p1 \
+ --vm=1,$VM_IMAGE,Nvme0n1p2:Nvme0n1p3 \
+ --test-type=spdk_vhost_blk \
+ --blk-hotremove-test \
+ --fio-jobs=$WORKDIR/hotplug/fio_jobs/default_integrity.job
+ ;;
+ -ro|--readonly)
+ echo 'Running readonly tests suite...'
+ run_test case $WORKDIR/readonly/readonly.sh --vm_image=$VM_IMAGE --disk=Nvme0n1 -x
+ report_test_completion "vhost_readonly"
+ ;;
+ -b|--boot)
+ echo 'Running os boot from vhost controller...'
+ $WORKDIR/vhost_boot/vhost_boot.sh --vm_image=$VM_IMAGE
+ report_test_completion "vhost_boot"
+ ;;
+ *)
+ echo "unknown test type: $1"
+ exit 1
+ ;;
+esac
diff --git a/src/spdk/test/vhost/test_plan.md b/src/spdk/test/vhost/test_plan.md
new file mode 100644
index 00000000..b412436a
--- /dev/null
+++ b/src/spdk/test/vhost/test_plan.md
@@ -0,0 +1,252 @@
+# SPDK vhost Test Plan
+
+## Current Tests
+
+### Integrity tests
+
+#### vhost self test
+- compiles SPDK and Qemu
+- launches SPDK Vhost
+- starts VM with 1 NVMe device attached to it
+- issues controller "reset" command using sg3_utils on guest system
+- performs data integrity check using dd to write and read data from the device
+- runs on 3 host systems (Ubuntu 16.04, Centos 7.3 and Fedora 25)
+ and 1 guest system (Ubuntu 16.04)
+- runs against vhost scsi and vhost blk
+
+#### FIO Integrity tests
+- NVMe device is split into 4 LUNs, each is attached to separate vhost controller
+- FIO uses job configuration with randwrite mode to verify if random pattern was
+ written to and read from correctly on each LUN
+- runs on Fedora 25 and Ubuntu 16.04 guest systems
+- runs against vhost scsi and vhost blk
+
+#### Lvol tests
+- starts vhost with at least 1 NVMe device
+- starts 1 VM or multiple VMs
+- lvol store is constructed on each NVMe device
+- on each lvol store 1 lvol bdev will be constructed for each running VM
+- Logical volume block device is used as backend instead of using
+ NVMe device backend directly
+- after set up, data integrity check will be performed by FIO randwrite
+ operation with verify flag enabled
+- optionally nested lvols can be tested with use of appropriate flag;
+ On each base lvol store additional lvol bdev will be created which will
+ serve as a base for nested lvol stores.
+ On each of the nested lvol stores there will be 1 lvol bdev created for each
+ VM running. Nested lvol bdevs will be used along with base lvol bdevs for
+ data integrity check.
+- runs against vhost scsi and vhost blk
+
+#### Filesystem integrity
+- runs SPDK with 1 VM with 1 NVMe device attached.
+- creates a partition table and filesystem on passed device, and mounts it
+- 1GB test file is created on mounted file system and FIO randrw traffic
+ (with enabled verification) is run
+- Tested file systems: ext4, brtfs, ntfs, xfs
+- runs against vhost scsi and vhost blk
+
+#### Windows HCK SCSI Compliance Test 2.0.
+- Runs SPDK with 1 VM with Windows Server 2012 R2 operating system
+- 4 devices are passed into the VM: NVMe, Split NVMe, Malloc and Split Malloc
+- On each device Windows HCK SCSI Compliance Test 2.0 is run
+
+#### MultiOS test
+- start 3 VMs with guest systems: Ubuntu 16.04, Fedora 25 and Windows Server 2012 R2
+- 3 physical NVMe devices are split into 9 LUNs
+- each guest uses 3 LUNs from 3 different physical NVMe devices
+- Linux guests run FIO integrity jobs to verify read/write operations,
+ while Windows HCK SCSI Compliance Test 2.0 is running on Windows guest
+
+#### vhost hot-remove tests
+- removing NVMe device (unbind from driver) which is already claimed
+ by controller in vhost
+- hotremove tests performed with and without I/O traffic to device
+- I/O traffic, if present in test, has verification enabled
+- checks that vhost and/or VMs do not crash
+- checks that other devices are unaffected by hot-remove of a NVMe device
+- performed against vhost blk and vhost scsi
+
+#### vhost scsi hot-attach and hot-detach tests
+- adding and removing devices via RPC to a controller which is already in use by a VM
+- I/O traffic generated with FIO read/write operations, verification enabled
+- checks that vhost and/or VMs do not crash
+- checks that other devices in the same controller are unaffected by hot-attach
+ and hot-detach operations
+
+#### virtio initiator tests
+- virtio user mode: connect to vhost-scsi controller sockets directly on host
+- virtio pci mode: connect to virtual pci devices on guest virtual machine
+- 6 concurrent jobs are run simultaneously on 7 devices, each with 8 virtqueues
+
+##### kernel virtio-scsi-pci device
+- test support for kernel vhost-scsi device
+- create 1GB ramdisk using targetcli
+- create target and add ramdisk to it using targetcli
+- add created device to virtio pci tests
+
+##### emulated virtio-scsi-pci device
+- test support for QEMU emulated virtio-scsi-pci device
+- add emulated virtio device "Virtio0" to virtio pci tests
+
+##### Test configuration
+- SPDK vhost application is used for testing
+- FIO using spdk fio_plugin: rw, randrw, randwrite, write with verification enabled.
+- trim sequential and trim random then write on trimmed areas with verification enabled
+ only on unmap supporting devices
+- FIO job configuration: iodepth=128, block size=4k, runtime=10s
+- all test cases run jobs in parallel on multiple bdevs
+- 8 queues per device
+
+##### vhost configuration
+- scsi controller with 4 NVMe splits
+- 2 block controllers, each with 1 NVMe split
+- scsi controller with malloc with 512 block size
+- scsi controller with malloc with 4096 block size
+
+##### Test case 1
+- virtio user on host
+- perform FIO rw, randwrite, randrw, write, parallel jobs on all devices
+
+##### Test case 2
+- virtio user on host
+- perform FIO trim, randtrim, rw, randwrite, randrw, write, - parallel jobs
+ then write on trimmed areas on unmap supporting devices
+
+##### Test case 3
+- virtio pci on vm
+- same config as in TC#1
+
+##### Test case 4
+- virtio pci on vm
+- same config as in TC#2
+
+### Live migration
+Live migration feature allows to move running virtual machines between SPDK vhost
+instances.
+Following tests include scenarios with SPDK vhost instances running on both the same
+physical server and between remote servers.
+Additional configuration of utilities like SSHFS share, NIC IP address adjustment,
+etc., might be necessary.
+
+#### Test case 1 - single vhost migration
+- Start SPDK Vhost application.
+ - Construct a single Malloc bdev.
+ - Construct two SCSI controllers and add previously created Malloc bdev to it.
+- Start first VM (VM_1) and connect to Vhost_1 controller.
+ Verify if attached disk is visible in the system.
+- Start second VM (VM_2) but with "-incoming" option enabled, connect to.
+ Connect to Vhost_2 controller. Use the same VM image as VM_1.
+- On VM_1 start FIO write job with verification enabled to connected Malloc bdev.
+- Start VM migration from VM_1 to VM_2 while FIO is still running on VM_1.
+- Once migration is complete check the result using Qemu monitor. Migration info
+ on VM_1 should return "Migration status: completed".
+- VM_2 should be up and running after migration. Via SSH log in and check FIO
+ job result - exit code should be 0 and there should be no data verification errors.
+- Cleanup:
+ - Shutdown both VMs.
+ - Gracefully shutdown Vhost instance.
+
+#### Test case 2 - single server migration
+- Detect RDMA NICs; At least 1 RDMA NIC is needed to run the test.
+ If there is no physical NIC available then emulated Soft Roce NIC will
+ be used instead.
+- Create /tmp/share directory and put a test VM image in there.
+- Start SPDK NVMeOF Target application.
+ - Construct a single NVMe bdev from available bound NVMe drives.
+ - Create NVMeoF subsystem with NVMe bdev as single namespace.
+- Start first SDPK Vhost application instance (later referred to as "Vhost_1").
+ - Use different shared memory ID and CPU mask than NVMeOF Target.
+ - Construct a NVMe bdev by connecting to NVMeOF Target
+ (using trtype: rdma).
+ - Construct a single SCSI controller and add NVMe bdev to it.
+- Start first VM (VM_1) and connect to Vhost_1 controller. Verify if attached disk
+ is visible in the system.
+- Start second SDPK Vhost application instance (later referred to as "Vhost_2").
+ - Use different shared memory ID and CPU mask than previous SPDK instances.
+ - Construct a NVMe bdev by connecting to NVMeOF Target. Connect to the same
+ subsystem as Vhost_1, multiconnection is allowed.
+ - Construct a single SCSI controller and add NVMe bdev to it.
+- Start second VM (VM_2) but with "-incoming" option enabled.
+- Check states of both VMs using Qemu monitor utility.
+ VM_1 should be in running state.
+ VM_2 should be in paused (inmigrate) state.
+- Run FIO I/O traffic with verification enabled on to attached NVME on VM_1.
+- While FIO is running issue a command for VM_1 to migrate.
+- When the migrate call returns check the states of VMs again.
+ VM_1 should be in paused (postmigrate) state. "info migrate" should report
+ "Migration status: completed".
+ VM_2 should be in running state.
+- Verify that FIO task completed successfully on VM_2 after migrating.
+ There should be no I/O failures, no verification failures, etc.
+- Cleanup:
+ - Shutdown both VMs.
+ - Gracefully shutdown Vhost instances and NVMEoF Target instance.
+ - Remove /tmp/share directory and it's contents.
+ - Clean RDMA NIC / Soft RoCE configuration.
+
+#### Test case 3 - remote server migration
+- Detect RDMA NICs on physical hosts. At least 1 RDMA NIC per host is needed
+ to run the test.
+- On Host 1 create /tmp/share directory and put a test VM image in there.
+- On Host 2 create /tmp/share directory. Using SSHFS mount /tmp/share from Host 1
+ so that the same VM image can be used on both hosts.
+- Start SPDK NVMeOF Target application on Host 1.
+ - Construct a single NVMe bdev from available bound NVMe drives.
+ - Create NVMeoF subsystem with NVMe bdev as single namespace.
+- Start first SDPK Vhost application instance on Host 1(later referred to as "Vhost_1").
+ - Use different shared memory ID and CPU mask than NVMeOF Target.
+ - Construct a NVMe bdev by connecting to NVMeOF Target
+ (using trtype: rdma).
+ - Construct a single SCSI controller and add NVMe bdev to it.
+- Start first VM (VM_1) and connect to Vhost_1 controller. Verify if attached disk
+ is visible in the system.
+- Start second SDPK Vhost application instance on Host 2(later referred to as "Vhost_2").
+ - Construct a NVMe bdev by connecting to NVMeOF Target. Connect to the same
+ subsystem as Vhost_1, multiconnection is allowed.
+ - Construct a single SCSI controller and add NVMe bdev to it.
+- Start second VM (VM_2) but with "-incoming" option enabled.
+- Check states of both VMs using Qemu monitor utility.
+ VM_1 should be in running state.
+ VM_2 should be in paused (inmigrate) state.
+- Run FIO I/O traffic with verification enabled on to attached NVME on VM_1.
+- While FIO is running issue a command for VM_1 to migrate.
+- When the migrate call returns check the states of VMs again.
+ VM_1 should be in paused (postmigrate) state. "info migrate" should report
+ "Migration status: completed".
+ VM_2 should be in running state.
+- Verify that FIO task completed successfully on VM_2 after migrating.
+ There should be no I/O failures, no verification failures, etc.
+- Cleanup:
+ - Shutdown both VMs.
+ - Gracefully shutdown Vhost instances and NVMEoF Target instance.
+ - Remove /tmp/share directory and it's contents.
+ - Clean RDMA NIC configuration.
+
+### Performance tests
+Tests verifying the performance and efficiency of the module.
+
+#### FIO Performance 6 NVMes
+- SPDK and created controllers run on 2 CPU cores.
+- Each NVMe drive is split into 2 Split NVMe bdevs, which gives a total of 12
+ in test setup.
+- 12 vhost controllers are created, one for each Split NVMe bdev. All controllers
+ use the same CPU mask as used for running Vhost instance.
+- 12 virtual machines are run as guest systems (with Ubuntu 16.04.2); Each VM
+ connects to a single corresponding vhost controller.
+ Per VM configuration is: 2 pass-through host CPU's, 1 GB RAM, 2 IO controller queues.
+- NVMe drives are pre-conditioned before the test starts. Pre-conditioning is done by
+ writing over whole disk sequentially at least 2 times.
+- FIO configurations used for tests:
+ - IO depths: 1, 8, 128
+ - Blocksize: 4k
+ - RW modes: read, randread, write, randwrite, rw, randrw
+ - Write modes are additionally run with 15 minute ramp-up time to allow better
+ measurements. Randwrite mode uses longer ramp-up preconditioning of 90 minutes per run.
+- Each FIO job result is compared with baseline results to allow detecting performance drops.
+
+## Future tests and improvements
+
+### Stress tests
+- Add stability and stress tests (long duration tests, long looped start/stop tests, etc.)
+to test pool
diff --git a/src/spdk/test/vhost/vhost_boot/vhost_boot.sh b/src/spdk/test/vhost/vhost_boot/vhost_boot.sh
new file mode 100755
index 00000000..42bd7f22
--- /dev/null
+++ b/src/spdk/test/vhost/vhost_boot/vhost_boot.sh
@@ -0,0 +1,111 @@
+#!/usr/bin/env bash
+set -xe
+
+basedir=$(readlink -f $(dirname $0))
+. $basedir/../common/common.sh
+rpc_py="python $SPDK_BUILD_DIR/scripts/rpc.py -s $(get_vhost_dir)/rpc.sock"
+vm_no="0"
+
+function err_clean
+{
+ trap - ERR
+ print_backtrace
+ set +e
+ error "Error on $1 $2"
+ vm_kill_all
+ $rpc_py remove_vhost_scsi_target naa.vhost_vm.$vm_no 0
+ $rpc_py remove_vhost_controller naa.vhost_vm.$vm_no
+ $rpc_py destroy_lvol_bdev $lvb_u
+ $rpc_py destroy_lvol_store -u $lvs_u
+ spdk_vhost_kill
+ exit 1
+}
+
+function usage()
+{
+ [[ ! -z $2 ]] && ( echo "$2"; echo ""; )
+ echo "Usage: $(basename $1) vm_image=PATH [-h|--help]"
+ echo "-h, --help Print help and exit"
+ echo " --vm_image=PATH Path to VM image used in these tests"
+}
+
+while getopts 'h-:' optchar; do
+ case "$optchar" in
+ -)
+ case "$OPTARG" in
+ vm_image=*) os_image="${OPTARG#*=}" ;;
+ *) usage $0 echo "Invalid argument '$OPTARG'" && exit 1 ;;
+ esac
+ ;;
+ h) usage $0 && exit 0 ;;
+ *) usage $0 "Invalid argument '$optchar'" && exit 1 ;;
+ esac
+done
+
+if [[ $EUID -ne 0 ]]; then
+ echo "INFO: Go away user come back as root"
+ exit 1
+fi
+
+if [[ -z $os_image ]]; then
+ echo "No path to os image is given"
+ exit 1
+fi
+
+timing_enter vhost_boot
+trap 'err_clean "${FUNCNAME}" "${LINENO}"' ERR
+timing_enter start_vhost
+spdk_vhost_run
+timing_exit start_vhost
+
+timing_enter create_lvol
+lvs_u=$($rpc_py construct_lvol_store Nvme0n1 lvs0)
+lvb_u=$($rpc_py construct_lvol_bdev -u $lvs_u lvb0 20000)
+timing_exit create_lvol
+
+timing_enter convert_vm_image
+modprobe nbd
+trap '$rpc_py stop_nbd_disk /dev/nbd0; rmmod nbd; err_clean "${FUNCNAME}" "${LINENO}"' ERR
+$rpc_py start_nbd_disk $lvb_u /dev/nbd0
+$QEMU_PREFIX/bin/qemu-img convert $os_image -O raw /dev/nbd0
+sync
+$rpc_py stop_nbd_disk /dev/nbd0
+sleep 1
+rmmod nbd
+timing_exit convert_vm_image
+
+trap 'err_clean "${FUNCNAME}" "${LINENO}"' ERR
+timing_enter create_vhost_controller
+$rpc_py construct_vhost_scsi_controller naa.vhost_vm.$vm_no
+$rpc_py add_vhost_scsi_lun naa.vhost_vm.$vm_no 0 $lvb_u
+timing_exit create_vhost_controller
+
+timing_enter setup_vm
+vm_setup --disk-type=spdk_vhost_scsi --force=$vm_no --disks="vhost_vm" --spdk-boot="vhost_vm"
+vm_run $vm_no
+vm_wait_for_boot 600 $vm_no
+timing_exit setup_vm
+
+timing_enter run_vm_cmd
+vm_ssh $vm_no "parted -s /dev/sda mkpart primary 10GB 100%; partprobe; sleep 0.1;"
+vm_ssh $vm_no "mkfs.ext4 -F /dev/sda2; mkdir -p /mnt/sda2test; mount /dev/sda2 /mnt/sda2test;"
+vm_ssh $vm_no "fio --name=integrity --bsrange=4k-512k --iodepth=128 --numjobs=1 --direct=1 \
+ --thread=1 --group_reporting=1 --rw=randrw --rwmixread=70 --filename=/mnt/sda2test/test_file \
+ --verify=md5 --do_verify=1 --verify_backlog=1024 --fsync_on_close=1 --runtime=20 \
+ --time_based=1 --size=1024m"
+vm_ssh $vm_no "umount /mnt/sda2test; rm -rf /mnt/sda2test"
+alignment_offset=$(vm_ssh $vm_no "cat /sys/block/sda/sda1/alignment_offset")
+echo "alignment_offset: $alignment_offset"
+timing_exit run_vm_cmd
+
+vm_shutdown_all
+
+timing_enter clean_vhost
+$rpc_py remove_vhost_scsi_target naa.vhost_vm.$vm_no 0
+$rpc_py remove_vhost_controller naa.vhost_vm.$vm_no
+$rpc_py destroy_lvol_bdev $lvb_u
+$rpc_py destroy_lvol_store -u $lvs_u
+spdk_vhost_kill
+timing_exit clean_vhost
+
+timing_exit vhost_boot