作者简介
王海亮 英特尔SPDK测试工程师
一
为什么SPDK要使用JSON格式配置文件?
众所周知,配置文件是软件应用中常用的初始化参数的配置方式,而配置文件的格式有很多种。不同的配置文件格式有不同的用户友好性,比如Windows较早的开发者喜欢使用INI,java工程师喜欢使用properties,通用的编程喜欢yaml、JSON等格式。
1.1
SPDK里INI配置文件回顾
SPDK测试代码里的配置文件主要使用了INI格式和JSON格式。
INI文件是一个无固定标准格式的配置文件。文件格式语法比较简单, 分为:parameter,p和comments。SPDK里一般命名为.conf或.conf.in。动态创建,动态删除。
例如,创建一个Malloc块设备的bdev.conf:
-
[Malloc]
-
NumberOfLuns
6
-
LunSizeInMB
32
然后在shell脚本里,以-c参数加载上面的配置文件。例如运行bdevperf:
./spdk/test/bdev/bdevperf -c bdev.conf -q 256 -w read -o 196608 -t 5
SPDK测试和开发工程师们应该都很熟悉这种方式了。随着应用场合的变迁和扩大,INI格式也有其弊端,它只支持键值模式,而且只有一层分类,无嵌套。在处理例如对象(object)、记录(record)、结构体(struct)、字典(dictionary)、哈希表(hash table)、键值列表(keyed list),还有数组(array)、向量(vector)、列表(list),以及对象组成的数组等等时,显得非常不足,但JSON可以。这些特性使其逐步成为理想的数据交换语言。当今,JSON已被所有大型企业所采用,十大最受欢迎的 Web API 接口列表中多数是以 JSON格式开放数据的。JSON也在程序编码级别和文件存储上被广泛采用:在 Stack Overflow网站上,现在更多的是关于 JSON 的问题,而不是其他的数据交换格式。
现在SPDK逐步改为使用JSON-RPC格式的配置文件。从SPDK v20.07版本开始,我们进行了INI格式到JSON的转换工作。涉及到INI conf解析的代码,运行时都会出现下面的提示:
conf.c: 613:spdk_conf_read: *ERROR*: INI configuration has been deprecated and will be removed in a future release. Please switch to JSON-RPC.
到SPDK v20.10,已经基本转换完成。在此过程中涉及到部分测试代码和脚本改动,原来的以INI格式配置文件为基础的测试方法,已经不能成功运行测试用例。为了方便SPDK用户迁移到JSON格式,我们适时地推出了这篇文章,希望提供一份有实用价值的参考。
1.2
JSON文件格式简介
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 |
JSON的优点
数据格式比较简单,易于读写,格式都是压缩的,占用带宽小。
易于解析这种语言,客户端JavaScript可以简单的通过eval()进行JSON数据的读取(浏览器解析)。
因为JSON格式能够直接为服务器端代码使用, 大大简化了服务器端和客户端的代码开发量, 但是完成的任务不变,且易于维护。能够被大多数后端语言支持。
JSON用于描述数据结构,有以下形式存在
对象(object):一个对象以{开始,并以}结束。一个对象包含一系列未排序的名称/值对
名称/值(collection):名称和值之间使用:隔开,一般的形式是:{name:value}
值的有序列表(array):一个或者多个值用,分区后,使用[,]括起来就形成了这样的列表
字符串:以""括起来的一串字符。
数值:一系列0-9的数字组合,可以为负数或者小数。还可以用e或者E表示为指数形式。
布尔值:表示为true或者false。
JSON格式更详细的描述可以参考RFC 4627。
二.SPDK里INI到JSON转换举例
SPDK里INI到JSON转换举例
在SPDK里,从INI带JSON的转换工作,主要涉及两部分:shell和C代码。
下面列举了常用的转换案例对照。
2.1
创建Malloc块设备
例如:spdk/examples/blob/hello_world里,原来的配置文件是hello_blob.conf:
-
[Malloc]
-
NumberOfLuns
1
-
LunSizeInMB
16
-
BlockSize
512
现在改为hello_blob.json:
-
{
-
"subsystems": [
-
{
-
"subsystem":
"bdev",
-
"config": [
-
{
-
"method":
"bdev_malloc_create",
-
"params": {
-
"name":
"Malloc0",
-
"num_blocks":
32768,
-
"block_size":
512
-
}
-
}
-
]
-
}
-
]
-
}
请注意,这里使用了RPC方法“bdev_malloc_create”创建malloc块设备。
SPDK使用的其他RPC方法请参考命令./scripts/rpc.py -h
调用方法变化:
examples/blob/hello_world/hello_blob hello_blob.conf
变为
examples/blob/hello_world/hello_blob hello_blob.json
如果是NumberOfLuns 3,
-
[Malloc]
-
NumberOfLuns
3
-
LunSizeInMB
300
-
BlockSize
512
要使用循环创建,于是变为:
-
local size=
300
# MB
-
local block_size=
512
-
local config
-
local malloc malloc_devs=
3
-
for (( malloc =
0; malloc < malloc_devs; malloc++ ));
do
-
config+=(
-
"$(
-
cat <<-JSON
-
{
-
"method
": "bdev_malloc_create
",
-
"params
": {
-
"name
": "Malloc$malloc
",
-
"num_blocks
": $(( (size << 20) / block_size )),
-
"block_size
": 512
-
}
-
}
-
JSON
-
)"
-
)
-
done
2.2
创建PCIe NVMe块设备
原来INI格式:
-
[Nvme]
-
TransportID "
trtype
:PCIe
traddr
:0000
:5f
:00.0"
Nvme0
现在改为:
-
{
-
"method":
"bdev_nvme_attach_controller",
-
"params": {
-
"trtype":
"PCIe",
-
"name":
"Nvme0",
-
"traddr":
"0000:5f:00.0"
-
}
-
}
调用方式
INI格式:
spdk/test/bdev/bdevperf/bdevperf -m 0x10 -i 1 -c $testdir/bdevperf.conf -q 128 -o 4096 -w write -t 1
对应修改为JSON格式:
spdk/test/bdev/bdevperf/bdevperf -m 0x10 -i 1 --json $testdir/bdevperf.json -q 128 -o 4096 -w write -t 1
2.3
加载NVMe-oF控制器
INI格式:
-
[Nvme]
-
TransportID \"trtype:$TEST_TRANSPORT adrfam:IPv4 subnqn:nqn.2016-06.io.spdk:cnode1 traddr:$NVMF_FIRST_TARGET_IP trsvcid:$NVMF_PORT\" Nvme0
对应JSON格式修改为:
-
function gen_nvmf_target_json()
-
{
-
local subsystem config=()
-
-
-
for subsystem in
"${@:-1}";
do
-
config+=(
-
"$(
-
cat <<-EOF
-
{
-
"params
": {
-
"name
": "Nvme$subsystem
",
-
"trtype
": "$TEST_TRANSPORT
",
-
"traddr
": "$NVMF_FIRST_TARGET_IP
",
-
"adrfam
": "ipv4
",
-
"trsvcid
": "$NVMF_PORT
",
-
"subnqn
": "nqn.
2016-
06.io.spdk:cnode$subsystem
"
-
},
-
"method
": "bdev_nvme_attach_controller
"
-
}
-
EOF
-
)"
-
)
-
done
-
jq . <<-JSON
-
{
-
"subsystems": [
-
{
-
"subsystem":
"bdev",
-
"config": [
-
$(IFS=
",";
printf
'%s\n'
"${config[*]}")
-
]
-
}
-
]
-
}
-
JSON
-
}
调用方式
INI格式:
spdk/test/bdev/bdevperf/bdevperf -m 0x10 -i 1 -c $testdir/bdevperf.conf -q 128 -o 4096 -w write -t 1
对应修改为JSON格式:
spdk/test/bdev/bdevperf/bdevperf -m 0x10 -i 1 --json <(gen_nvmf_target_json) -q 128 -o 4096 -w write -t 1
2.4
创建iSCSI启动器
INI格式:
-
[
iSCSI_Initiator]
-
URL iscsi:
//$TARGET_IP/iqn.2016-06.io.spdk:disk1/0 iSCSI0
对应修改为JSON格式:
-
function initiator_json_config() {
-
# Prepare config file
for iSCSI initiator
-
jq . <<-
JSON
-
{
-
"subsystems": [
-
{
-
"subsystem":
"bdev",
-
"config": [
-
{
-
"method":
"bdev_iscsi_create",
-
"params": {
-
"name":
"iSCSI0",
-
"url":
"iscsi://$TARGET_IP/iqn.2016-06.io.spdk:disk1/0",
-
"initiator_iqn":
"iqn.2016-06.io.spdk:disk1/0"
-
}
-
}${*:+,$*}
-
]
-
}
-
]
-
}
-
JSON
-
}
调用方式
INI格式:
spdk/test/bdev/bdevperf/bdevperf -c $testdir/bdev.conf -q 128 -o 4096 -w verify -t 5 -s 512
对应修改为JSON格式:
spdk/test/bdev/bdevperf/bdevperf" --json <(initiator_json_config) -q 128 -o 4096 -w verify -t 5 -s 512
2.5
创建AIO块设备
INI格式:
-
[AIO]
-
AIO
${tmp_file}
${bdevname} 512
对应修改为JSON格式:
-
jq . <
"$testdir/config"
-
{
-
"subsystems": [
-
{
-
"subsystem":
"bdev",
-
"config": [
-
{
-
"method":
"bdev_aio_create",
-
"params": {
-
"name":
"${bdevname}",
-
"block_size": 512,
-
"filename":
"${tmp_file}"
-
}
-
}
-
]
-
}
-
]
-
}
-
JSON
此外,也可以用RPC方法接收来INI格式参数,例如bdev_set_options,
-
[Bdev]
-
BdevIoPoolSize
128
-
BdevIoCacheSize
1
-
[AIO]
-
AIO
$testdir/aio.bdev aio0 4096
在shell里调用,并使用save_config RPC方法来保存为JSON文件:
1. # Minimal number of bdev io pool (128) and cache (1)
2. $rpc_py bdev_set_options --bdev-io-pool-size 128 --bdev-io-cache-size 1
3. $rpc_py framework_start_init
4. $rpc_py save_config > $testdir/bdevperf.json
2.6
创建OCF块设备
例如:INI里将Nvme0n1分成8个小块,Nvme0n1p0 - Nvme0n1p7,一个OCF块建立在两个Nvme小块之上。
-
[OCF]
-
OCF
PT_Nvme pt Nvme0n1p0 Nvme0n1p1
-
OCF
WT_Nvme wt Nvme0n1p2 Nvme0n1p3
-
OCF
WB_Nvme0 wb Nvme0n1p4 Nvme0n1p5
-
OCF
WB_Nvme1 wb Nvme0n1p6 Nvme0n1p7
JSON里用bdev_split_create RPC方法,也将nvme0n1分成8个小块,但需要增加"mode","cache_bdev_name"和"core_bdev_name"参数。
于是改为:
-
ocf_names[
1]=PT_Nvme ocf_modes[
1]=pt
-
ocf_names[
2]=WT_Nvme ocf_modes[
2]=wt
-
ocf_names[
3]=WB_Nvme
0 ocf_modes[
3]=wb
-
ocf_names[
4]=WB_Nvme1 ocf_modes[
4]=wb
-
-
-
config+=(
-
"$(
-
cat <<- JSON
-
{
-
"method
": "bdev_split_create
",
-
"params
": {
-
"base_bdev
": "Nvme0n1
",
-
"split_count
": 8,
-
"split_size_mb
": 101
-
}
-
}
-
JSON
-
)"
-
)
-
-
-
for ((d =
0, c =
1; d <= ${
#ocf_names[@]} + 2; d += 2, c++)); do
-
config+=(
-
"$(
-
cat <<- JSON
-
{
-
"method
": "bdev_ocf_create
",
-
"params
": {
-
"name
": "${ocf_names[c]}
",
-
"mode
": "${ocf_modes[c]}
",
-
"cache_bdev_name
": "Nvme0n1p$d
",
-
"core_bdev_name
": "Nvme0n1p$((d +
1))
"
-
}
-
}
-
JSON
-
)"
-
)
-
done
以上保存为modes.conf,调用方法不变:
LD_PRELOAD=examples/bdev/fio_plugin/fio_plugin test/ocf/integrity/test.fio--
ioengine=spdk_bdev --filename=PT_Nvme:WT_Nvme:WB_Nvme0:WB_Nvme1--spdk_json_conf="$curdir/modes.conf" --thread=1
更多请参考本公众号前期微信文章????缓存助力存储加速–OCF与SPDK介绍及用法
2.7
Fio_plugin的修改
和fio工具一样,运行fio_plugin也需要job文件。只需要把job文件里指定的spdk_conf 由bdev.conf.in改为bdev.json:
-
[global]
-
ioengine=spdk_bdev
-
spdk_conf=/home/qwan/scripts/fio_job/full_speed/spdk_bdev.conf
-
#这里改为
-
spdk_json_conf=/home/qwan/scripts/fio_job/full_speed/spdk_bdev.json
C代码里,增加相应的"spdk_json_conf"操作项:
-
static
struct fio_option options[] = {
-
{
-
.name =
"spdk_conf",
-
.lname =
"SPDK configuration file",
-
.type = FIO_OPT_STR_STORE,
-
.off1 = offsetof(
struct spdk_fio_options, conf),
-
.help =
"A SPDK configuration file",
-
.category = FIO_OPT_C_ENGINE,
-
.
group = FIO_OPT_G_INVALID,
-
},
-
{
-
.name =
"spdk_json_conf",
-
.lname =
"SPDK JSON configuration file",
-
.type = FIO_OPT_STR_STORE,
-
.off1 = offsetof(
struct spdk_fio_options, json_conf),
-
.help =
"A SPDK JSON configuration file",
-
.category = FIO_OPT_C_ENGINE,
-
.
group = FIO_OPT_G_INVALID,
-
},
调用方法不变:
$ LD_PRELOAD=spdk/examples/nvme/fio_plugin/fio_plugin /usr/src/fio/fio example_config.job
2.8
上述JSON的三种使用方法小结
(1)C代码里app参数-c改为-j,并且opts. config_file改为opts.json_config_file。
例如:
opts.config_file = cli_context->config_file;
改为
opts.json_config_file = cli_context->config_file;
2.1节使用了这种方法。
(2)使用EAL参数--json调用。
例如:
spdk/test/bdev/bdevperf/bdevperf -m 0x10 -i 1 --json <(gen_nvmf_target_json) -q 128 -o 4096 -w write -t 1
还可以直接加文件名,app --json $testdir/bdevperf.json
2.2、2.3、2.4、2.6、2.7节使用了这种方法。
(3)使用RPC方法save_config保存JSON文件
例如:
$rpc_py save_config > $testdir/bdevperf.json
2.5节使用了这种方法。
因为JSON本质上是RPC的调用,所以我们推荐这种方法。此外,如果熟悉了RPC操作,还可以按RPC的命令来填充和检查JSON文件。
转载须知
推荐阅读
分享、在看与点赞,至少点亮一个吧
转载:https://blog.csdn.net/weixin_37097605/article/details/112301001