From f3c14b8dee46ae7d5352aa25c01501e251640097 Mon Sep 17 00:00:00 2001 From: Mike Gerdts Date: Mon, 10 Apr 2023 08:12:49 -0500 Subject: [PATCH] vbdev_lvol: add bdev_lvol_get_lvols RPC This provides information about logical volumes without providing information about the bdevs. It is useful for listing the lvols associated with specific lvol stores and for listing lvols that are degraded and have no associated bdev. Signed-off-by: Mike Gerdts Change-Id: I795161ac88d9707831d9fcd2079635c7e46ecc42 Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/17547 Reviewed-by: Jim Harris Reviewed-by: Ben Walker Tested-by: SPDK CI Jenkins --- CHANGELOG.md | 5 ++ doc/jsonrpc.md | 52 +++++++++++++++ doc/lvol.md | 6 ++ module/bdev/lvol/vbdev_lvol_rpc.c | 107 ++++++++++++++++++++++++++++++ python/spdk/rpc/lvol.py | 21 ++++++ scripts/rpc.py | 10 +++ test/lvol/basic.sh | 26 ++++++++ 7 files changed, 227 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5e3ee5bd..c05404896 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,11 @@ New API `spdk_lvol_iter_immediate_clones` was added to iterate the clones of an New APIs `spdk_lvol_get_by_uuid` and `spdk_lvol_get_by_names` to get lvols by the lvol's UUID or lvstore and lvol names. +New `bdev_lvol_get_lvols` RPC to list logical volumes. This provides information about logical +volumes without providing information about the bdevs. It is useful for listing the lvols +associated with specific lvol stores and for listing lvols that are degraded and have no +associated bdev. + ### nvmf New `spdk_nvmf_request_copy_to/from_buf()` APIs have been added, which support diff --git a/doc/jsonrpc.md b/doc/jsonrpc.md index a7f299305..85f83546b 100644 --- a/doc/jsonrpc.md +++ b/doc/jsonrpc.md @@ -9903,6 +9903,58 @@ Example response: } ~~~ +### bdev_lvol_get_lvols {#rpc_bdev_lvol_get_lvols} + +Get a list of logical volumes. This list can be limited by lvol store and will display volumes even if +they are degraded. Degraded lvols do not have an associated bdev, thus this RPC call may return lvols +not returned by `bdev_get_bdevs`. + +#### Parameters + +Name | Optional | Type | Description +----------------------- | -------- | ----------- | ----------- +lvs_uuid | Optional | string | Only show volumes in the logical volume store with this UUID +lvs_name | Optional | string | Only show volumes in the logical volume store with this name + +Either lvs_uuid or lvs_name may be specified, but not both. +If both lvs_uuid and lvs_name are omitted, information about lvols in all logical volume stores is returned. + +#### Example + +Example request: + +~~~json +{ + "jsonrpc": "2.0", + "method": "bdev_lvol_get_lvols", + "id": 1, + "params": { + "lvs_name": "lvs_test" + } +} +~~~ + +Example response: + +~~~json +[ + { + "alias": "lvs_test/lvol1", + "uuid": "b335c368-851d-4099-81e0-018cc494fdf6", + "name": "lvol1", + "is_thin_provisioned": false, + "is_snapshot": false, + "is_clone": false, + "is_esnap_clone": false, + "is_degraded": false, + "lvs": { + "name": "lvs_test", + "uuid": "a1c8d950-5715-4558-936d-ab9e6eca0794" + } + } +] +~~~ + ## RAID ### bdev_raid_get_bdevs {#rpc_bdev_raid_get_bdevs} diff --git a/doc/lvol.md b/doc/lvol.md index e5ef239c2..fed70cb0f 100644 --- a/doc/lvol.md +++ b/doc/lvol.md @@ -150,6 +150,12 @@ bdev_lvol_create [-h] [-u UUID] [-l LVS_NAME] [-t] [-c CLEAR_METHOD] lvol_name s optional arguments: -h, --help show help -c, --clear-method specify data clusters clear method "none", "unmap" (default), "write_zeroes" +bdev_lvol_get_lvols [-h] [-u LVS_UUID] [-l LVS_NAME] + Display logical volume list, including those that do not have associated bdevs. + optional arguments: + -h, --help show help + -u LVS_UUID, --lvs_uuid UUID show volumes only in the specified lvol store + -l LVS_NAME, --lvs_name LVS_NAME show volumes only in the specified lvol store bdev_get_bdevs [-h] [-b NAME] User can view created bdevs using this call including those created on top of lvols. optional arguments: diff --git a/module/bdev/lvol/vbdev_lvol_rpc.c b/module/bdev/lvol/vbdev_lvol_rpc.c index 67b7e4a94..ec64ef85e 100644 --- a/module/bdev/lvol/vbdev_lvol_rpc.c +++ b/module/bdev/lvol/vbdev_lvol_rpc.c @@ -1149,6 +1149,113 @@ cleanup: SPDK_RPC_REGISTER("bdev_lvol_get_lvstores", rpc_bdev_lvol_get_lvstores, SPDK_RPC_RUNTIME) SPDK_RPC_REGISTER_ALIAS_DEPRECATED(bdev_lvol_get_lvstores, get_lvol_stores) +struct rpc_bdev_lvol_get_lvols { + char *lvs_uuid; + char *lvs_name; +}; + +static void +free_rpc_bdev_lvol_get_lvols(struct rpc_bdev_lvol_get_lvols *req) +{ + free(req->lvs_uuid); + free(req->lvs_name); +} + +static const struct spdk_json_object_decoder rpc_bdev_lvol_get_lvols_decoders[] = { + {"lvs_uuid", offsetof(struct rpc_bdev_lvol_get_lvols, lvs_uuid), spdk_json_decode_string, true}, + {"lvs_name", offsetof(struct rpc_bdev_lvol_get_lvols, lvs_name), spdk_json_decode_string, true}, +}; + +static void +rpc_dump_lvol(struct spdk_json_write_ctx *w, struct spdk_lvol *lvol) +{ + struct spdk_lvol_store *lvs = lvol->lvol_store; + char uuid[SPDK_UUID_STRING_LEN]; + + spdk_json_write_object_begin(w); + + spdk_json_write_named_string_fmt(w, "alias", "%s/%s", lvs->name, lvol->name); + spdk_json_write_named_string(w, "uuid", lvol->uuid_str); + spdk_json_write_named_string(w, "name", lvol->name); + spdk_json_write_named_bool(w, "is_thin_provisioned", spdk_blob_is_thin_provisioned(lvol->blob)); + spdk_json_write_named_bool(w, "is_snapshot", spdk_blob_is_snapshot(lvol->blob)); + spdk_json_write_named_bool(w, "is_clone", spdk_blob_is_clone(lvol->blob)); + spdk_json_write_named_bool(w, "is_esnap_clone", spdk_blob_is_esnap_clone(lvol->blob)); + spdk_json_write_named_bool(w, "is_degraded", spdk_blob_is_degraded(lvol->blob)); + + spdk_json_write_named_object_begin(w, "lvs"); + spdk_json_write_named_string(w, "name", lvs->name); + spdk_uuid_fmt_lower(uuid, sizeof(uuid), &lvs->uuid); + spdk_json_write_named_string(w, "uuid", uuid); + spdk_json_write_object_end(w); + + spdk_json_write_object_end(w); +} + +static void +rpc_dump_lvols(struct spdk_json_write_ctx *w, struct lvol_store_bdev *lvs_bdev) +{ + struct spdk_lvol_store *lvs = lvs_bdev->lvs; + struct spdk_lvol *lvol; + + TAILQ_FOREACH(lvol, &lvs->lvols, link) { + rpc_dump_lvol(w, lvol); + } +} + +static void +rpc_bdev_lvol_get_lvols(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params) +{ + struct rpc_bdev_lvol_get_lvols req = {}; + struct spdk_json_write_ctx *w; + struct lvol_store_bdev *lvs_bdev = NULL; + struct spdk_lvol_store *lvs = NULL; + int rc; + + if (params != NULL) { + if (spdk_json_decode_object(params, rpc_bdev_lvol_get_lvols_decoders, + SPDK_COUNTOF(rpc_bdev_lvol_get_lvols_decoders), + &req)) { + SPDK_INFOLOG(lvol_rpc, "spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "spdk_json_decode_object failed"); + goto cleanup; + } + + rc = vbdev_get_lvol_store_by_uuid_xor_name(req.lvs_uuid, req.lvs_name, &lvs); + if (rc != 0) { + spdk_jsonrpc_send_error_response(request, rc, spdk_strerror(-rc)); + goto cleanup; + } + + lvs_bdev = vbdev_get_lvs_bdev_by_lvs(lvs); + if (lvs_bdev == NULL) { + spdk_jsonrpc_send_error_response(request, ENODEV, spdk_strerror(-ENODEV)); + goto cleanup; + } + } + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_array_begin(w); + + if (lvs_bdev != NULL) { + rpc_dump_lvols(w, lvs_bdev); + } else { + for (lvs_bdev = vbdev_lvol_store_first(); lvs_bdev != NULL; + lvs_bdev = vbdev_lvol_store_next(lvs_bdev)) { + rpc_dump_lvols(w, lvs_bdev); + } + } + spdk_json_write_array_end(w); + + spdk_jsonrpc_end_result(request, w); + +cleanup: + free_rpc_bdev_lvol_get_lvols(&req); +} + +SPDK_RPC_REGISTER("bdev_lvol_get_lvols", rpc_bdev_lvol_get_lvols, SPDK_RPC_RUNTIME) + struct rpc_bdev_lvol_grow_lvstore { char *uuid; char *lvs_name; diff --git a/python/spdk/rpc/lvol.py b/python/spdk/rpc/lvol.py index 74ceb833c..d2d4fbcd3 100644 --- a/python/spdk/rpc/lvol.py +++ b/python/spdk/rpc/lvol.py @@ -261,3 +261,24 @@ def bdev_lvol_get_lvstores(client, uuid=None, lvs_name=None): if lvs_name: params['lvs_name'] = lvs_name return client.call('bdev_lvol_get_lvstores', params) + + +def bdev_lvol_get_lvols(client, lvs_uuid=None, lvs_name=None): + """List logical volumes + + Args: + lvs_uuid: Only show volumes in the logical volume store with this UUID (optional) + lvs_name: Only show volumes in the logical volume store with this name (optional) + + Either lvs_uuid or lvs_name may be specified, but not both. + If both lvs_uuid and lvs_name are omitted, information about volumes in all + logical volume stores is returned. + """ + if (lvs_uuid and lvs_name): + raise ValueError("Exactly one of uuid or lvs_name may be specified") + params = {} + if lvs_uuid: + params['lvs_uuid'] = lvs_uuid + if lvs_name: + params['lvs_name'] = lvs_name + return client.call('bdev_lvol_get_lvols', params) diff --git a/scripts/rpc.py b/scripts/rpc.py index a9331e4e3..09afd3999 100755 --- a/scripts/rpc.py +++ b/scripts/rpc.py @@ -2052,6 +2052,16 @@ Format: 'user:u1 secret:s1 muser:mu1 msecret:ms1,user:u2 secret:s2 muser:mu2 mse p.add_argument('-l', '--lvs-name', help='lvol store name', required=False) p.set_defaults(func=bdev_lvol_get_lvstores) + def bdev_lvol_get_lvols(args): + print_dict(rpc.lvol.bdev_lvol_get_lvols(args.client, + lvs_uuid=args.lvs_uuid, + lvs_name=args.lvs_name)) + + p = subparsers.add_parser('bdev_lvol_get_lvols', help='Display current logical volume list') + p.add_argument('-u', '--lvs-uuid', help='only lvols in lvol store UUID', required=False) + p.add_argument('-l', '--lvs-name', help='only lvols in lvol store name', required=False) + p.set_defaults(func=bdev_lvol_get_lvols) + def bdev_raid_get_bdevs(args): print_json(rpc.bdev.bdev_raid_get_bdevs(args.client, category=args.category)) diff --git a/test/lvol/basic.sh b/test/lvol/basic.sh index 8f1bf1ad0..3516af7f1 100755 --- a/test/lvol/basic.sh +++ b/test/lvol/basic.sh @@ -533,6 +533,31 @@ function test_construct_nested_lvol() { check_leftover_devices } +# List lvols without going through the bdev layer. +function test_lvol_list() { + # create an lvol store + malloc_name=$(rpc_cmd bdev_malloc_create $MALLOC_SIZE_MB $MALLOC_BS) + lvs_uuid=$(rpc_cmd bdev_lvol_create_lvstore "$malloc_name" lvs_test) + + # An empty lvolstore is not listed by bdev_lvol_get_lvols + lvols=$(rpc_cmd bdev_lvol_get_lvols) + [ "$(jq -r '. | length' <<< "$lvols")" == "0" ] + + # Create an lvol, it should appear in the list + lvol_uuid=$(rpc_cmd bdev_lvol_create -u "$lvs_uuid" lvol_test "$LVS_DEFAULT_CAPACITY_MB") + lvols=$(rpc_cmd bdev_lvol_get_lvols) + [ "$(jq -r '. | length' <<< "$lvols")" == "1" ] + [ "$(jq -r '.[0].uuid' <<< "$lvols")" == "$lvol_uuid" ] + [ "$(jq -r '.[0].name' <<< "$lvols")" == "lvol_test" ] + [ "$(jq -r '.[0].alias' <<< "$lvols")" == "lvs_test/lvol_test" ] + [ "$(jq -r '.[0].lvs.name' <<< "$lvols")" == "lvs_test" ] + [ "$(jq -r '.[0].lvs.uuid' <<< "$lvols")" == "$lvs_uuid" ] + + rpc_cmd bdev_lvol_delete_lvstore -u "$lvs_uuid" + rpc_cmd bdev_malloc_delete "$malloc_name" + check_leftover_devices +} + # Send SIGTERM after creating lvol store function test_sigterm() { # create an lvol store @@ -563,6 +588,7 @@ run_test "test_construct_lvol_inexistent_lvs" test_construct_lvol_inexistent_lvs run_test "test_construct_lvol_full_lvs" test_construct_lvol_full_lvs run_test "test_construct_lvol_alias_conflict" test_construct_lvol_alias_conflict run_test "test_construct_nested_lvol" test_construct_nested_lvol +run_test "test_lvol_list" test_lvol_list run_test "test_sigterm" test_sigterm trap - SIGINT SIGTERM EXIT