diff --git a/CHANGELOG.md b/CHANGELOG.md index 67aa69663..23916c337 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -176,6 +176,15 @@ For the active-active policy of the multipath mode, in addition to the default r selector, the minimum queue depth path selector was added. The minimum queue depth path selector selects an I/O path according to the number of outstanding requests of each nvme qpair. +### ublk device + +The ublk application supports the ublk kernel driver. It's implemented as a ublk backend +in spdk_tgt and could be started with specifying configuration. See the +[ublk](https://www.kernel.org/doc/html/latest/block/ublk.html) documentation for more details. + +ublk bdev could export a block device via Linux ublk. It will move this backend device into userspace +as `/dev/ublkb*`. Before to adding ublk device, need to create ublk target by rpc methond. + ## v22.09 ### accel diff --git a/doc/jsonrpc.md b/doc/jsonrpc.md index 4362bf035..c48822dab 100644 --- a/doc/jsonrpc.md +++ b/doc/jsonrpc.md @@ -10831,6 +10831,50 @@ Example response: } ~~~ +### ublk_get_disks {#rpc_ublk_get_disks} + +Display full or specified ublk device list + +#### Parameters + +Name | Optional | Type | Description +----------------------- | -------- | ----------- | ----------- +ublk_id | Optional | int | ublk device id to display + +#### Response + +Display ublk device list + +#### Example + +Example request: + +~~~json +{ + "jsonrpc": "2.0", + "method": "ublk_get_disks", + "id": 1 +} +~~~ + +Example response: + +~~~json +{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "ublk_device": "/dev/ublkb1", + "id": 1, + "queue_depth": 512, + "num_queues": 1, + "bdev_name": "Malloc1" + } + ] +} +~~~ + ## Linux Network Block Device (NBD) {#jsonrpc_components_nbd} SPDK supports exporting bdevs through Linux nbd. These devices then appear as standard Linux kernel block devices diff --git a/lib/ublk/ublk.c b/lib/ublk/ublk.c index 34824956b..f15752872 100644 --- a/lib/ublk/ublk.c +++ b/lib/ublk/ublk.c @@ -561,6 +561,34 @@ ublk_dev_find_by_id(uint32_t ublk_id) return NULL; } +uint32_t +ublk_dev_get_id(struct spdk_ublk_dev *ublk) +{ + return ublk->ublk_id; +} + +struct spdk_ublk_dev *ublk_dev_first(void) +{ + return TAILQ_FIRST(&g_ublk_bdevs); +} + +struct spdk_ublk_dev *ublk_dev_next(struct spdk_ublk_dev *prev) +{ + return TAILQ_NEXT(prev, tailq); +} + +uint32_t +ublk_dev_get_queue_depth(struct spdk_ublk_dev *ublk) +{ + return ublk->queue_depth; +} + +uint32_t +ublk_dev_get_num_queues(struct spdk_ublk_dev *ublk) +{ + return ublk->num_queues; +} + const char * ublk_dev_get_bdev_name(struct spdk_ublk_dev *ublk) { diff --git a/lib/ublk/ublk_internal.h b/lib/ublk/ublk_internal.h index e430cf86b..8a59bb82f 100644 --- a/lib/ublk/ublk_internal.h +++ b/lib/ublk/ublk_internal.h @@ -25,7 +25,12 @@ int ublk_start_disk(const char *bdev_name, uint32_t ublk_id, uint32_t num_queues, uint32_t queue_depth); int ublk_stop_disk(uint32_t ublk_id, ublk_del_cb del_cb, void *cb_arg); struct spdk_ublk_dev *ublk_dev_find_by_id(uint32_t ublk_id); +uint32_t ublk_dev_get_id(struct spdk_ublk_dev *ublk); const char *ublk_dev_get_bdev_name(struct spdk_ublk_dev *ublk); +struct spdk_ublk_dev *ublk_dev_first(void); +struct spdk_ublk_dev *ublk_dev_next(struct spdk_ublk_dev *prev); +uint32_t ublk_dev_get_queue_depth(struct spdk_ublk_dev *ublk); +uint32_t ublk_dev_get_num_queues(struct spdk_ublk_dev *ublk); #ifdef __cplusplus } diff --git a/lib/ublk/ublk_rpc.c b/lib/ublk/ublk_rpc.c index f89e2727b..c6a4a3a19 100644 --- a/lib/ublk/ublk_rpc.c +++ b/lib/ublk/ublk_rpc.c @@ -187,3 +187,75 @@ invalid: } SPDK_RPC_REGISTER("ublk_stop_disk", rpc_ublk_stop_disk, SPDK_RPC_RUNTIME) + +static void +rpc_dump_ublk_info(struct spdk_json_write_ctx *w, + struct spdk_ublk_dev *ublk) +{ + char ublk_path[32]; + + snprintf(ublk_path, 32, "%s%u", "/dev/ublkb", ublk_dev_get_id(ublk)); + spdk_json_write_object_begin(w); + + spdk_json_write_named_string(w, "ublk_device", ublk_path); + spdk_json_write_named_uint32(w, "id", ublk_dev_get_id(ublk)); + spdk_json_write_named_uint32(w, "queue_depth", ublk_dev_get_queue_depth(ublk)); + spdk_json_write_named_uint32(w, "num_queues", ublk_dev_get_num_queues(ublk)); + spdk_json_write_named_string(w, "bdev_name", ublk_dev_get_bdev_name(ublk)); + + spdk_json_write_object_end(w); +} + +struct rpc_ublk_get_disks { + uint32_t ublk_id; +}; + +static const struct spdk_json_object_decoder rpc_ublk_get_disks_decoders[] = { + {"ublk_id", offsetof(struct rpc_ublk_get_disks, ublk_id), spdk_json_decode_uint32, true}, +}; + +static void +rpc_ublk_get_disks(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_ublk_get_disks req = {}; + struct spdk_json_write_ctx *w; + struct spdk_ublk_dev *ublk = NULL; + + if (params != NULL) { + if (spdk_json_decode_object(params, rpc_ublk_get_disks_decoders, + SPDK_COUNTOF(rpc_ublk_get_disks_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "spdk_json_decode_object failed"); + return; + } + + if (req.ublk_id) { + ublk = ublk_dev_find_by_id(req.ublk_id); + if (ublk == NULL) { + SPDK_ERRLOG("ublk device '%d' does not exist\n", req.ublk_id); + spdk_jsonrpc_send_error_response(request, -ENODEV, spdk_strerror(ENODEV)); + return; + } + } + } + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_array_begin(w); + + if (ublk != NULL) { + rpc_dump_ublk_info(w, ublk); + } else { + for (ublk = ublk_dev_first(); ublk != NULL; ublk = ublk_dev_next(ublk)) { + rpc_dump_ublk_info(w, ublk); + } + } + + spdk_json_write_array_end(w); + spdk_jsonrpc_end_result(request, w); + + return; +} +SPDK_RPC_REGISTER("ublk_get_disks", rpc_ublk_get_disks, SPDK_RPC_RUNTIME) diff --git a/python/spdk/rpc/ublk.py b/python/spdk/rpc/ublk.py index 640e0161b..62fdf427c 100644 --- a/python/spdk/rpc/ublk.py +++ b/python/spdk/rpc/ublk.py @@ -28,3 +28,10 @@ def ublk_start_disk(client, bdev_name, ublk_id=1, num_queues=1, queue_depth=128) def ublk_stop_disk(client, ublk_id=1): params = {'ublk_id': ublk_id} return client.call('ublk_stop_disk', params) + + +def ublk_get_disks(client, ublk_id=1): + params = {} + if ublk_id: + params['ublk_id'] = ublk_id + return client.call('ublk_get_disks', params) diff --git a/scripts/rpc.py b/scripts/rpc.py index 2357e247d..db3ff1b12 100755 --- a/scripts/rpc.py +++ b/scripts/rpc.py @@ -2260,6 +2260,15 @@ Format: 'user:u1 secret:s1 muser:mu1 msecret:ms1,user:u2 secret:s2 muser:mu2 mse p.add_argument('ublk_id', help='ublk device id to be deleted. Example: 1.', type=int) p.set_defaults(func=ublk_stop_disk) + def ublk_get_disks(args): + print_dict(rpc.ublk.ublk_get_disks(args.client, + ublk_id=args.ublk_id)) + + p = subparsers.add_parser('ublk_get_disks', + help='Display full or specified ublk device list') + p.add_argument('-n', '--ublk-id', help="ublk device id. Example: 1", type=int, required=False) + p.set_defaults(func=ublk_get_disks) + # nbd def nbd_start_disk(args): print(rpc.nbd.nbd_start_disk(args.client,