blob: clones of external snapshots
This is the beginning of support for external snapshots. An external snapshot is a read-only blobstore device (struct spdk_bs_dev) that can be used as a blob's back device. Normally a blob will have no back device (a normal blob), a zeroes back device (a thin provisioned blob), or a blob back device (a clone blob). When a blob has an external snapshot ("esnap") as its back device, it is called an esnap clone. With this patch, esnap clones can be created but they are not yet useful. Subsequent patches in the series will plumb the IO path, enable various features, and allow lvol bdevs to be esnap clones. Signed-off-by: Mike Gerdts <mgerdts@nvidia.com> Change-Id: I29206b628a2b03b6386a88532565e228df988e0e Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/14969 Tested-by: SPDK CI Jenkins <sys_sgci@intel.com> Reviewed-by: Shuhei Matsumoto <smatsumoto@nvidia.com> Reviewed-by: Jim Harris <james.r.harris@intel.com>
This commit is contained in:
parent
0f4bfd55e7
commit
ce67e0c787
24
doc/blob.md
24
doc/blob.md
@ -159,6 +159,9 @@ options and their defaults are:
|
||||
Blobstore found here is appropriate to claim or not. The default is NULL and unless the application is being deployed in
|
||||
an environment where multiple applications using the same disks are at risk of inadvertently using the wrong Blobstore, there
|
||||
is no need to set this value. It can, however, be set to any valid set of characters.
|
||||
* **External Snapshot Device Creation Callback**: If the blobstore supports external snapshots this function will be called
|
||||
as a blob that clones an external snapshot (an "esnap clone") is opened so that the blobstore consumer can load the external
|
||||
snapshot and register a blobstore device that will satisfy read requests. See @ref blob_pg_esnap_and_esnap_clone.
|
||||
|
||||
### Sub-page Sized Operations
|
||||
|
||||
@ -324,6 +327,9 @@ on whether the operation targets blocks that are backed by a cluster owned by th
|
||||
* **write to other blocks**: A copy-on-write operation is triggered. See @ref blob_pg_copy_on_write
|
||||
for details.
|
||||
|
||||
External snapshots allow some external data source to act as a snapshot. This allows clones to be
|
||||
created of data that resides outside of the blobstore containing the clone.
|
||||
|
||||
#### Thin Provisioning {#blob_pg_thin_provisioning}
|
||||
|
||||
As mentioned in @ref blob_pg_cluster_layout, a blob may be thin provisioned. A thin provisioned blob
|
||||
@ -412,6 +418,21 @@ A clone can remove its dependence on a snapshot with the following operations:
|
||||
than being copied. If the snapshot that was deleted was itself a clone of another snapshot, the
|
||||
clone remains a clone, but is now a clone of a different snapshot.
|
||||
|
||||
#### External Snapshots and Esnap Clones {#blob_pg_esnap_and_esnap_clone}
|
||||
|
||||
A blobstore that is loaded with the `esnap_bs_dev_create` callback defined will support external
|
||||
snapshots (esnaps). An external snapshot is not useful on its own: it needs to be cloned by a blob.
|
||||
A clone of an external snapshot is referred to as an *esnap clone*. An esnap clone supports IO and
|
||||
other operations just like any other clone.
|
||||
|
||||
An esnap clone can be recognized in various ways:
|
||||
|
||||
* **On disk**: the blob metadata has the `SPDK_BLOB_EXTERNAL_SNAPSHOT` (0x8) bit is set in
|
||||
`invalid_flags` and an internal XATTR with name `BLOB_EXTERNAL_SNAPSHOT_ID` ("EXTSNAP") exists.
|
||||
* **In memory**: The `spdk_blob` structure contains the metadata read from disk, `blob->parent_id`
|
||||
is set to `SPDK_BLOBID_EXTERNAL_SNAPSHOT`, and `blob->back_bs_dev` references a blobstore device
|
||||
which is not a blob in the same blobstore nor a zeroes device.
|
||||
|
||||
#### Copy-on-write {#blob_pg_copy_on_write}
|
||||
|
||||
A copy-on-write operation is somewhat expensive, with the cost being proportional to the cluster
|
||||
@ -427,7 +448,8 @@ size. Typical copy-on-write involves the following steps:
|
||||
|
||||
If the source cluster is backed by a zeroes device, steps 2 through 4 are skipped. Alternatively, if
|
||||
the blobstore resides on a device that can perform the copy on its own, steps 2 through 4 are
|
||||
offloaded to the device.
|
||||
offloaded to the device. Neither of these optimizations are available when the back device is an
|
||||
external snapshot.
|
||||
|
||||
### Sequences and Batches
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause
|
||||
* Copyright (C) 2017 Intel Corporation.
|
||||
* All rights reserved.
|
||||
* Copyright (c) 2021, 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* Copyright (c) 2021-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
@ -41,7 +41,8 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
typedef uint64_t spdk_blob_id;
|
||||
#define SPDK_BLOBID_INVALID (uint64_t)-1
|
||||
#define SPDK_BLOBID_INVALID (uint64_t)-1
|
||||
#define SPDK_BLOBID_EXTERNAL_SNAPSHOT (uint64_t)-2
|
||||
#define SPDK_BLOBSTORE_TYPE_LENGTH 16
|
||||
|
||||
enum blob_clear_method {
|
||||
@ -58,6 +59,7 @@ enum bs_clear_method {
|
||||
};
|
||||
|
||||
struct spdk_blob_store;
|
||||
struct spdk_bs_dev;
|
||||
struct spdk_io_channel;
|
||||
struct spdk_blob;
|
||||
struct spdk_xattr_names;
|
||||
@ -116,6 +118,30 @@ typedef void (*spdk_blob_op_with_handle_complete)(void *cb_arg, struct spdk_blob
|
||||
typedef void (*spdk_bs_dev_cpl)(struct spdk_io_channel *channel,
|
||||
void *cb_arg, int bserrno);
|
||||
|
||||
/**
|
||||
* Blob device open completion callback with blobstore device.
|
||||
*
|
||||
* \param cb_arg Callback argument.
|
||||
* \param bs_dev Blobstore device.
|
||||
* \param bserrno 0 if it completed successfully, or negative errno if it failed.
|
||||
*/
|
||||
typedef void (*spdk_blob_op_with_bs_dev)(void *cb_arg, struct spdk_bs_dev *bs_dev, int bserrno);
|
||||
|
||||
/**
|
||||
* External snapshot device open callback. As an esnap clone blob is loading, it uses this
|
||||
* callback registered with the blobstore to create the external snapshot device. The blobstore
|
||||
* consumer must set this while loading the blobstore if it intends to support external snapshots.
|
||||
*
|
||||
* \param blob The blob that needs its external snapshot device.
|
||||
* \param esnap_id A copy of the esnap_id passed via blob_opts when creating the esnap clone.
|
||||
* \param id_size The size in bytes of the data referenced by esnap_id.
|
||||
* \param bs_dev When 0 is returned, the newly created blobstore device is returned by reference.
|
||||
*
|
||||
* \return 0 on success, else a negative errno.
|
||||
*/
|
||||
typedef int (*spdk_bs_esnap_dev_create)(struct spdk_blob *blob, const void *esnap_id,
|
||||
uint32_t id_size, struct spdk_bs_dev **bs_dev);
|
||||
|
||||
struct spdk_bs_dev_cb_args {
|
||||
spdk_bs_dev_cpl cb_fn;
|
||||
struct spdk_io_channel *channel;
|
||||
@ -256,8 +282,13 @@ struct spdk_bs_opts {
|
||||
|
||||
/** Force recovery during import. This is a uint64_t for padding reasons, treated as a bool. */
|
||||
uint64_t force_recover;
|
||||
|
||||
/**
|
||||
* External snapshot creation callback to register with the blobstore.
|
||||
*/
|
||||
spdk_bs_esnap_dev_create esnap_bs_dev_create;
|
||||
} __attribute__((packed));
|
||||
SPDK_STATIC_ASSERT(sizeof(struct spdk_bs_opts) == 72, "Incorrect size");
|
||||
SPDK_STATIC_ASSERT(sizeof(struct spdk_bs_opts) == 80, "Incorrect size");
|
||||
|
||||
/**
|
||||
* Initialize a spdk_bs_opts structure to the default blobstore option values.
|
||||
@ -497,8 +528,24 @@ struct spdk_blob_opts {
|
||||
* New added fields should be put at the end of the struct.
|
||||
*/
|
||||
size_t opts_size;
|
||||
|
||||
/**
|
||||
* If set, create an esnap clone. The memory referenced by esnap_id will be copied into the
|
||||
* blob's metadata and can be retrieved with spdk_blob_get_esnap_id(), typically from an
|
||||
* esnap_bs_dev_create() callback.
|
||||
* See struct_bs_opts.
|
||||
*
|
||||
* When esnap_id is specified, num_clusters should be specified. If it is not, the blob will
|
||||
* have no capacity until spdk_blob_resize() is called.
|
||||
*/
|
||||
const void *esnap_id;
|
||||
|
||||
/**
|
||||
* The size of data referenced by esnap_id, in bytes.
|
||||
*/
|
||||
uint64_t esnap_id_len;
|
||||
};
|
||||
SPDK_STATIC_ASSERT(sizeof(struct spdk_blob_opts) == 64, "Incorrect size");
|
||||
SPDK_STATIC_ASSERT(sizeof(struct spdk_blob_opts) == 80, "Incorrect size");
|
||||
|
||||
/**
|
||||
* Initialize a spdk_blob_opts structure to the default blob option values.
|
||||
@ -596,6 +643,18 @@ int spdk_blob_get_clones(struct spdk_blob_store *bs, spdk_blob_id blobid, spdk_b
|
||||
*/
|
||||
spdk_blob_id spdk_blob_get_parent_snapshot(struct spdk_blob_store *bs, spdk_blob_id blobid);
|
||||
|
||||
/**
|
||||
* Get the id used to access the esnap clone's parent.
|
||||
*
|
||||
* \param blob The clone's blob.
|
||||
* \param id On successful return, *id will reference memory that has the same life as blob.
|
||||
* \param len On successful return *len will be the size of id in bytes.
|
||||
*
|
||||
* \return 0 on success
|
||||
* \return -EINVAL if blob is not an esnap clone.
|
||||
*/
|
||||
int spdk_blob_get_esnap_id(struct spdk_blob *blob, const void **id, size_t *len);
|
||||
|
||||
/**
|
||||
* Check if blob is read only.
|
||||
*
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause
|
||||
* Copyright (C) 2017 Intel Corporation.
|
||||
* All rights reserved.
|
||||
* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* Copyright (c) 2021-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "spdk/stdinc.h"
|
||||
@ -40,6 +40,13 @@ static int blob_remove_xattr(struct spdk_blob *blob, const char *name, bool inte
|
||||
static void blob_write_extent_page(struct spdk_blob *blob, uint32_t extent, uint64_t cluster_num,
|
||||
struct spdk_blob_md_page *page, spdk_blob_op_complete cb_fn, void *cb_arg);
|
||||
|
||||
static inline bool
|
||||
blob_is_esnap_clone(const struct spdk_blob *blob)
|
||||
{
|
||||
assert(blob != NULL);
|
||||
return !!(blob->invalid_flags & SPDK_BLOB_EXTERNAL_SNAPSHOT);
|
||||
}
|
||||
|
||||
static int
|
||||
blob_id_cmp(struct spdk_blob *blob1, struct spdk_blob *blob2)
|
||||
{
|
||||
@ -1338,6 +1345,54 @@ blob_load_snapshot_cpl(void *cb_arg, struct spdk_blob *snapshot, int bserrno)
|
||||
|
||||
static void blob_update_clear_method(struct spdk_blob *blob);
|
||||
|
||||
static int
|
||||
blob_load_esnap(struct spdk_blob *blob)
|
||||
{
|
||||
struct spdk_blob_store *bs = blob->bs;
|
||||
struct spdk_bs_dev *bs_dev = NULL;
|
||||
const void *esnap_id = NULL;
|
||||
size_t id_len = 0;
|
||||
int rc;
|
||||
|
||||
if (bs->esnap_bs_dev_create == NULL) {
|
||||
SPDK_NOTICELOG("blob 0x%" PRIx64 " is an esnap clone but the blobstore was opened "
|
||||
"without support for esnap clones\n", blob->id);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
assert(blob->back_bs_dev == NULL);
|
||||
|
||||
rc = blob_get_xattr_value(blob, BLOB_EXTERNAL_SNAPSHOT_ID, &esnap_id, &id_len, true);
|
||||
if (rc != 0) {
|
||||
SPDK_ERRLOG("blob 0x%" PRIx64 " is an esnap clone but has no esnap ID\n", blob->id);
|
||||
return -EINVAL;
|
||||
}
|
||||
assert(id_len > 0 && id_len < UINT32_MAX);
|
||||
|
||||
SPDK_INFOLOG(blob, "Creating external snapshot device\n");
|
||||
|
||||
rc = bs->esnap_bs_dev_create(blob, esnap_id, (uint32_t)id_len, &bs_dev);
|
||||
if (rc != 0) {
|
||||
SPDK_DEBUGLOG(blob_esnap, "blob 0x%" PRIx64 ": failed to load back_bs_dev "
|
||||
"with error %d\n", blob->id, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
assert(bs_dev != NULL);
|
||||
SPDK_DEBUGLOG(blob_esnap, "blob 0x%" PRIx64 ": loaded back_bs_dev\n", blob->id);
|
||||
if ((bs->io_unit_size % bs_dev->blocklen) != 0) {
|
||||
SPDK_NOTICELOG("blob 0x%" PRIx64 " external snapshot device block size %u is not "
|
||||
"compatible with blobstore block size %u\n",
|
||||
blob->id, bs_dev->blocklen, bs->io_unit_size);
|
||||
bs_dev->destroy(bs_dev);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
blob->back_bs_dev = bs_dev;
|
||||
blob->parent_id = SPDK_BLOBID_EXTERNAL_SNAPSHOT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
blob_load_backing_dev(void *cb_arg)
|
||||
{
|
||||
@ -1347,6 +1402,13 @@ blob_load_backing_dev(void *cb_arg)
|
||||
size_t len;
|
||||
int rc;
|
||||
|
||||
if (blob_is_esnap_clone(blob)) {
|
||||
rc = blob_load_esnap(blob);
|
||||
assert((rc == 0) ^ (blob->back_bs_dev == NULL));
|
||||
blob_load_final(ctx, rc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spdk_blob_is_thin_provisioned(blob)) {
|
||||
rc = blob_get_xattr_value(blob, BLOB_SNAPSHOT, &value, &len, true);
|
||||
if (rc == 0) {
|
||||
@ -3284,7 +3346,8 @@ bs_blob_list_add(struct spdk_blob *blob)
|
||||
assert(blob != NULL);
|
||||
|
||||
snapshot_id = blob->parent_id;
|
||||
if (snapshot_id == SPDK_BLOBID_INVALID) {
|
||||
if (snapshot_id == SPDK_BLOBID_INVALID ||
|
||||
snapshot_id == SPDK_BLOBID_EXTERNAL_SNAPSHOT) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -3407,6 +3470,7 @@ spdk_bs_opts_init(struct spdk_bs_opts *opts, size_t opts_size)
|
||||
SET_FIELD(iter_cb_fn, NULL);
|
||||
SET_FIELD(iter_cb_arg, NULL);
|
||||
SET_FIELD(force_recover, false);
|
||||
SET_FIELD(esnap_bs_dev_create, NULL);
|
||||
|
||||
#undef FIELD_OK
|
||||
#undef SET_FIELD
|
||||
@ -3534,6 +3598,7 @@ bs_alloc(struct spdk_bs_dev *dev, struct spdk_bs_opts *opts, struct spdk_blob_st
|
||||
bs->max_channel_ops = opts->max_channel_ops;
|
||||
bs->super_blob = SPDK_BLOBID_INVALID;
|
||||
memcpy(&bs->bstype, &opts->bstype, sizeof(opts->bstype));
|
||||
bs->esnap_bs_dev_create = opts->esnap_bs_dev_create;
|
||||
|
||||
/* The metadata is assumed to be at least 1 page */
|
||||
bs->used_md_pages = spdk_bit_array_create(1);
|
||||
@ -4610,12 +4675,13 @@ bs_opts_copy(struct spdk_bs_opts *src, struct spdk_bs_opts *dst)
|
||||
SET_FIELD(iter_cb_fn);
|
||||
SET_FIELD(iter_cb_arg);
|
||||
SET_FIELD(force_recover);
|
||||
SET_FIELD(esnap_bs_dev_create);
|
||||
|
||||
dst->opts_size = src->opts_size;
|
||||
|
||||
/* You should not remove this statement, but need to update the assert statement
|
||||
* if you add a new field, and also add a corresponding SET_FIELD statement */
|
||||
SPDK_STATIC_ASSERT(sizeof(struct spdk_bs_opts) == 72, "Incorrect size");
|
||||
SPDK_STATIC_ASSERT(sizeof(struct spdk_bs_opts) == 80, "Incorrect size");
|
||||
|
||||
#undef FIELD_OK
|
||||
#undef SET_FIELD
|
||||
@ -5780,12 +5846,14 @@ blob_opts_copy(const struct spdk_blob_opts *src, struct spdk_blob_opts *dst)
|
||||
}
|
||||
|
||||
SET_FIELD(use_extent_table);
|
||||
SET_FIELD(esnap_id);
|
||||
SET_FIELD(esnap_id_len);
|
||||
|
||||
dst->opts_size = src->opts_size;
|
||||
|
||||
/* You should not remove this statement, but need to update the assert statement
|
||||
* if you add a new field, and also add a corresponding SET_FIELD statement */
|
||||
SPDK_STATIC_ASSERT(sizeof(struct spdk_blob_opts) == 64, "Incorrect size");
|
||||
SPDK_STATIC_ASSERT(sizeof(struct spdk_blob_opts) == 80, "Incorrect size");
|
||||
|
||||
#undef FIELD_OK
|
||||
#undef SET_FIELD
|
||||
@ -5860,6 +5928,22 @@ bs_create_blob(struct spdk_blob_store *bs,
|
||||
|
||||
blob_set_clear_method(blob, opts_local.clear_method);
|
||||
|
||||
if (opts_local.esnap_id != NULL) {
|
||||
if (opts_local.esnap_id_len > UINT16_MAX) {
|
||||
SPDK_ERRLOG("esnap id length %" PRIu64 "is too long\n",
|
||||
opts_local.esnap_id_len);
|
||||
goto error;
|
||||
|
||||
}
|
||||
blob_set_thin_provision(blob);
|
||||
blob->invalid_flags |= SPDK_BLOB_EXTERNAL_SNAPSHOT;
|
||||
rc = blob_set_xattr(blob, BLOB_EXTERNAL_SNAPSHOT_ID,
|
||||
opts_local.esnap_id, opts_local.esnap_id_len, true);
|
||||
if (rc != 0) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
rc = blob_resize(blob, opts_local.num_clusters);
|
||||
if (rc < 0) {
|
||||
goto error;
|
||||
@ -8463,4 +8547,15 @@ spdk_bs_grow(struct spdk_bs_dev *dev, struct spdk_bs_opts *o,
|
||||
bs_grow_load_super_cpl, ctx);
|
||||
}
|
||||
|
||||
int
|
||||
spdk_blob_get_esnap_id(struct spdk_blob *blob, const void **id, size_t *len)
|
||||
{
|
||||
if (!blob_is_esnap_clone(blob)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return blob_get_xattr_value(blob, BLOB_EXTERNAL_SNAPSHOT_ID, id, len, true);
|
||||
}
|
||||
|
||||
SPDK_LOG_REGISTER_COMPONENT(blob)
|
||||
SPDK_LOG_REGISTER_COMPONENT(blob_esnap)
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause
|
||||
* Copyright (C) 2017 Intel Corporation.
|
||||
* All rights reserved.
|
||||
* Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef SPDK_BLOBSTORE_H
|
||||
@ -186,6 +186,8 @@ struct spdk_blob_store {
|
||||
TAILQ_HEAD(, spdk_blob_list) snapshots;
|
||||
|
||||
bool clean;
|
||||
|
||||
spdk_bs_esnap_dev_create esnap_bs_dev_create;
|
||||
};
|
||||
|
||||
struct spdk_bs_channel {
|
||||
@ -219,6 +221,7 @@ enum spdk_blob_op_type {
|
||||
#define BLOB_SNAPSHOT "SNAP"
|
||||
#define SNAPSHOT_IN_PROGRESS "SNAPTMP"
|
||||
#define SNAPSHOT_PENDING_REMOVAL "SNAPRM"
|
||||
#define BLOB_EXTERNAL_SNAPSHOT_ID "EXTSNAP"
|
||||
|
||||
struct spdk_blob_bs_dev {
|
||||
struct spdk_bs_dev bs_dev;
|
||||
@ -313,10 +316,12 @@ struct spdk_blob_md_descriptor_extent_page {
|
||||
uint32_t cluster_idx[0];
|
||||
};
|
||||
|
||||
#define SPDK_BLOB_THIN_PROV (1ULL << 0)
|
||||
#define SPDK_BLOB_INTERNAL_XATTR (1ULL << 1)
|
||||
#define SPDK_BLOB_EXTENT_TABLE (1ULL << 2)
|
||||
#define SPDK_BLOB_INVALID_FLAGS_MASK (SPDK_BLOB_THIN_PROV | SPDK_BLOB_INTERNAL_XATTR | SPDK_BLOB_EXTENT_TABLE)
|
||||
#define SPDK_BLOB_THIN_PROV (1ULL << 0)
|
||||
#define SPDK_BLOB_INTERNAL_XATTR (1ULL << 1)
|
||||
#define SPDK_BLOB_EXTENT_TABLE (1ULL << 2)
|
||||
#define SPDK_BLOB_EXTERNAL_SNAPSHOT (1ULL << 3)
|
||||
#define SPDK_BLOB_INVALID_FLAGS_MASK (SPDK_BLOB_THIN_PROV | SPDK_BLOB_INTERNAL_XATTR | \
|
||||
SPDK_BLOB_EXTENT_TABLE | SPDK_BLOB_EXTERNAL_SNAPSHOT)
|
||||
|
||||
#define SPDK_BLOB_READ_ONLY (1ULL << 0)
|
||||
#define SPDK_BLOB_DATA_RO_FLAGS_MASK SPDK_BLOB_READ_ONLY
|
||||
|
@ -29,6 +29,7 @@
|
||||
spdk_bs_create_clone;
|
||||
spdk_blob_get_clones;
|
||||
spdk_blob_get_parent_snapshot;
|
||||
spdk_blob_get_esnap_id;
|
||||
spdk_blob_is_read_only;
|
||||
spdk_blob_is_snapshot;
|
||||
spdk_blob_is_clone;
|
||||
|
@ -28,6 +28,7 @@ test/common/lib/test_sock
|
||||
test/common/lib/ut_multithread
|
||||
test/common/lib/test_rdma
|
||||
test/unit/lib/bdev/raid/common
|
||||
test/unit/lib/blob/blob.c/esnap_dev
|
||||
test/unit/lib/blob/bs_dev_common
|
||||
test/unit/lib/blob/bs_scheduler
|
||||
test/unit/lib/ftl/common/utils
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause
|
||||
* Copyright (C) 2017 Intel Corporation.
|
||||
* All rights reserved.
|
||||
* Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* Copyright (c) 2021-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "spdk/stdinc.h"
|
||||
@ -16,6 +16,7 @@
|
||||
#include "blob/request.c"
|
||||
#include "blob/zeroes.c"
|
||||
#include "blob/blob_bs_dev.c"
|
||||
#include "esnap_dev.c"
|
||||
|
||||
struct spdk_blob_store *g_bs;
|
||||
spdk_blob_id g_blobid;
|
||||
@ -7407,6 +7408,98 @@ blob_seek_io_unit(void)
|
||||
ut_blob_close_and_delete(bs, blob);
|
||||
}
|
||||
|
||||
static void
|
||||
blob_esnap_create(void)
|
||||
{
|
||||
struct spdk_blob_store *bs = g_bs;
|
||||
struct spdk_bs_opts bs_opts;
|
||||
struct ut_esnap_opts esnap_opts;
|
||||
struct spdk_blob_opts opts;
|
||||
struct spdk_blob *blob;
|
||||
uint32_t cluster_sz, block_sz;
|
||||
const uint32_t esnap_num_clusters = 4;
|
||||
uint64_t esnap_num_blocks;
|
||||
uint32_t sz;
|
||||
spdk_blob_id blobid;
|
||||
|
||||
cluster_sz = spdk_bs_get_cluster_size(bs);
|
||||
block_sz = spdk_bs_get_io_unit_size(bs);
|
||||
esnap_num_blocks = cluster_sz * esnap_num_clusters / block_sz;
|
||||
|
||||
/* Create a normal blob and verify it is not an esnap clone. */
|
||||
ut_spdk_blob_opts_init(&opts);
|
||||
blob = ut_blob_create_and_open(bs, &opts);
|
||||
CU_ASSERT(!blob_is_esnap_clone(blob));
|
||||
ut_blob_close_and_delete(bs, blob);
|
||||
|
||||
/* Create an esnap clone blob then verify it is an esnap clone and has the right size */
|
||||
ut_spdk_blob_opts_init(&opts);
|
||||
ut_esnap_opts_init(block_sz, esnap_num_blocks, __func__, &esnap_opts);
|
||||
opts.esnap_id = &esnap_opts;
|
||||
opts.esnap_id_len = sizeof(esnap_opts);
|
||||
opts.num_clusters = esnap_num_clusters;
|
||||
blob = ut_blob_create_and_open(bs, &opts);
|
||||
SPDK_CU_ASSERT_FATAL(blob_is_esnap_clone(blob));
|
||||
sz = spdk_blob_get_num_clusters(blob);
|
||||
CU_ASSERT(sz == esnap_num_clusters);
|
||||
ut_blob_close_and_delete(bs, blob);
|
||||
|
||||
/* Create an esnap clone without the size and verify it can be grown */
|
||||
ut_spdk_blob_opts_init(&opts);
|
||||
ut_esnap_opts_init(block_sz, esnap_num_blocks, __func__, &esnap_opts);
|
||||
opts.esnap_id = &esnap_opts;
|
||||
opts.esnap_id_len = sizeof(esnap_opts);
|
||||
blob = ut_blob_create_and_open(bs, &opts);
|
||||
SPDK_CU_ASSERT_FATAL(blob_is_esnap_clone(blob));
|
||||
sz = spdk_blob_get_num_clusters(blob);
|
||||
CU_ASSERT(sz == 0);
|
||||
spdk_blob_resize(blob, 1, blob_op_complete, NULL);
|
||||
poll_threads();
|
||||
CU_ASSERT(g_bserrno == 0);
|
||||
sz = spdk_blob_get_num_clusters(blob);
|
||||
CU_ASSERT(sz == 1);
|
||||
spdk_blob_resize(blob, esnap_num_clusters, blob_op_complete, NULL);
|
||||
poll_threads();
|
||||
CU_ASSERT(g_bserrno == 0);
|
||||
sz = spdk_blob_get_num_clusters(blob);
|
||||
CU_ASSERT(sz == esnap_num_clusters);
|
||||
spdk_blob_resize(blob, esnap_num_clusters + 1, blob_op_complete, NULL);
|
||||
poll_threads();
|
||||
CU_ASSERT(g_bserrno == 0);
|
||||
sz = spdk_blob_get_num_clusters(blob);
|
||||
CU_ASSERT(sz == esnap_num_clusters + 1);
|
||||
|
||||
/* Reload the blobstore and be sure that the blob can be opened. */
|
||||
blobid = spdk_blob_get_id(blob);
|
||||
spdk_blob_close(blob, blob_op_complete, NULL);
|
||||
poll_threads();
|
||||
CU_ASSERT(g_bserrno == 0);
|
||||
g_blob = NULL;
|
||||
spdk_bs_opts_init(&bs_opts, sizeof(bs_opts));
|
||||
bs_opts.esnap_bs_dev_create = ut_esnap_create;
|
||||
ut_bs_reload(&bs, &bs_opts);
|
||||
spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
|
||||
poll_threads();
|
||||
CU_ASSERT(g_bserrno == 0);
|
||||
CU_ASSERT(g_blob != NULL);
|
||||
blob = g_blob;
|
||||
SPDK_CU_ASSERT_FATAL(blob_is_esnap_clone(blob));
|
||||
sz = spdk_blob_get_num_clusters(blob);
|
||||
CU_ASSERT(sz == esnap_num_clusters + 1);
|
||||
|
||||
/* Reload the blobstore without esnap_bs_dev_create: should fail to open blob. */
|
||||
spdk_blob_close(blob, blob_op_complete, NULL);
|
||||
poll_threads();
|
||||
CU_ASSERT(g_bserrno == 0);
|
||||
g_blob = NULL;
|
||||
spdk_bs_opts_init(&bs_opts, sizeof(bs_opts));
|
||||
ut_bs_reload(&bs, &bs_opts);
|
||||
spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
|
||||
poll_threads();
|
||||
CU_ASSERT(g_bserrno != 0);
|
||||
CU_ASSERT(g_blob == NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
suite_bs_setup(void)
|
||||
{
|
||||
@ -7420,6 +7513,23 @@ suite_bs_setup(void)
|
||||
CU_ASSERT(g_bs != NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
suite_esnap_bs_setup(void)
|
||||
{
|
||||
struct spdk_bs_dev *dev;
|
||||
struct spdk_bs_opts bs_opts;
|
||||
|
||||
dev = init_dev();
|
||||
memset(g_dev_buffer, 0, DEV_BUFFER_SIZE);
|
||||
spdk_bs_opts_init(&bs_opts, sizeof(bs_opts));
|
||||
bs_opts.cluster_sz = 16 * 1024;
|
||||
bs_opts.esnap_bs_dev_create = ut_esnap_create;
|
||||
spdk_bs_init(dev, &bs_opts, bs_op_with_handle_complete, NULL);
|
||||
poll_threads();
|
||||
CU_ASSERT(g_bserrno == 0);
|
||||
SPDK_CU_ASSERT_FATAL(g_bs != NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
suite_bs_cleanup(void)
|
||||
{
|
||||
@ -7500,7 +7610,7 @@ suite_blob_cleanup(void)
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
CU_pSuite suite, suite_bs, suite_blob;
|
||||
CU_pSuite suite, suite_bs, suite_blob, suite_esnap_bs;
|
||||
unsigned int num_failures;
|
||||
|
||||
CU_set_error_action(CUEA_ABORT);
|
||||
@ -7511,6 +7621,9 @@ main(int argc, char **argv)
|
||||
suite_bs_setup, suite_bs_cleanup);
|
||||
suite_blob = CU_add_suite_with_setup_and_teardown("blob_blob", NULL, NULL,
|
||||
suite_blob_setup, suite_blob_cleanup);
|
||||
suite_esnap_bs = CU_add_suite_with_setup_and_teardown("blob_esnap_bs", NULL, NULL,
|
||||
suite_esnap_bs_setup,
|
||||
suite_bs_cleanup);
|
||||
|
||||
CU_ADD_TEST(suite, blob_init);
|
||||
CU_ADD_TEST(suite_bs, blob_open);
|
||||
@ -7583,6 +7696,7 @@ main(int argc, char **argv)
|
||||
CU_ADD_TEST(suite_bs, blob_persist_test);
|
||||
CU_ADD_TEST(suite_bs, blob_decouple_snapshot);
|
||||
CU_ADD_TEST(suite_bs, blob_seek_io_unit);
|
||||
CU_ADD_TEST(suite_esnap_bs, blob_esnap_create);
|
||||
|
||||
allocate_threads(2);
|
||||
set_thread(0);
|
||||
|
108
test/unit/lib/blob/blob.c/esnap_dev.c
Normal file
108
test/unit/lib/blob/blob.c/esnap_dev.c
Normal file
@ -0,0 +1,108 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause
|
||||
* Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
*/
|
||||
#include "spdk/stdinc.h"
|
||||
|
||||
#include "spdk_cunit.h"
|
||||
#include "spdk/blob.h"
|
||||
|
||||
/*
|
||||
* This creates a bs_dev that does not depend on a bdev. Typical use without assertions looks like:
|
||||
*
|
||||
* struct spdk_bs_dev *dev;
|
||||
* struct spdk_bs_opts bs_opts;
|
||||
* struct spdk_blob_opts blob_opts;
|
||||
* struct ut_snap_opts esnap_opts;
|
||||
*
|
||||
* Create the blobstore with external snapshot support.
|
||||
* dev = init_dev();
|
||||
* memset(g_dev_buffer, 0, DEV_BUFFER_SIZE);
|
||||
* spdk_bs_opts_init(&bs_opts, sizeof(bs_opts));
|
||||
* bs_opts.esnap_bs_dev_create = ut_esnap_create;
|
||||
*
|
||||
* Create an esnap clone blob.
|
||||
* ut_spdk_blob_opts_init(&blob_opts);
|
||||
* ut_esnap_opts_init(512, 2048, "name", &esnap_opts);
|
||||
* blob_opts.esnap_id = &esnap_opts;
|
||||
* blob_opts.esnap_id_len = sizeof(esnap_opts);
|
||||
* opts.num_clusters = 4;
|
||||
* blob = ut_blob_create_and_open(bs, &opts);
|
||||
*
|
||||
* At this point the blob can be used like any other blob.
|
||||
*/
|
||||
|
||||
#define UT_ESNAP_OPTS_MAGIC 0xbadf1ea5
|
||||
struct ut_esnap_opts {
|
||||
/*
|
||||
* This structure gets stored in an xattr. The magic number is used to give some assurance
|
||||
* that we got the right thing before trying to use the other fields.
|
||||
*/
|
||||
uint32_t magic;
|
||||
uint32_t block_size;
|
||||
uint64_t num_blocks;
|
||||
char name[32];
|
||||
};
|
||||
|
||||
struct ut_esnap_dev {
|
||||
struct spdk_bs_dev bs_dev;
|
||||
struct ut_esnap_opts ut_opts;
|
||||
spdk_blob_id blob_id;
|
||||
uint32_t num_channels;
|
||||
};
|
||||
|
||||
static void
|
||||
ut_esnap_opts_init(uint32_t block_size, uint32_t num_blocks, const char *name,
|
||||
struct ut_esnap_opts *opts)
|
||||
{
|
||||
memset(opts, 0, sizeof(*opts));
|
||||
opts->magic = UT_ESNAP_OPTS_MAGIC;
|
||||
opts->block_size = block_size;
|
||||
opts->num_blocks = num_blocks;
|
||||
spdk_strcpy_pad(opts->name, name, sizeof(opts->name) - 1, '\0');
|
||||
}
|
||||
|
||||
static void
|
||||
ut_esnap_destroy(struct spdk_bs_dev *bs_dev)
|
||||
{
|
||||
free(bs_dev);
|
||||
}
|
||||
|
||||
static struct spdk_bs_dev *
|
||||
ut_esnap_dev_alloc(const struct ut_esnap_opts *opts)
|
||||
{
|
||||
struct ut_esnap_dev *ut_dev;
|
||||
struct spdk_bs_dev *bs_dev;
|
||||
|
||||
assert(opts->magic == UT_ESNAP_OPTS_MAGIC);
|
||||
|
||||
ut_dev = calloc(1, sizeof(*ut_dev));
|
||||
if (ut_dev == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ut_dev->ut_opts = *opts;
|
||||
bs_dev = &ut_dev->bs_dev;
|
||||
|
||||
bs_dev->blocklen = opts->block_size;
|
||||
bs_dev->blockcnt = opts->num_blocks;
|
||||
|
||||
bs_dev->destroy = ut_esnap_destroy;
|
||||
|
||||
return bs_dev;
|
||||
}
|
||||
|
||||
static int
|
||||
ut_esnap_create(struct spdk_blob *blob, const void *id, uint32_t id_len,
|
||||
struct spdk_bs_dev **bs_devp)
|
||||
{
|
||||
struct spdk_bs_dev *bs_dev = NULL;
|
||||
|
||||
SPDK_CU_ASSERT_FATAL(id != NULL);
|
||||
SPDK_CU_ASSERT_FATAL(sizeof(struct ut_esnap_opts) == id_len);
|
||||
|
||||
bs_dev = ut_esnap_dev_alloc(id);
|
||||
SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
|
||||
|
||||
*bs_devp = bs_dev;
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user