diff --git a/doc/img/lvol_esnap_clone.svg b/doc/img/lvol_esnap_clone.svg new file mode 100644 index 000000000..f54de169c --- /dev/null +++ b/doc/img/lvol_esnap_clone.svg @@ -0,0 +1,673 @@ + + + + + + Thin Provisioning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Thin Provisioning + + + + + + + + + + + + + + + + + + + 26f9a7... + + b44ab3... + + ee5593... + + 7a3bfe... + + 8f4e15... + + + 40c285... + read-only bdev + esnap cloneVolume + + + + + + + + + + + + + + + active clusters + + + + + + + + + + + + + + + + read + allocate and copy cluster + + external snapshot cluster + + + allocated cluster + + diff --git a/doc/jsonrpc.md b/doc/jsonrpc.md index 06768d578..a7f299305 100644 --- a/doc/jsonrpc.md +++ b/doc/jsonrpc.md @@ -9636,6 +9636,55 @@ Example response: } ~~~ +### bdev_lvol_clone_bdev {#rpc_bdev_lvol_clone_bdev} + +Create a logical volume based on an external snapshot bdev. The external snapshot bdev +is a bdev that will not be written to by any consumer and must not be an lvol in the +lvstore as the clone. + +Regardless of whether the bdev is specified by name or UUID, the bdev UUID will be stored +in the logical volume's metadata for use while the lvolstore is loading. For this reason, +it is important that the bdev chosen has a static UUID. + +#### Parameters + +Name | Optional | Type | Description +----------------------- | -------- | ----------- | ----------- +bdev | Required | string | Name or UUID for bdev that acts as the external snapshot +lvs_name | Required | string | logical volume store name +clone_name | Required | string | Name for the logical volume to create + +#### Response + +UUID of the created logical volume clone is returned. + +#### Example + +Example request: + +~~~json +{ + "jsonrpc": "2.0", + "method": "bdev_lvol_clone_bdev", + "id": 1, + "params": { + "bdev_uuid": "e4b40d8b-f623-416d-8234-baf5a4c83cbd", + "lvs_name": "lvs1", + "clone_name": "clone2" + } +} +~~~ + +Example response: + +~~~json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "336f662b-08e5-4006-8e06-e2023f7f9886" +} +~~~ + ### bdev_lvol_rename {#rpc_bdev_lvol_rename} Rename a logical volume. New name will rename only the alias of the logical volume. diff --git a/doc/lvol.md b/doc/lvol.md index 977ec27ea..e5ef239c2 100644 --- a/doc/lvol.md +++ b/doc/lvol.md @@ -74,6 +74,18 @@ A snapshot can be removed only if there is a single clone on top of it. The rela The cluster map of clone and snapshot will be merged and entries for unallocated clusters in the clone will be updated with addresses from the snapshot cluster map. The entire operation modifies metadata only - no data is copied during this process. +### External Snapshots + +With the external snapshots feature, clones can be made of any bdev. These clones are commonly called *esnap clones*. +Esnap clones work very similarly to thin provisioning. Rather than the back device being an zeroes device, the external snapshot +bdev is used as the back device. + +![Clone of External Snapshot](lvol_esnap_clone.svg) + +A bdev that is used as an external snapshot cannot be opened for writing by anything else so long as an esnap clone exists. + +A bdev may have multiple esnap clones and esnap clones can themselves be snapshotted and cloned. + ### Inflation {#lvol_inflation} Blobs can be inflated to copy data from backing devices (e.g. snapshots) and allocate all remaining clusters. As a result of this @@ -155,6 +167,10 @@ bdev_lvol_clone [-h] snapshot_name clone_name Create a clone with clone_name of a given lvol snapshot. optional arguments: -h, --help show help +bdev_lvol_clone_bdev [-h] bdev_name_or_uuid lvs_name clone_name + Create a clone with clone_name of a bdev. The bdev must not be an lvol in the lvs_name lvstore. + optional arguments: + -h, --help show help bdev_lvol_rename [-h] old_name new_name Change lvol bdev name optional arguments: diff --git a/module/bdev/lvol/vbdev_lvol.c b/module/bdev/lvol/vbdev_lvol.c index b285732f0..f9629f639 100644 --- a/module/bdev/lvol/vbdev_lvol.c +++ b/module/bdev/lvol/vbdev_lvol.c @@ -1150,7 +1150,7 @@ ignore_bdev_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev, voi } void -vbdev_lvol_create_bdev_clone(const char *esnap_uuid, +vbdev_lvol_create_bdev_clone(const char *esnap_name, struct spdk_lvol_store *lvs, const char *clone_name, spdk_lvol_op_with_handle_complete cb_fn, void *cb_arg) { @@ -1158,7 +1158,6 @@ vbdev_lvol_create_bdev_clone(const char *esnap_uuid, struct spdk_bdev_desc *desc; struct spdk_bdev *bdev; char bdev_uuid[SPDK_UUID_STRING_LEN]; - struct spdk_uuid uuid; uint64_t sz; int rc; @@ -1168,16 +1167,9 @@ vbdev_lvol_create_bdev_clone(const char *esnap_uuid, return; } - rc = spdk_uuid_parse(&uuid, esnap_uuid); + rc = spdk_bdev_open_ext(esnap_name, false, ignore_bdev_event_cb, NULL, &desc); if (rc != 0) { - SPDK_ERRLOG("Invalid UUID '%s'\n", esnap_uuid); - cb_fn(cb_arg, NULL, -EINVAL); - return; - } - - rc = spdk_bdev_open_ext(esnap_uuid, false, ignore_bdev_event_cb, NULL, &desc); - if (rc != 0) { - SPDK_ERRLOG("bdev '%s' could not be opened: error %d\n", esnap_uuid, rc); + SPDK_ERRLOG("bdev '%s' could not be opened: error %d\n", esnap_name, rc); cb_fn(cb_arg, NULL, rc); return; } @@ -1186,20 +1178,12 @@ vbdev_lvol_create_bdev_clone(const char *esnap_uuid, rc = spdk_uuid_fmt_lower(bdev_uuid, sizeof(bdev_uuid), spdk_bdev_get_uuid(bdev)); if (rc != 0) { spdk_bdev_close(desc); - SPDK_ERRLOG("bdev %s: unable to parse UUID\n", esnap_uuid); + SPDK_ERRLOG("bdev %s: unable to parse UUID\n", esnap_name); assert(false); cb_fn(cb_arg, NULL, -ENODEV); return; } - /* Verify the bdev name or alias isn't a UUID that is different from the bdev's UUID. */ - if (spdk_uuid_compare(&uuid, spdk_bdev_get_uuid(bdev)) != 0) { - spdk_bdev_close(desc); - SPDK_ERRLOG("bdev with name or alias %s has UUID %s\n", esnap_uuid, bdev_uuid); - cb_fn(cb_arg, NULL, -EINVAL); - return; - } - req = calloc(1, sizeof(*req)); if (req == NULL) { spdk_bdev_close(desc); diff --git a/module/bdev/lvol/vbdev_lvol_rpc.c b/module/bdev/lvol/vbdev_lvol_rpc.c index 8f9405e17..67b7e4a94 100644 --- a/module/bdev/lvol/vbdev_lvol_rpc.c +++ b/module/bdev/lvol/vbdev_lvol_rpc.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: BSD-3-Clause * Copyright (C) 2017 Intel Corporation. * All rights reserved. + * Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #include "spdk/rpc.h" @@ -540,6 +541,89 @@ cleanup: SPDK_RPC_REGISTER("bdev_lvol_clone", rpc_bdev_lvol_clone, SPDK_RPC_RUNTIME) +struct rpc_bdev_lvol_clone_bdev { + /* name or UUID. Whichever is used, the UUID will be stored in the lvol's metadata. */ + char *bdev_name; + char *lvs_name; + char *clone_name; +}; + +static void +free_rpc_bdev_lvol_clone_bdev(struct rpc_bdev_lvol_clone_bdev *req) +{ + free(req->bdev_name); + free(req->lvs_name); + free(req->clone_name); +} + +static const struct spdk_json_object_decoder rpc_bdev_lvol_clone_bdev_decoders[] = { + { + "bdev", offsetof(struct rpc_bdev_lvol_clone_bdev, bdev_name), + spdk_json_decode_string, false + }, + { + "lvs_name", offsetof(struct rpc_bdev_lvol_clone_bdev, lvs_name), + spdk_json_decode_string, false + }, + { + "clone_name", offsetof(struct rpc_bdev_lvol_clone_bdev, clone_name), + spdk_json_decode_string, false + }, +}; + +static void +rpc_bdev_lvol_clone_bdev(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params) +{ + struct rpc_bdev_lvol_clone_bdev req = {}; + struct spdk_bdev *bdev; + struct spdk_lvol_store *lvs = NULL; + struct spdk_lvol *lvol; + int rc; + + SPDK_INFOLOG(lvol_rpc, "Cloning bdev\n"); + + if (spdk_json_decode_object(params, rpc_bdev_lvol_clone_bdev_decoders, + SPDK_COUNTOF(rpc_bdev_lvol_clone_bdev_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(NULL, req.lvs_name, &lvs); + if (rc != 0) { + SPDK_INFOLOG(lvol_rpc, "lvs_name '%s' not found\n", req.lvs_name); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "lvs does not exist"); + goto cleanup; + } + + bdev = spdk_bdev_get_by_name(req.bdev_name); + if (bdev == NULL) { + SPDK_INFOLOG(lvol_rpc, "bdev '%s' does not exist\n", req.bdev_name); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "bdev does not exist"); + goto cleanup; + } + + lvol = vbdev_lvol_get_from_bdev(bdev); + if (lvol != NULL && lvol->lvol_store == lvs) { + SPDK_INFOLOG(lvol_rpc, "bdev '%s' is an lvol in lvstore '%s\n", req.bdev_name, + req.lvs_name); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "bdev is an lvol in same lvs as clone; " + "use bdev_lvol_clone instead"); + goto cleanup; + } + + vbdev_lvol_create_bdev_clone(req.bdev_name, lvs, req.clone_name, + rpc_bdev_lvol_clone_cb, request); +cleanup: + free_rpc_bdev_lvol_clone_bdev(&req); +} + +SPDK_RPC_REGISTER("bdev_lvol_clone_bdev", rpc_bdev_lvol_clone_bdev, SPDK_RPC_RUNTIME) + struct rpc_bdev_lvol_rename { char *old_name; char *new_name; diff --git a/python/spdk/rpc/lvol.py b/python/spdk/rpc/lvol.py index 5ce534a34..74ceb833c 100644 --- a/python/spdk/rpc/lvol.py +++ b/python/spdk/rpc/lvol.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (C) 2017 Intel Corporation. # All rights reserved. +# Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. def bdev_lvol_create_lvstore(client, bdev_name, lvs_name, cluster_sz=None, @@ -122,6 +123,30 @@ def bdev_lvol_clone(client, snapshot_name, clone_name): return client.call('bdev_lvol_clone', params) +def bdev_lvol_clone_bdev(client, bdev, lvs_name, clone_name): + """Create a logical volume based on a snapshot. + + Regardless of whether the bdev is specified by name or UUID, the bdev UUID + will be stored in the logical volume's metadata for use while the lvolstore + is loading. For this reason, it is important that the bdev chosen has a + static UUID. + + Args: + bdev: bdev to clone; must not be an lvol in same lvstore as clone + lvs_name: name of logical volume store to use + clone_name: name of logical volume to create + + Returns: + Name of created logical volume clone. + """ + params = { + 'bdev': bdev, + 'lvs_name': lvs_name, + 'clone_name': clone_name + } + return client.call('bdev_lvol_clone_bdev', params) + + def bdev_lvol_rename(client, old_name, new_name): """Rename a logical volume. diff --git a/scripts/rpc.py b/scripts/rpc.py index 463d7264a..a9331e4e3 100755 --- a/scripts/rpc.py +++ b/scripts/rpc.py @@ -3,6 +3,7 @@ # Copyright (C) 2016 Intel Corporation # All rights reserved. # Copyright (c) 2022 Dell Inc, or its subsidiaries. +# Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # import logging @@ -1966,6 +1967,19 @@ Format: 'user:u1 secret:s1 muser:mu1 msecret:ms1,user:u2 secret:s2 muser:mu2 mse p.add_argument('clone_name', help='lvol clone name') p.set_defaults(func=bdev_lvol_clone) + def bdev_lvol_clone_bdev(args): + print_json(rpc.lvol.bdev_lvol_clone_bdev(args.client, + bdev=args.bdev, + lvs_name=args.lvs_name, + clone_name=args.clone_name)) + + p = subparsers.add_parser('bdev_lvol_clone_bdev', + help='Create a clone of a non-lvol bdev') + p.add_argument('bdev', help='bdev to clone') + p.add_argument('lvs_name', help='logical volume store name') + p.add_argument('clone_name', help='lvol clone name') + p.set_defaults(func=bdev_lvol_clone_bdev) + def bdev_lvol_rename(args): rpc.lvol.bdev_lvol_rename(args.client, old_name=args.old_name, diff --git a/test/common/autotest_common.sh b/test/common/autotest_common.sh index 4d9d14602..d743f5d21 100755 --- a/test/common/autotest_common.sh +++ b/test/common/autotest_common.sh @@ -609,6 +609,12 @@ function rpc_cmd_simple_data_json() { "num_blocks" "uuid" "product_name" + "supported_io_types.read" + "supported_io_types.write" + "driver_specific.lvol.clone" + "driver_specific.lvol.base_snapshot" + "driver_specific.lvol.esnap_clone" + "driver_specific.lvol.external_snapshot_name" ) [[ -v $elems ]] || return 1 diff --git a/test/lvol/external_snapshot.sh b/test/lvol/external_snapshot.sh new file mode 100755 index 000000000..7d10ac167 --- /dev/null +++ b/test/lvol/external_snapshot.sh @@ -0,0 +1,232 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +testdir=$(readlink -f "$(dirname "$0")") +rootdir=$(readlink -f "$testdir/../..") +source "$rootdir/test/common/autotest_common.sh" +source "$rootdir/test/lvol/common.sh" +source "$rootdir/test/bdev/nbd_common.sh" + +set -u + +g_nbd_dev=INVALID +g_cluster_size=INVALID +g_block_size=INVALID + +function test_esnap_reload() { + local bs_dev esnap_dev + local block_size=512 + local esnap_size_mb=1 + local lvs_cluster_size=$((16 * 1024)) + local lvs_uuid esnap_uuid eclone_uuid snap_uuid clone_uuid uuid + local aio_bdev=test_esnap_reload_aio0 + + # Create the lvstore on an aio device. Can't use malloc because we need to remove + # the device and re-add it to trigger an lvstore unload and then load. + rm -f $testdir/aio_bdev_0 + truncate -s "${AIO_SIZE_MB}M" $testdir/aio_bdev_0 + bs_dev=$(rpc_cmd bdev_aio_create "$testdir/aio_bdev_0" "$aio_bdev" "$block_size") + lvs_uuid=$(rpc_cmd bdev_lvol_create_lvstore -c "$lvs_cluster_size" "$bs_dev" lvs_test) + + # Create a bdev that will be the external snapshot + esnap_uuid=e4b40d8b-f623-416d-8234-baf5a4c83cbd + esnap_dev=$(rpc_cmd bdev_malloc_create -u "$esnap_uuid" "$esnap_size_mb" "$block_size") + eclone_uuid=$(rpc_cmd bdev_lvol_clone_bdev "$esnap_uuid" lvs_test "eclone1") + + # Unload the lvstore + rpc_cmd bdev_aio_delete "$aio_bdev" + NOT rpc_cmd bdev_lvol_get_lvstores -l lvs_test + + # Load the lvstore, expect to see eclone1 again + bs_dev=$(rpc_cmd bdev_aio_create "$testdir/aio_bdev_0" "$aio_bdev" "$block_size") + lvs_uuid=$(rpc_cmd bdev_lvol_get_lvstores -l lvs_test) + uuid=$(rpc_cmd bdev_get_bdevs -b lvs_test/eclone1 | jq -r '.[].name') + [[ "$uuid" == "$eclone_uuid" ]] + + # Create a snapshot of the eclone, reload, and verify all is there. + snap_uuid=$(rpc_cmd bdev_lvol_snapshot "$eclone_uuid" snap1) + rpc_cmd bdev_aio_delete "$aio_bdev" + NOT rpc_cmd bdev_lvol_get_lvstores -l lvs_test + bs_dev=$(rpc_cmd bdev_aio_create "$testdir/aio_bdev_0" "$aio_bdev" "$block_size") + lvs_uuid=$(rpc_cmd bdev_lvol_get_lvstores -l lvs_test) + uuid=$(rpc_cmd bdev_get_bdevs -b lvs_test/eclone1 | jq -r '.[].name') + [[ "$uuid" == "$eclone_uuid" ]] + uuid=$(rpc_cmd bdev_get_bdevs -b lvs_test/snap1 | jq -r '.[].name') + [[ "$uuid" == "$snap_uuid" ]] + + # Create a clone of the snapshot, reload, and verify all is there. + clone_uuid=$(rpc_cmd bdev_lvol_clone "$snap_uuid" clone1) + rpc_cmd bdev_aio_delete "$aio_bdev" + NOT rpc_cmd bdev_lvol_get_lvstores -l lvs_test + bs_dev=$(rpc_cmd bdev_aio_create "$testdir/aio_bdev_0" "$aio_bdev" "$block_size") + lvs_uuid=$(rpc_cmd bdev_lvol_get_lvstores -l lvs_test) + uuid=$(rpc_cmd bdev_get_bdevs -b lvs_test/eclone1 | jq -r '.[].name') + [[ "$uuid" == "$eclone_uuid" ]] + uuid=$(rpc_cmd bdev_get_bdevs -b lvs_test/snap1 | jq -r '.[].name') + [[ "$uuid" == "$snap_uuid" ]] + uuid=$(rpc_cmd bdev_get_bdevs -b lvs_test/clone1 | jq -r '.[].name') + [[ "$uuid" == "$clone_uuid" ]] + + rpc_cmd bdev_lvol_delete "$clone_uuid" + rpc_cmd bdev_lvol_delete "$snap_uuid" + rpc_cmd bdev_lvol_delete "$eclone_uuid" + rpc_cmd bdev_aio_delete "$aio_bdev" + rpc_cmd bdev_malloc_delete "$esnap_dev" +} + +function log_jq_out() { + local key + + xtrace_disable + + while read -r key; do + printf '%50s = %s\n' "$key" "${jq_out[$key]}" + done < <(printf '%s\n' "${!jq_out[@]}" | sort) + + xtrace_restore +} + +function verify_clone() { + local bdev=$1 + local parent=$2 + + rpc_cmd_simple_data_json bdev bdev_get_bdevs -b "$bdev" + log_jq_out + + [[ "${jq_out["supported_io_types.read"]}" == true ]] + [[ "${jq_out["supported_io_types.write"]}" == true ]] + [[ "${jq_out["driver_specific.lvol.clone"]}" == true ]] + [[ "${jq_out["driver_specific.lvol.base_snapshot"]}" == "$parent" ]] + [[ "${jq_out["driver_specific.lvol.esnap_clone"]}" == false ]] + [[ "${jq_out["driver_specific.lvol.external_snapshot_name"]}" == null ]] +} + +function verify_esnap_clone() { + local bdev=$1 + local parent=$2 + local writable=${3:-true} + + rpc_cmd_simple_data_json bdev bdev_get_bdevs -b "$bdev" + log_jq_out + + [[ "${jq_out["supported_io_types.read"]}" == true ]] + [[ "${jq_out["supported_io_types.write"]}" == "$writable" ]] + [[ "${jq_out["driver_specific.lvol.esnap_clone"]}" == true ]] + [[ "${jq_out["driver_specific.lvol.external_snapshot_name"]}" == "$parent" ]] +} + +function test_esnap_clones() { + local bs_dev esnap_dev + local block_size=512 + local lvs_size_mb=100 + local esnap_size_mb=1 + local lvs_cluster_size=$((16 * 1024)) + local lvs_uuid esnap_uuid + local vol1_uuid vol2_uuid vol3_uuid vol3_uuid vol4_uuid vol5_uuid + + # Create the lvstore on a malloc device. + bs_dev=$(rpc_cmd bdev_malloc_create $lvs_size_mb $block_size) + lvs_uuid=$(rpc_cmd bdev_lvol_create_lvstore -c "$lvs_cluster_size" "$bs_dev" lvs_test) + + # Create a bdev that will be the external snapshot + # State: + # esnap1 + esnap_uuid=2abddd12-c08d-40ad-bccf-ab131586ee4c + esnap_dev=$(rpc_cmd bdev_malloc_create -b esnap1 -u "$esnap_uuid" "$esnap_size_mb" \ + "$block_size") + + # Create an esnap clone: vol1 + # New state: + # esnap1 <-- vol1(rw) + vol1_uuid=$(rpc_cmd bdev_lvol_clone_bdev "$esnap_uuid" lvs_test vol1) + verify_esnap_clone "$vol1_uuid" "$esnap_uuid" + + # Create a snapshot of the esnap clone: vol2 + # New state: + # esnap1 <-- vol2(ro) <-- vol1(rw) + vol2_uuid=$(rpc_cmd bdev_lvol_snapshot "$vol1_uuid" vol2) + verify_esnap_clone "$vol2_uuid" "$esnap_uuid" false + verify_clone "$vol1_uuid" vol2 + + # Delete vol2. + # New state: + # esnap1 <-- vol1(rw) + rpc_cmd bdev_lvol_delete "$vol2_uuid" + NOT rpc_cmd bdev_get_bdevs -b "$vol2_uuid" + verify_esnap_clone "$vol1_uuid" "$esnap_uuid" + vol2_uuid= + + # Snapshot vol1: vol3 + # New state: + # ensap1 <-- vol3(ro) <-- vol1(rw) + vol3_uuid=$(rpc_cmd bdev_lvol_snapshot "$vol1_uuid" vol3) + verify_esnap_clone "$vol3_uuid" "$esnap_uuid" false + verify_clone "$vol1_uuid" vol3 + + # Delete vol1 + # New state: + # esnap1 <-- vol3(ro) + rpc_cmd bdev_lvol_delete $vol1_uuid + NOT rpc_cmd bdev_get_bdevs -b $vol1_uuid + verify_esnap_clone "$vol3_uuid" "$esnap_uuid" false + vol1_uuid= + + # Create clone of vol3: vol4 + # Verify vol3 is still a read-only esnap clone and vol4 is a normal clone. + # New state: + # ensap1 <-- vol3(ro) <-- vol4(rw) + vol4_uuid=$(rpc_cmd bdev_lvol_clone "$vol3_uuid" vol4) + rpc_cmd bdev_get_bdevs -b "$vol4_uuid" + verify_esnap_clone "$vol3_uuid" "$esnap_uuid" false + verify_clone "$vol4_uuid" vol3 + + # Create clone of vol3 (vol5). + # New state: + # ensap1 <-- vol3(ro) <-- vol4(rw) + # `<-- vol5(rw) + vol5_uuid=$(rpc_cmd bdev_lvol_clone "$vol3_uuid" vol5) + verify_esnap_clone "$vol3_uuid" "$esnap_uuid" false + verify_clone "$vol4_uuid" vol3 + verify_clone "$vol5_uuid" vol3 + + # Cannot delete vol3 because it has multiple clones + NOT rpc_cmd bdev_lvol_delete "$vol3_uuid" + + # Delete vol4 + # New state: + # ensap1 <-- vol3(ro) <-- vol5(rw) + rpc_cmd bdev_lvol_delete "$vol4_uuid" + NOT rpc_cmd bdev_get_bdevs -b "$vol4_uuid" + verify_esnap_clone "$vol3_uuid" "$esnap_uuid" false + verify_clone "$vol5_uuid" vol3 + + # Delete vol3. + # New state: + # ensap1 <-- vol5(rw) + rpc_cmd bdev_lvol_delete "$vol3_uuid" + NOT rpc_cmd bdev_get_bdevs -b "$vol3_uuid" + verify_esnap_clone "$vol5_uuid" "$esnap_uuid" + + # Delete vol5. + # New state: + # esnap1 + rpc_cmd bdev_lvol_delete "$vol5_uuid" + NOT rpc_cmd bdev_get_bdevs -b "$vol5_uuid" + + rpc_cmd bdev_malloc_delete "$bs_dev" + rpc_cmd bdev_malloc_delete "$esnap_dev" +} + +$SPDK_BIN_DIR/spdk_tgt & +spdk_pid=$! +trap 'killprocess "$spdk_pid"; rm -f "$testdir/aio_bdev_0"; exit 1' SIGINT SIGTERM SIGPIPE EXIT +waitforlisten $spdk_pid +modprobe nbd + +run_test "test_esnap_reload" test_esnap_reload +run_test "test_esnap_clones" test_esnap_clones + +trap - SIGINT SIGTERM SIGPIPE EXIT +killprocess $spdk_pid +rm -f "$testdir/aio_bdev_0" diff --git a/test/lvol/lvol.sh b/test/lvol/lvol.sh index fe71d9aa3..21b3a7ec4 100755 --- a/test/lvol/lvol.sh +++ b/test/lvol/lvol.sh @@ -19,6 +19,7 @@ run_test "lvol_snapshot_clone" $rootdir/test/lvol/snapshot_clone.sh run_test "lvol_rename" $rootdir/test/lvol/rename.sh run_test "lvol_provisioning" $rootdir/test/lvol/thin_provisioning.sh run_test "lvol_esnap" $rootdir/test/lvol/esnap/esnap +run_test "lvol_external_snapshot" $rootdir/test/lvol/external_snapshot.sh timing_exit basic timing_exit lvol diff --git a/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c b/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c index 16839baba..8752fd86b 100644 --- a/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c +++ b/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c @@ -1826,7 +1826,7 @@ ut_lvol_esnap_clone_bad_args(void) struct spdk_bdev bdev = { 0 }; struct spdk_lvol_store *lvs; const char *esnap_uuid = "255f4236-9427-42d0-a9d1-aa17f37dd8db"; - const char *name_uuid = "5c164b0a-93af-434f-ac35-51af59791f3b"; + const char *esnap_name = "esnap1"; int rc; /* Lvol store is successfully created */ @@ -1840,7 +1840,7 @@ ut_lvol_esnap_clone_bad_args(void) rc = spdk_uuid_parse(&bdev.uuid, esnap_uuid); CU_ASSERT(rc == 0); - bdev.name = strdup(name_uuid); + bdev.name = strdup(esnap_name); SPDK_CU_ASSERT_FATAL(bdev.name != NULL); bdev.blocklen = 512; bdev.blockcnt = 8192; @@ -1852,23 +1852,23 @@ ut_lvol_esnap_clone_bad_args(void) vbdev_lvol_create_bdev_clone(esnap_uuid, NULL, "clone1", vbdev_lvol_create_complete, NULL); CU_ASSERT(g_lvolerrno == -EINVAL); - /* Error when a uuid-like name is provided and that matches the bdev name but not uuid */ - g_lvolerrno = 0xbad; - vbdev_lvol_create_bdev_clone(name_uuid, lvs, "clone1", vbdev_lvol_create_complete, NULL); - CU_ASSERT(g_lvolerrno == -EINVAL); - /* Error when the bdev does not exist */ g_base_bdev = NULL; g_lvolerrno = 0xbad; vbdev_lvol_create_bdev_clone(esnap_uuid, lvs, "clone1", vbdev_lvol_create_complete, NULL); CU_ASSERT(g_lvolerrno == -ENODEV); - /* Success when the stars all align. */ + /* Success when creating by bdev UUID */ g_base_bdev = &bdev; g_lvolerrno = 0xbad; vbdev_lvol_create_bdev_clone(esnap_uuid, lvs, "clone1", vbdev_lvol_create_complete, NULL); CU_ASSERT(g_lvolerrno == 0); + /* Success when creating by bdev name */ + g_lvolerrno = 0xbad; + vbdev_lvol_create_bdev_clone(esnap_name, lvs, "clone2", vbdev_lvol_create_complete, NULL); + CU_ASSERT(g_lvolerrno == 0); + g_lvol_store = lvs; vbdev_lvs_destruct(g_lvol_store, lvol_store_op_complete, NULL); CU_ASSERT(g_lvserrno == 0);