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 <mgerdts@nvidia.com>
Change-Id: I795161ac88d9707831d9fcd2079635c7e46ecc42
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/17547
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
This commit is contained in:
Mike Gerdts 2023-04-10 08:12:49 -05:00 committed by David Ko
parent 68cde3b770
commit a4862f5a56
7 changed files with 227 additions and 0 deletions

View File

@ -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

View File

@ -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}

View File

@ -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:

View File

@ -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;

View File

@ -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)

View File

@ -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))

View File

@ -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