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