blob: add IO channels for esnap clones

The channel passed to blob IO operations is useful for tracking
operations within the blobstore and the bs_dev that the blobstore
resides on. Esnap clone blobs perform reads from other bs_devs and
require per-thread, per-bs_dev channels.

This commit augments struct spdk_bs_channel with a tree containing
channels for the external snapshot bs_devs. The tree is indexed by blob
ID. These "esnap channels" are lazily created on the first read from an
external snapshot via each bs_channel. They are removed as bs_channels
are destroyed and blobs are closed.

Change-Id: I97aebe5a2f3584bfbf3a10ede8f3128448d30d6e
Signed-off-by: Mike Gerdts <mgerdts@nvidia.com>
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/14974
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Community-CI: Mellanox Build Bot
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
This commit is contained in:
Mike Gerdts 2022-09-23 06:48:29 -05:00 committed by Jim Harris
parent d0516312ff
commit b47cee6c96
7 changed files with 886 additions and 28 deletions

View File

@ -129,6 +129,11 @@ Channels are an SPDK-wide abstraction and with Blobstore the best way to think a
required in order to do IO. The application will perform IO to the channel and channels are best thought of as being required in order to do IO. The application will perform IO to the channel and channels are best thought of as being
associated 1:1 with a thread. associated 1:1 with a thread.
With external snapshots (see @ref blob_pg_esnap_and_esnap_clone), a read from a blob may lead to
reading from the device containing the blobstore or an external snapshot device. To support this,
each blobstore IO channel maintains a tree of channels to be used when reading from external
snapshot devices.
### Blob Identifiers ### Blob Identifiers
When an application creates a blob, it does not provide a name as is the case with many other similar When an application creates a blob, it does not provide a name as is the case with many other similar
@ -465,6 +470,13 @@ of IO. They are an internal construct only and are pre-allocated on a per channe
earlier). They are removed from a channel associated linked list when the set (sequence or batch) is started and earlier). They are removed from a channel associated linked list when the set (sequence or batch) is started and
then returned to the list when completed. then returned to the list when completed.
Each request set maintains a reference to a `channel` and a `back_channel`. The `channel` is used
for performing IO on the blobstore device. The `back_channel` is used for performing IO on the
blob's back device, `blob->back_bs_dev`. For blobs that are not esnap clones, `channel` and
`back_channel` reference an IO channel used with the device that contains the blobstore. For blobs
that are esnap clones, `channel` is the same as with any other blob and `back_channel` is an IO
channel for the external snapshot device.
### Key Internal Structures ### Key Internal Structures
`blobstore.h` contains many of the key structures for the internal workings of Blobstore. Only a few notable ones `blobstore.h` contains many of the key structures for the internal workings of Blobstore. Only a few notable ones

View File

@ -40,6 +40,24 @@ 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, 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); struct spdk_blob_md_page *page, spdk_blob_op_complete cb_fn, void *cb_arg);
/*
* External snapshots require a channel per thread per esnap bdev. The tree
* is populated lazily as blob IOs are handled by the back_bs_dev. When this
* channel is destroyed, all the channels in the tree are destroyed.
*/
struct blob_esnap_channel {
RB_ENTRY(blob_esnap_channel) node;
spdk_blob_id blob_id;
struct spdk_io_channel *channel;
};
static int blob_esnap_channel_compare(struct blob_esnap_channel *c1, struct blob_esnap_channel *c2);
static void blob_esnap_destroy_bs_dev_channels(struct spdk_blob *blob,
spdk_blob_op_with_handle_complete cb_fn, void *cb_arg);
static void blob_esnap_destroy_bs_channel(struct spdk_bs_channel *ch);
RB_GENERATE_STATIC(blob_esnap_channel_tree, blob_esnap_channel, node, blob_esnap_channel_compare)
static inline bool static inline bool
blob_is_esnap_clone(const struct spdk_blob *blob) blob_is_esnap_clone(const struct spdk_blob *blob)
{ {
@ -339,10 +357,33 @@ blob_free(struct spdk_blob *blob)
free(blob); free(blob);
} }
static void
blob_back_bs_destroy_esnap_done(void *ctx, struct spdk_blob *blob, int bserrno)
{
struct spdk_bs_dev *bs_dev = ctx;
if (bserrno != 0) {
/*
* This is probably due to a memory allocation failure when creating the
* blob_esnap_destroy_ctx before iterating threads.
*/
SPDK_ERRLOG("blob 0x%" PRIx64 ": Unable to destroy bs dev channels: error %d\n",
blob->id, bserrno);
assert(false);
}
SPDK_DEBUGLOG(blob_esnap, "blob 0x%" PRIx64 ": calling destroy on back_bs_dev\n", blob->id);
bs_dev->destroy(bs_dev);
}
static void static void
blob_back_bs_destroy(struct spdk_blob *blob) blob_back_bs_destroy(struct spdk_blob *blob)
{ {
blob->back_bs_dev->destroy(blob->back_bs_dev); SPDK_DEBUGLOG(blob_esnap, "blob 0x%" PRIx64 ": preparing to destroy back_bs_dev\n",
blob->id);
blob_esnap_destroy_bs_dev_channels(blob, blob_back_bs_destroy_esnap_done,
blob->back_bs_dev);
blob->back_bs_dev = NULL; blob->back_bs_dev = NULL;
} }
@ -2526,7 +2567,7 @@ blob_can_copy(struct spdk_blob *blob, uint32_t cluster_start_page, uint64_t *bas
{ {
uint64_t lba = bs_dev_page_to_lba(blob->back_bs_dev, cluster_start_page); uint64_t lba = bs_dev_page_to_lba(blob->back_bs_dev, cluster_start_page);
return (blob->bs->dev->copy != NULL) && return (!blob_is_esnap_clone(blob) && blob->bs->dev->copy != NULL) &&
blob->back_bs_dev->translate_lba(blob->back_bs_dev, lba, base_lba); blob->back_bs_dev->translate_lba(blob->back_bs_dev, lba, base_lba);
} }
@ -2862,7 +2903,7 @@ blob_request_submit_op_single(struct spdk_io_channel *_ch, struct spdk_blob *blo
case SPDK_BLOB_READ: { case SPDK_BLOB_READ: {
spdk_bs_batch_t *batch; spdk_bs_batch_t *batch;
batch = bs_batch_open(_ch, &cpl); batch = bs_batch_open(_ch, &cpl, blob);
if (!batch) { if (!batch) {
cb_fn(cb_arg, -ENOMEM); cb_fn(cb_arg, -ENOMEM);
return; return;
@ -2890,7 +2931,7 @@ blob_request_submit_op_single(struct spdk_io_channel *_ch, struct spdk_blob *blo
return; return;
} }
batch = bs_batch_open(_ch, &cpl); batch = bs_batch_open(_ch, &cpl, blob);
if (!batch) { if (!batch) {
cb_fn(cb_arg, -ENOMEM); cb_fn(cb_arg, -ENOMEM);
return; return;
@ -2920,7 +2961,7 @@ blob_request_submit_op_single(struct spdk_io_channel *_ch, struct spdk_blob *blo
case SPDK_BLOB_UNMAP: { case SPDK_BLOB_UNMAP: {
spdk_bs_batch_t *batch; spdk_bs_batch_t *batch;
batch = bs_batch_open(_ch, &cpl); batch = bs_batch_open(_ch, &cpl, blob);
if (!batch) { if (!batch) {
cb_fn(cb_arg, -ENOMEM); cb_fn(cb_arg, -ENOMEM);
return; return;
@ -3287,6 +3328,7 @@ bs_channel_create(void *io_device, void *ctx_buf)
TAILQ_INIT(&channel->need_cluster_alloc); TAILQ_INIT(&channel->need_cluster_alloc);
TAILQ_INIT(&channel->queued_io); TAILQ_INIT(&channel->queued_io);
RB_INIT(&channel->esnap_channels);
return 0; return 0;
} }
@ -3309,6 +3351,8 @@ bs_channel_destroy(void *io_device, void *ctx_buf)
bs_user_op_abort(op, -EIO); bs_user_op_abort(op, -EIO);
} }
blob_esnap_destroy_bs_channel(channel);
free(channel->req_mem); free(channel->req_mem);
spdk_free(channel->new_cluster_page); spdk_free(channel->new_cluster_page);
channel->dev->destroy_channel(channel->dev, channel->dev_channel); channel->dev->destroy_channel(channel->dev, channel->dev_channel);
@ -7788,6 +7832,24 @@ blob_close_cpl(spdk_bs_sequence_t *seq, void *cb_arg, int bserrno)
bs_sequence_finish(seq, bserrno); bs_sequence_finish(seq, bserrno);
} }
static void
blob_close_esnap_done(void *cb_arg, struct spdk_blob *blob, int bserrno)
{
spdk_bs_sequence_t *seq = cb_arg;
if (bserrno != 0) {
SPDK_DEBUGLOG(blob_esnap, "blob 0x%" PRIx64 ": close failed with error %d\n",
blob->id, bserrno);
bs_sequence_finish(seq, bserrno);
return;
}
SPDK_DEBUGLOG(blob_esnap, "blob %" PRIx64 ": closed, syncing metadata\n", blob->id);
/* Sync metadata */
blob_persist(seq, blob, blob_close_cpl, blob);
}
void void
spdk_blob_close(struct spdk_blob *blob, spdk_blob_op_complete cb_fn, void *cb_arg) spdk_blob_close(struct spdk_blob *blob, spdk_blob_op_complete cb_fn, void *cb_arg)
{ {
@ -7813,6 +7875,11 @@ spdk_blob_close(struct spdk_blob *blob, spdk_blob_op_complete cb_fn, void *cb_ar
return; return;
} }
if (blob->open_ref == 1 && blob_is_esnap_clone(blob)) {
blob_esnap_destroy_bs_dev_channels(blob, blob_close_esnap_done, seq);
return;
}
/* Sync metadata */ /* Sync metadata */
blob_persist(seq, blob, blob_close_cpl, blob); blob_persist(seq, blob, blob_close_cpl, blob);
} }
@ -7827,6 +7894,7 @@ struct spdk_io_channel *spdk_bs_alloc_io_channel(struct spdk_blob_store *bs)
void void
spdk_bs_free_io_channel(struct spdk_io_channel *channel) spdk_bs_free_io_channel(struct spdk_io_channel *channel)
{ {
blob_esnap_destroy_bs_channel(spdk_io_channel_get_ctx(channel));
spdk_put_io_channel(channel); spdk_put_io_channel(channel);
} }
@ -8574,5 +8642,164 @@ spdk_blob_get_esnap_id(struct spdk_blob *blob, const void **id, size_t *len)
return blob_get_xattr_value(blob, BLOB_EXTERNAL_SNAPSHOT_ID, id, len, true); return blob_get_xattr_value(blob, BLOB_EXTERNAL_SNAPSHOT_ID, id, len, true);
} }
struct spdk_io_channel *
blob_esnap_get_io_channel(struct spdk_io_channel *ch, struct spdk_blob *blob)
{
struct spdk_bs_channel *bs_channel = spdk_io_channel_get_ctx(ch);
struct spdk_bs_dev *bs_dev = blob->back_bs_dev;
struct blob_esnap_channel find = {};
struct blob_esnap_channel *esnap_channel, *existing;
find.blob_id = blob->id;
esnap_channel = RB_FIND(blob_esnap_channel_tree, &bs_channel->esnap_channels, &find);
if (spdk_likely(esnap_channel != NULL)) {
SPDK_DEBUGLOG(blob_esnap, "blob 0x%" PRIx64 ": using cached channel on thread %s\n",
blob->id, spdk_thread_get_name(spdk_get_thread()));
return esnap_channel->channel;
}
SPDK_DEBUGLOG(blob_esnap, "blob 0x%" PRIx64 ": allocating channel on thread %s\n",
blob->id, spdk_thread_get_name(spdk_get_thread()));
esnap_channel = calloc(1, sizeof(*esnap_channel));
if (esnap_channel == NULL) {
SPDK_NOTICELOG("blob 0x%" PRIx64 " channel allocation failed: no memory\n",
find.blob_id);
return NULL;
}
esnap_channel->channel = bs_dev->create_channel(bs_dev);
if (esnap_channel->channel == NULL) {
SPDK_NOTICELOG("blob 0x%" PRIx64 " back channel allocation failed\n", blob->id);
free(esnap_channel);
return NULL;
}
esnap_channel->blob_id = find.blob_id;
existing = RB_INSERT(blob_esnap_channel_tree, &bs_channel->esnap_channels, esnap_channel);
if (spdk_unlikely(existing != NULL)) {
/*
* This should be unreachable: all modifications to this tree happen on this thread.
*/
SPDK_ERRLOG("blob 0x%" PRIx64 "lost race to allocate a channel\n", find.blob_id);
assert(false);
bs_dev->destroy_channel(bs_dev, esnap_channel->channel);
free(esnap_channel);
return existing->channel;
}
return esnap_channel->channel;
}
static int
blob_esnap_channel_compare(struct blob_esnap_channel *c1, struct blob_esnap_channel *c2)
{
return (c1->blob_id < c2->blob_id ? -1 : c1->blob_id > c2->blob_id);
}
struct blob_esnap_destroy_ctx {
spdk_blob_op_with_handle_complete cb_fn;
void *cb_arg;
struct spdk_blob *blob;
struct spdk_bs_dev *back_bs_dev;
};
static void
blob_esnap_destroy_channels_done(struct spdk_io_channel_iter *i, int status)
{
struct blob_esnap_destroy_ctx *ctx = spdk_io_channel_iter_get_ctx(i);
struct spdk_blob *blob = ctx->blob;
SPDK_DEBUGLOG(blob_esnap, "blob 0x%" PRIx64 ": done destroying channels for this blob\n",
blob->id);
ctx->cb_fn(ctx->cb_arg, blob, status);
free(ctx);
}
static void
blob_esnap_destroy_one_channel(struct spdk_io_channel_iter *i)
{
struct blob_esnap_destroy_ctx *ctx = spdk_io_channel_iter_get_ctx(i);
struct spdk_blob *blob = ctx->blob;
struct spdk_bs_dev *bs_dev = ctx->back_bs_dev;
struct spdk_io_channel *channel = spdk_io_channel_iter_get_channel(i);
struct spdk_bs_channel *bs_channel = spdk_io_channel_get_ctx(channel);
struct blob_esnap_channel *esnap_channel;
struct blob_esnap_channel find = {};
assert(spdk_get_thread() == spdk_io_channel_get_thread(channel));
find.blob_id = blob->id;
esnap_channel = RB_FIND(blob_esnap_channel_tree, &bs_channel->esnap_channels, &find);
if (esnap_channel != NULL) {
SPDK_DEBUGLOG(blob_esnap, "blob 0x%" PRIx64 ": destroying channel on thread %s\n",
blob->id, spdk_thread_get_name(spdk_get_thread()));
RB_REMOVE(blob_esnap_channel_tree, &bs_channel->esnap_channels, esnap_channel);
bs_dev->destroy_channel(bs_dev, esnap_channel->channel);
free(esnap_channel);
}
spdk_for_each_channel_continue(i, 0);
}
/*
* Destroy the channels for a specific blob on each thread with a blobstore channel. This should be
* used when closing an esnap clone blob and after decoupling from the parent.
*/
static void
blob_esnap_destroy_bs_dev_channels(struct spdk_blob *blob, spdk_blob_op_with_handle_complete cb_fn,
void *cb_arg)
{
struct blob_esnap_destroy_ctx *ctx;
if (!blob_is_esnap_clone(blob)) {
cb_fn(cb_arg, blob, 0);
return;
}
ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL) {
cb_fn(cb_arg, blob, -ENOMEM);
return;
}
ctx->cb_fn = cb_fn;
ctx->cb_arg = cb_arg;
ctx->blob = blob;
ctx->back_bs_dev = blob->back_bs_dev;
SPDK_DEBUGLOG(blob_esnap, "blob 0x%" PRIx64 ": destroying channels for this blob\n",
blob->id);
spdk_for_each_channel(blob->bs, blob_esnap_destroy_one_channel, ctx,
blob_esnap_destroy_channels_done);
}
/*
* Destroy all bs_dev channels on a specific blobstore channel. This should be used when a
* bs_channel is destroyed.
*/
static void
blob_esnap_destroy_bs_channel(struct spdk_bs_channel *ch)
{
struct blob_esnap_channel *esnap_channel, *esnap_channel_tmp;
assert(spdk_get_thread() == spdk_io_channel_get_thread(spdk_io_channel_from_ctx(ch)));
SPDK_DEBUGLOG(blob_esnap, "destroying channels on thread %s\n",
spdk_thread_get_name(spdk_get_thread()));
RB_FOREACH_SAFE(esnap_channel, blob_esnap_channel_tree, &ch->esnap_channels,
esnap_channel_tmp) {
SPDK_DEBUGLOG(blob_esnap, "blob 0x%" PRIx64
": destroying one channel in thread %s\n",
esnap_channel->blob_id, spdk_thread_get_name(spdk_get_thread()));
RB_REMOVE(blob_esnap_channel_tree, &ch->esnap_channels, esnap_channel);
spdk_put_io_channel(esnap_channel->channel);
free(esnap_channel);
}
SPDK_DEBUGLOG(blob_esnap, "done destroying channels on thread %s\n",
spdk_thread_get_name(spdk_get_thread()));
}
SPDK_LOG_REGISTER_COMPONENT(blob) SPDK_LOG_REGISTER_COMPONENT(blob)
SPDK_LOG_REGISTER_COMPONENT(blob_esnap) SPDK_LOG_REGISTER_COMPONENT(blob_esnap)

View File

@ -205,6 +205,8 @@ struct spdk_bs_channel {
TAILQ_HEAD(, spdk_bs_request_set) need_cluster_alloc; TAILQ_HEAD(, spdk_bs_request_set) need_cluster_alloc;
TAILQ_HEAD(, spdk_bs_request_set) queued_io; TAILQ_HEAD(, spdk_bs_request_set) queued_io;
RB_HEAD(blob_esnap_channel_tree, blob_esnap_channel) esnap_channels;
}; };
/** operation type */ /** operation type */
@ -420,6 +422,8 @@ SPDK_STATIC_ASSERT(sizeof(struct spdk_bs_super_block) == 0x1000, "Invalid super
struct spdk_bs_dev *bs_create_zeroes_dev(void); struct spdk_bs_dev *bs_create_zeroes_dev(void);
struct spdk_bs_dev *bs_create_blob_bs_dev(struct spdk_blob *blob); struct spdk_bs_dev *bs_create_blob_bs_dev(struct spdk_blob *blob);
struct spdk_io_channel *blob_esnap_get_io_channel(struct spdk_io_channel *ch,
struct spdk_blob *blob);
/* Unit Conversions /* Unit Conversions
* *

View File

@ -72,9 +72,9 @@ bs_sequence_completion(struct spdk_io_channel *channel, void *cb_arg, int bserrn
set->u.sequence.cb_fn((spdk_bs_sequence_t *)set, set->u.sequence.cb_arg, bserrno); set->u.sequence.cb_fn((spdk_bs_sequence_t *)set, set->u.sequence.cb_arg, bserrno);
} }
static spdk_bs_sequence_t * static inline spdk_bs_sequence_t *
bs_sequence_start(struct spdk_io_channel *_channel, bs_sequence_start(struct spdk_io_channel *_channel, struct spdk_bs_cpl *cpl,
struct spdk_bs_cpl *cpl) struct spdk_io_channel *back_channel)
{ {
struct spdk_bs_channel *channel; struct spdk_bs_channel *channel;
struct spdk_bs_request_set *set; struct spdk_bs_request_set *set;
@ -90,7 +90,7 @@ bs_sequence_start(struct spdk_io_channel *_channel,
set->cpl = *cpl; set->cpl = *cpl;
set->bserrno = 0; set->bserrno = 0;
set->channel = channel; set->channel = channel;
set->back_channel = _channel; set->back_channel = back_channel;
set->cb_args.cb_fn = bs_sequence_completion; set->cb_args.cb_fn = bs_sequence_completion;
set->cb_args.cb_arg = set; set->cb_args.cb_arg = set;
@ -104,7 +104,7 @@ bs_sequence_start(struct spdk_io_channel *_channel,
spdk_bs_sequence_t * spdk_bs_sequence_t *
bs_sequence_start_bs(struct spdk_io_channel *_channel, struct spdk_bs_cpl *cpl) bs_sequence_start_bs(struct spdk_io_channel *_channel, struct spdk_bs_cpl *cpl)
{ {
return bs_sequence_start(_channel, cpl); return bs_sequence_start(_channel, cpl, _channel);
} }
/* Use when performing IO on a blob. */ /* Use when performing IO on a blob. */
@ -112,7 +112,24 @@ spdk_bs_sequence_t *
bs_sequence_start_blob(struct spdk_io_channel *_channel, struct spdk_bs_cpl *cpl, bs_sequence_start_blob(struct spdk_io_channel *_channel, struct spdk_bs_cpl *cpl,
struct spdk_blob *blob) struct spdk_blob *blob)
{ {
return bs_sequence_start(_channel, cpl); struct spdk_io_channel *esnap_ch = _channel;
if (spdk_blob_is_esnap_clone(blob)) {
esnap_ch = blob_esnap_get_io_channel(_channel, blob);
if (esnap_ch == NULL) {
/*
* The most likely reason we are here is because of some logic error
* elsewhere that caused channel allocations to fail. We could get here due
* to being out of memory as well. If we are out of memory, the process is
* this will be just one of many problems that this process will be having.
* Killing it off debug builds now due to logic errors is the right thing to
* do and killing it off due to ENOMEM is no big loss.
*/
assert(false);
return NULL;
}
}
return bs_sequence_start(_channel, cpl, esnap_ch);
} }
void void
@ -308,11 +325,18 @@ bs_batch_completion(struct spdk_io_channel *_channel,
} }
spdk_bs_batch_t * spdk_bs_batch_t *
bs_batch_open(struct spdk_io_channel *_channel, bs_batch_open(struct spdk_io_channel *_channel, struct spdk_bs_cpl *cpl, struct spdk_blob *blob)
struct spdk_bs_cpl *cpl)
{ {
struct spdk_bs_channel *channel; struct spdk_bs_channel *channel;
struct spdk_bs_request_set *set; struct spdk_bs_request_set *set;
struct spdk_io_channel *back_channel = _channel;
if (spdk_blob_is_esnap_clone(blob)) {
back_channel = blob_esnap_get_io_channel(_channel, blob);
if (back_channel == NULL) {
return NULL;
}
}
channel = spdk_io_channel_get_ctx(_channel); channel = spdk_io_channel_get_ctx(_channel);
assert(channel != NULL); assert(channel != NULL);
@ -325,7 +349,7 @@ bs_batch_open(struct spdk_io_channel *_channel,
set->cpl = *cpl; set->cpl = *cpl;
set->bserrno = 0; set->bserrno = 0;
set->channel = channel; set->channel = channel;
set->back_channel = _channel; set->back_channel = back_channel;
set->u.batch.cb_fn = NULL; set->u.batch.cb_fn = NULL;
set->u.batch.cb_arg = NULL; set->u.batch.cb_arg = NULL;

View File

@ -91,8 +91,8 @@ struct spdk_bs_request_set {
*/ */
struct spdk_bs_channel *channel; struct spdk_bs_channel *channel;
/* /*
* The channel used by the blobstore to perform IO on back_bs_dev. * The channel used by the blobstore to perform IO on back_bs_dev. Unless the blob
* For now, back_channel == spdk_io_channel_get_ctx(set->channel). * is an esnap clone, back_channel == spdk_io_channel_get_ctx(set->channel).
*/ */
struct spdk_io_channel *back_channel; struct spdk_io_channel *back_channel;
@ -135,6 +135,9 @@ spdk_bs_sequence_t *bs_sequence_start_bs(struct spdk_io_channel *channel,
spdk_bs_sequence_t *bs_sequence_start_blob(struct spdk_io_channel *channel, spdk_bs_sequence_t *bs_sequence_start_blob(struct spdk_io_channel *channel,
struct spdk_bs_cpl *cpl, struct spdk_blob *blob); struct spdk_bs_cpl *cpl, struct spdk_blob *blob);
spdk_bs_sequence_t *bs_sequence_start_esnap(struct spdk_io_channel *channel,
struct spdk_bs_cpl *cpl, struct spdk_blob *blob);
void bs_sequence_read_bs_dev(spdk_bs_sequence_t *seq, struct spdk_bs_dev *bs_dev, void bs_sequence_read_bs_dev(spdk_bs_sequence_t *seq, struct spdk_bs_dev *bs_dev,
void *payload, uint64_t lba, uint32_t lba_count, void *payload, uint64_t lba, uint32_t lba_count,
spdk_bs_sequence_cpl cb_fn, void *cb_arg); spdk_bs_sequence_cpl cb_fn, void *cb_arg);
@ -172,7 +175,7 @@ void bs_sequence_finish(spdk_bs_sequence_t *seq, int bserrno);
void bs_user_op_sequence_finish(void *cb_arg, int bserrno); void bs_user_op_sequence_finish(void *cb_arg, int bserrno);
spdk_bs_batch_t *bs_batch_open(struct spdk_io_channel *channel, spdk_bs_batch_t *bs_batch_open(struct spdk_io_channel *channel,
struct spdk_bs_cpl *cpl); struct spdk_bs_cpl *cpl, struct spdk_blob *blob);
void bs_batch_read_bs_dev(spdk_bs_batch_t *batch, struct spdk_bs_dev *bs_dev, void bs_batch_read_bs_dev(spdk_bs_batch_t *batch, struct spdk_bs_dev *bs_dev,
void *payload, uint64_t lba, uint32_t lba_count); void *payload, uint64_t lba, uint32_t lba_count);

View File

@ -12,6 +12,7 @@
#include "common/lib/ut_multithread.c" #include "common/lib/ut_multithread.c"
#include "../bs_dev_common.c" #include "../bs_dev_common.c"
#include "thread/thread.c"
#include "blob/blobstore.c" #include "blob/blobstore.c"
#include "blob/request.c" #include "blob/request.c"
#include "blob/zeroes.c" #include "blob/zeroes.c"
@ -7436,19 +7437,21 @@ blob_esnap_create(void)
/* Create an esnap clone blob then verify it is an esnap clone and has the right size */ /* Create an esnap clone blob then verify it is an esnap clone and has the right size */
ut_spdk_blob_opts_init(&opts); ut_spdk_blob_opts_init(&opts);
ut_esnap_opts_init(block_sz, esnap_num_blocks, __func__, &esnap_opts); ut_esnap_opts_init(block_sz, esnap_num_blocks, __func__, NULL, &esnap_opts);
opts.esnap_id = &esnap_opts; opts.esnap_id = &esnap_opts;
opts.esnap_id_len = sizeof(esnap_opts); opts.esnap_id_len = sizeof(esnap_opts);
opts.num_clusters = esnap_num_clusters; opts.num_clusters = esnap_num_clusters;
blob = ut_blob_create_and_open(bs, &opts); blob = ut_blob_create_and_open(bs, &opts);
SPDK_CU_ASSERT_FATAL(blob != NULL);
SPDK_CU_ASSERT_FATAL(spdk_blob_is_esnap_clone(blob)); SPDK_CU_ASSERT_FATAL(spdk_blob_is_esnap_clone(blob));
SPDK_CU_ASSERT_FATAL(blob_is_esnap_clone(blob));
sz = spdk_blob_get_num_clusters(blob); sz = spdk_blob_get_num_clusters(blob);
CU_ASSERT(sz == esnap_num_clusters); CU_ASSERT(sz == esnap_num_clusters);
ut_blob_close_and_delete(bs, blob); ut_blob_close_and_delete(bs, blob);
/* Create an esnap clone without the size and verify it can be grown */ /* Create an esnap clone without the size and verify it can be grown */
ut_spdk_blob_opts_init(&opts); ut_spdk_blob_opts_init(&opts);
ut_esnap_opts_init(block_sz, esnap_num_blocks, __func__, &esnap_opts); ut_esnap_opts_init(block_sz, esnap_num_blocks, __func__, NULL, &esnap_opts);
opts.esnap_id = &esnap_opts; opts.esnap_id = &esnap_opts;
opts.esnap_id_len = sizeof(esnap_opts); opts.esnap_id_len = sizeof(esnap_opts);
blob = ut_blob_create_and_open(bs, &opts); blob = ut_blob_create_and_open(bs, &opts);
@ -7538,6 +7541,346 @@ blob_esnap_create(void)
g_blob = NULL; g_blob = NULL;
} }
static bool
blob_esnap_verify_contents(struct spdk_blob *blob, struct spdk_io_channel *ch,
uint64_t offset, uint64_t size, uint32_t readsize, const char *how)
{
const uint32_t bs_blksz = blob->bs->io_unit_size;
const uint32_t esnap_blksz = blob->back_bs_dev->blocklen;
const uint32_t start_blk = offset / bs_blksz;
const uint32_t num_blocks = spdk_max(size, readsize) / bs_blksz;
const uint32_t blocks_per_read = spdk_min(size, readsize) / bs_blksz;
uint32_t blob_block;
struct iovec iov;
uint8_t buf[spdk_min(size, readsize)];
bool block_ok;
SPDK_CU_ASSERT_FATAL(offset % bs_blksz == 0);
SPDK_CU_ASSERT_FATAL(size % bs_blksz == 0);
SPDK_CU_ASSERT_FATAL(readsize % bs_blksz == 0);
memset(buf, 0, readsize);
iov.iov_base = buf;
iov.iov_len = readsize;
for (blob_block = start_blk; blob_block < num_blocks; blob_block += blocks_per_read) {
if (strcmp(how, "read") == 0) {
spdk_blob_io_read(blob, ch, buf, blob_block, blocks_per_read,
bs_op_complete, NULL);
} else if (strcmp(how, "readv") == 0) {
spdk_blob_io_readv(blob, ch, &iov, 1, blob_block, blocks_per_read,
bs_op_complete, NULL);
} else if (strcmp(how, "readv_ext") == 0) {
/*
* This is currently pointless. NULL ext_opts leads to dev->readv(), not
* dev->readv_ext().
*/
spdk_blob_io_readv_ext(blob, ch, &iov, 1, blob_block, blocks_per_read,
bs_op_complete, NULL, NULL);
} else {
abort();
}
poll_threads();
CU_ASSERT(g_bserrno == 0);
if (g_bserrno != 0) {
return false;
}
block_ok = ut_esnap_content_is_correct(buf, blocks_per_read * bs_blksz, blob->id,
blob_block * bs_blksz, esnap_blksz);
CU_ASSERT(block_ok);
if (!block_ok) {
return false;
}
}
return true;
}
static void
blob_esnap_io_size(uint32_t bs_blksz, uint32_t esnap_blksz)
{
struct spdk_bs_dev *dev;
struct spdk_blob_store *bs;
struct spdk_bs_opts bsopts;
struct spdk_blob_opts opts;
struct ut_esnap_opts esnap_opts;
struct spdk_blob *blob;
const uint32_t cluster_sz = 16 * 1024;
const uint64_t esnap_num_clusters = 4;
const uint32_t esnap_sz = cluster_sz * esnap_num_clusters;
const uint64_t esnap_num_blocks = esnap_sz / esnap_blksz;
const uint64_t blob_num_blocks = esnap_sz / bs_blksz;
uint32_t block;
struct spdk_io_channel *bs_ch;
spdk_bs_opts_init(&bsopts, sizeof(bsopts));
bsopts.cluster_sz = cluster_sz;
bsopts.esnap_bs_dev_create = ut_esnap_create;
/* Create device with desired block size */
dev = init_dev();
dev->blocklen = bs_blksz;
dev->blockcnt = DEV_BUFFER_SIZE / dev->blocklen;
/* Initialize a new blob store */
spdk_bs_init(dev, &bsopts, bs_op_with_handle_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
SPDK_CU_ASSERT_FATAL(g_bs != NULL);
SPDK_CU_ASSERT_FATAL(g_bs->io_unit_size == bs_blksz);
bs = g_bs;
bs_ch = spdk_bs_alloc_io_channel(bs);
SPDK_CU_ASSERT_FATAL(bs_ch != NULL);
/* Create and open the esnap clone */
ut_spdk_blob_opts_init(&opts);
ut_esnap_opts_init(esnap_blksz, esnap_num_blocks, __func__, NULL, &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 != NULL);
/* Verify that large reads return the content of the esnap device */
CU_ASSERT(blob_esnap_verify_contents(blob, bs_ch, 0, esnap_sz, esnap_sz, "read"));
CU_ASSERT(blob_esnap_verify_contents(blob, bs_ch, 0, esnap_sz, esnap_sz, "readv"));
CU_ASSERT(blob_esnap_verify_contents(blob, bs_ch, 0, esnap_sz, esnap_sz, "readv_ext"));
/* Verify that small reads return the content of the esnap device */
CU_ASSERT(blob_esnap_verify_contents(blob, bs_ch, 0, esnap_sz, bs_blksz, "read"));
CU_ASSERT(blob_esnap_verify_contents(blob, bs_ch, 0, esnap_sz, bs_blksz, "readv"));
CU_ASSERT(blob_esnap_verify_contents(blob, bs_ch, 0, esnap_sz, bs_blksz, "readv_ext"));
/* Write one blob block at a time; verify that the surrounding blocks are OK */
for (block = 0; block < blob_num_blocks; block++) {
char buf[bs_blksz];
union ut_word word;
word.f.blob_id = 0xfedcba90;
word.f.lba = block;
ut_memset8(buf, word.num, bs_blksz);
spdk_blob_io_write(blob, bs_ch, buf, block, 1, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
if (g_bserrno != 0) {
break;
}
/* Read and verify the block before the current block */
if (block != 0) {
spdk_blob_io_read(blob, bs_ch, buf, block - 1, 1, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
if (g_bserrno != 0) {
break;
}
CU_ASSERT(ut_esnap_content_is_correct(buf, bs_blksz, word.f.blob_id,
(block - 1) * bs_blksz, bs_blksz));
}
/* Read and verify the current block */
spdk_blob_io_read(blob, bs_ch, buf, block, 1, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
if (g_bserrno != 0) {
break;
}
CU_ASSERT(ut_esnap_content_is_correct(buf, bs_blksz, word.f.blob_id,
block * bs_blksz, bs_blksz));
/* Check the block that follows */
if (block + 1 < blob_num_blocks) {
g_bserrno = 0xbad;
spdk_blob_io_read(blob, bs_ch, buf, block + 1, 1, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
if (g_bserrno != 0) {
break;
}
CU_ASSERT(ut_esnap_content_is_correct(buf, bs_blksz, blob->id,
(block + 1) * bs_blksz,
esnap_blksz));
}
}
/* Clean up */
spdk_bs_free_io_channel(bs_ch);
g_bserrno = 0xbad;
spdk_blob_close(blob, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_unload(g_bs, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
g_bs = NULL;
memset(g_dev_buffer, 0, DEV_BUFFER_SIZE);
}
static void
blob_esnap_io_4096_4096(void)
{
blob_esnap_io_size(4096, 4096);
}
static void
blob_esnap_io_512_512(void)
{
blob_esnap_io_size(512, 512);
}
static void
blob_esnap_io_4096_512(void)
{
blob_esnap_io_size(4096, 512);
}
static void
blob_esnap_io_512_4096(void)
{
struct spdk_bs_dev *dev;
struct spdk_blob_store *bs;
struct spdk_bs_opts bs_opts;
struct spdk_blob_opts blob_opts;
struct ut_esnap_opts esnap_opts;
uint64_t cluster_sz = 16 * 1024;
uint32_t bs_blksz = 512;
uint32_t esnap_blksz = 4096;
uint64_t esnap_num_blocks = 64;
spdk_blob_id blobid;
/* Create device with desired block size */
dev = init_dev();
dev->blocklen = bs_blksz;
dev->blockcnt = DEV_BUFFER_SIZE / dev->blocklen;
/* Initialize a new blob store */
spdk_bs_opts_init(&bs_opts, sizeof(bs_opts));
bs_opts.cluster_sz = cluster_sz;
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);
SPDK_CU_ASSERT_FATAL(g_bs->io_unit_size == bs_blksz);
bs = g_bs;
/* Try to create and open the esnap clone. Create should succeed, open should fail. */
ut_spdk_blob_opts_init(&blob_opts);
ut_esnap_opts_init(esnap_blksz, esnap_num_blocks, __func__, NULL, &esnap_opts);
blob_opts.esnap_id = &esnap_opts;
blob_opts.esnap_id_len = sizeof(esnap_opts);
blob_opts.num_clusters = esnap_num_blocks * esnap_blksz / bs_blksz;
spdk_bs_create_blob_ext(bs, &blob_opts, blob_op_with_id_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
blobid = g_blobid;
spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == -EINVAL);
CU_ASSERT(g_blob == NULL);
/* Clean up */
spdk_bs_unload(bs, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
g_bs = NULL;
memset(g_dev_buffer, 0, DEV_BUFFER_SIZE);
}
static void
blob_esnap_thread_add_remove(void)
{
struct spdk_blob_store *bs = g_bs;
struct spdk_blob_opts opts;
struct ut_esnap_opts ut_esnap_opts;
struct spdk_blob *blob;
struct ut_esnap_dev *ut_dev;
spdk_blob_id blobid;
uint64_t start_thread = g_ut_thread_id;
bool destroyed = false;
struct spdk_io_channel *ch0, *ch1;
struct ut_esnap_channel *ut_ch0, *ut_ch1;
const uint32_t blocklen = bs->io_unit_size;
char buf[blocklen * 4];
SPDK_CU_ASSERT_FATAL(g_ut_num_threads > 1);
set_thread(0);
/* Create the esnap clone */
ut_esnap_opts_init(blocklen, 2048, "add_remove_1", &destroyed, &ut_esnap_opts);
ut_spdk_blob_opts_init(&opts);
opts.esnap_id = &ut_esnap_opts;
opts.esnap_id_len = sizeof(ut_esnap_opts);
opts.num_clusters = 10;
spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
blobid = g_blobid;
/* Open the blob. No channels should be allocated yet. */
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;
ut_dev = (struct ut_esnap_dev *)blob->back_bs_dev;
CU_ASSERT(ut_dev != NULL);
CU_ASSERT(ut_dev->num_channels == 0);
/* Create a channel on thread 0. It is lazily created on the first read. */
ch0 = spdk_bs_alloc_io_channel(bs);
CU_ASSERT(ch0 != NULL);
ut_ch0 = ut_esnap_get_io_channel(ch0, blobid);
CU_ASSERT(ut_ch0 == NULL);
CU_ASSERT(ut_dev->num_channels == 0);
spdk_blob_io_read(blob, ch0, buf, 0, 1, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(ut_dev->num_channels == 1);
ut_ch0 = ut_esnap_get_io_channel(ch0, blobid);
CU_ASSERT(ut_ch0 != NULL);
CU_ASSERT(ut_ch0->blocks_read == 1);
/* Create a channel on thread 1 and verify its lazy creation too. */
set_thread(1);
ch1 = spdk_bs_alloc_io_channel(bs);
CU_ASSERT(ch1 != NULL);
ut_ch1 = ut_esnap_get_io_channel(ch1, blobid);
CU_ASSERT(ut_ch1 == NULL);
CU_ASSERT(ut_dev->num_channels == 1);
spdk_blob_io_read(blob, ch1, buf, 0, 4, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(ut_dev->num_channels == 2);
ut_ch1 = ut_esnap_get_io_channel(ch1, blobid);
CU_ASSERT(ut_ch1 != NULL);
CU_ASSERT(ut_ch1->blocks_read == 4);
/* Close the channel on thread 0 and verify the bs_dev channel is also gone. */
set_thread(0);
spdk_bs_free_io_channel(ch0);
poll_threads();
CU_ASSERT(ut_dev->num_channels == 1);
/* Close the blob. There is no outstanding IO so it should close right away. */
g_bserrno = 0xbad;
spdk_blob_close(blob, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(destroyed);
/* The esnap channel for the blob should be gone now too. */
ut_ch1 = ut_esnap_get_io_channel(ch1, blobid);
CU_ASSERT(ut_ch1 == NULL);
/* Clean up */
set_thread(1);
spdk_bs_free_io_channel(ch1);
set_thread(start_thread);
}
static void static void
freeze_done(void *cb_arg, int bserrno) freeze_done(void *cb_arg, int bserrno)
{ {
@ -7704,10 +8047,12 @@ suite_esnap_bs_setup(void)
static void static void
suite_bs_cleanup(void) suite_bs_cleanup(void)
{ {
spdk_bs_unload(g_bs, bs_op_complete, NULL); if (g_bs != NULL) {
poll_threads(); spdk_bs_unload(g_bs, bs_op_complete, NULL);
CU_ASSERT(g_bserrno == 0); poll_threads();
g_bs = NULL; CU_ASSERT(g_bserrno == 0);
g_bs = NULL;
}
memset(g_dev_buffer, 0, DEV_BUFFER_SIZE); memset(g_dev_buffer, 0, DEV_BUFFER_SIZE);
} }
@ -7870,6 +8215,11 @@ main(int argc, char **argv)
CU_ADD_TEST(suite_esnap_bs, blob_esnap_create); CU_ADD_TEST(suite_esnap_bs, blob_esnap_create);
CU_ADD_TEST(suite_bs, blob_nested_freezes); CU_ADD_TEST(suite_bs, blob_nested_freezes);
CU_ADD_TEST(suite, blob_ext_md_pages); CU_ADD_TEST(suite, blob_ext_md_pages);
CU_ADD_TEST(suite, blob_esnap_io_4096_4096);
CU_ADD_TEST(suite, blob_esnap_io_512_512);
CU_ADD_TEST(suite, blob_esnap_io_4096_512);
CU_ADD_TEST(suite, blob_esnap_io_512_4096);
CU_ADD_TEST(suite_esnap_bs, blob_esnap_thread_add_remove);
allocate_threads(2); allocate_threads(2);
set_thread(0); set_thread(0);

View File

@ -13,6 +13,8 @@
* struct spdk_bs_opts bs_opts; * struct spdk_bs_opts bs_opts;
* struct spdk_blob_opts blob_opts; * struct spdk_blob_opts blob_opts;
* struct ut_snap_opts esnap_opts; * struct ut_snap_opts esnap_opts;
* struct spdk_io_channel *bs_chan;
* bool destroyed = false;
* *
* Create the blobstore with external snapshot support. * Create the blobstore with external snapshot support.
* dev = init_dev(); * dev = init_dev();
@ -21,16 +23,45 @@
* bs_opts.esnap_bs_dev_create = ut_esnap_create; * bs_opts.esnap_bs_dev_create = ut_esnap_create;
* *
* Create an esnap clone blob. * Create an esnap clone blob.
* ut_spdk_blob_opts_init(&blob_opts); * ut_esnap_opts_init(512, 2048, "name", &destroyed, &esnap_opts);
* ut_esnap_opts_init(512, 2048, "name", &esnap_opts);
* blob_opts.esnap_id = &esnap_opts; * blob_opts.esnap_id = &esnap_opts;
* blob_opts.esnap_id_len = sizeof(esnap_opts); * blob_opts.esnap_id_len = sizeof(esnap_opts);
* opts.num_clusters = 4; * opts.num_clusters = 4;
* blob = ut_blob_create_and_open(bs, &opts); * blob = ut_blob_create_and_open(bs, &opts);
* *
* At this point the blob can be used like any other blob. * Do stuff like you would with any other blob.
* bs_chan = spdk_bs_alloc_io_channel(bs);
* ...
*
* You can check the value of destroyed to verify that spdk_blob_close() led to the
* destruction of the bs_dev created during spdk_blob_open().
* spdk_blob_close(blob, blob_op_complete, NULL);
* poll_threads();
* CU_ASSERT(destroyed);
*/ */
static void
ut_memset4(void *dst, uint32_t pat, size_t len)
{
uint32_t *vals = dst;
assert((len % 4) == 0);
for (size_t i = 0; i < (len / 4); i++) {
vals[i] = pat;
}
}
static void
ut_memset8(void *dst, uint64_t pat, size_t len)
{
uint64_t *vals = dst;
assert((len % 8) == 0);
for (size_t i = 0; i < (len / 8); i++) {
vals[i] = pat;
}
}
#define UT_ESNAP_OPTS_MAGIC 0xbadf1ea5 #define UT_ESNAP_OPTS_MAGIC 0xbadf1ea5
struct ut_esnap_opts { struct ut_esnap_opts {
/* /*
@ -40,6 +71,11 @@ struct ut_esnap_opts {
uint32_t magic; uint32_t magic;
uint32_t block_size; uint32_t block_size;
uint64_t num_blocks; uint64_t num_blocks;
/*
* If non-NULL, referenced address will be set to true when the device is fully destroyed.
* This address must remain valid for the life of the blob, even across blobstore reload.
*/
bool *destroyed;
char name[32]; char name[32];
}; };
@ -50,21 +86,197 @@ struct ut_esnap_dev {
uint32_t num_channels; uint32_t num_channels;
}; };
struct ut_esnap_channel {
struct ut_esnap_dev *dev;
struct spdk_thread *thread;
uint64_t blocks_read;
};
static void static void
ut_esnap_opts_init(uint32_t block_size, uint32_t num_blocks, const char *name, ut_esnap_opts_init(uint32_t block_size, uint32_t num_blocks, const char *name, bool *destroyed,
struct ut_esnap_opts *opts) struct ut_esnap_opts *opts)
{ {
memset(opts, 0, sizeof(*opts)); memset(opts, 0, sizeof(*opts));
opts->magic = UT_ESNAP_OPTS_MAGIC; opts->magic = UT_ESNAP_OPTS_MAGIC;
opts->block_size = block_size; opts->block_size = block_size;
opts->num_blocks = num_blocks; opts->num_blocks = num_blocks;
opts->destroyed = destroyed;
spdk_strcpy_pad(opts->name, name, sizeof(opts->name) - 1, '\0'); spdk_strcpy_pad(opts->name, name, sizeof(opts->name) - 1, '\0');
} }
static struct spdk_io_channel *
ut_esnap_create_channel(struct spdk_bs_dev *dev)
{
struct spdk_io_channel *ch;
ch = spdk_get_io_channel(dev);
if (ch == NULL) {
return NULL;
}
return ch;
}
static void
ut_esnap_destroy_channel(struct spdk_bs_dev *dev, struct spdk_io_channel *channel)
{
spdk_put_io_channel(channel);
}
/*
* When reading, each block is filled with 64-bit values made up of the least significant 32 bits of
* the blob ID and the lba.
*/
union ut_word {
uint64_t num;
struct {
uint32_t blob_id;
uint32_t lba;
} f;
};
static bool
ut_esnap_content_is_correct(void *buf, uint32_t buf_sz, uint32_t id,
uint32_t start_byte, uint32_t esnap_blksz)
{
union ut_word *words = buf;
uint32_t off, i, j, lba;
j = 0;
for (off = start_byte; off < start_byte + buf_sz; off += esnap_blksz) {
lba = off / esnap_blksz;
for (i = 0; i < esnap_blksz / sizeof(*words); i++) {
if (words[j].f.blob_id != id || words[j].f.lba != lba) {
return false;
}
j++;
}
}
return true;
}
static void
ut_esnap_read(struct spdk_bs_dev *bs_dev, struct spdk_io_channel *channel, void *payload,
uint64_t lba, uint32_t lba_count, struct spdk_bs_dev_cb_args *cb_args)
{
struct ut_esnap_dev *ut_dev = (struct ut_esnap_dev *)bs_dev;
struct ut_esnap_channel *ut_ch = spdk_io_channel_get_ctx(channel);
const uint32_t block_size = ut_dev->ut_opts.block_size;
union ut_word word;
uint64_t cur;
/* The channel passed in must be associated with this bs_dev. */
CU_ASSERT(&ut_ch->dev->bs_dev == bs_dev);
CU_ASSERT(spdk_get_thread() == ut_ch->thread);
SPDK_CU_ASSERT_FATAL(sizeof(word) == 8);
SPDK_CU_ASSERT_FATAL(lba + lba_count <= UINT32_MAX);
word.f.blob_id = ut_dev->blob_id & 0xffffffff;
for (cur = 0; cur < lba_count; cur++) {
word.f.lba = lba + cur;
ut_memset8(payload + cur * block_size, word.num, block_size);
}
ut_ch->blocks_read += lba_count;
cb_args->cb_fn(cb_args->channel, cb_args->cb_arg, 0);
}
static void
ut_esnap_readv(struct spdk_bs_dev *bs_dev, struct spdk_io_channel *channel,
struct iovec *iov, int iovcnt, uint64_t lba, uint32_t lba_count,
struct spdk_bs_dev_cb_args *cb_args)
{
struct ut_esnap_channel *ut_ch = spdk_io_channel_get_ctx(channel);
/* The channel passed in must be associated with this bs_dev. */
CU_ASSERT(&ut_ch->dev->bs_dev == bs_dev);
CU_ASSERT(spdk_get_thread() == ut_ch->thread);
if (iovcnt != 1) {
CU_ASSERT(false);
cb_args->cb_fn(cb_args->channel, cb_args->cb_arg, -ENOTSUP);
return;
}
ut_esnap_read(bs_dev, channel, iov->iov_base, lba, lba_count, cb_args);
}
static void
ut_esnap_readv_ext(struct spdk_bs_dev *bs_dev, struct spdk_io_channel *channel,
struct iovec *iov, int iovcnt, uint64_t lba, uint32_t lba_count,
struct spdk_bs_dev_cb_args *cb_args, struct spdk_blob_ext_io_opts *io_opts)
{
struct ut_esnap_channel *ut_ch = spdk_io_channel_get_ctx(channel);
/* The channel passed in must be associated with this bs_dev. */
CU_ASSERT(&ut_ch->dev->bs_dev == bs_dev);
CU_ASSERT(spdk_get_thread() == ut_ch->thread);
CU_ASSERT(false);
cb_args->cb_fn(cb_args->channel, cb_args->cb_arg, -ENOTSUP);
}
static bool
ut_esnap_is_zeroes(struct spdk_bs_dev *dev, uint64_t lba, uint64_t lba_count)
{
return false;
}
static int
ut_esnap_io_channel_create(void *io_device, void *ctx)
{
struct ut_esnap_dev *ut_dev = io_device;
struct ut_esnap_channel *ut_ch = ctx;
ut_ch->dev = ut_dev;
ut_ch->thread = spdk_get_thread();
ut_ch->blocks_read = 0;
ut_dev->num_channels++;
return 0;
}
static void
ut_esnap_io_channel_destroy(void *io_device, void *ctx)
{
struct ut_esnap_dev *ut_dev = io_device;
struct ut_esnap_channel *ut_ch = ctx;
CU_ASSERT(ut_ch->thread == spdk_get_thread());
CU_ASSERT(ut_dev->num_channels > 0);
ut_dev->num_channels--;
return;
}
static void
ut_esnap_dev_free(void *io_device)
{
struct ut_esnap_dev *ut_dev = io_device;
if (ut_dev->ut_opts.destroyed != NULL) {
*ut_dev->ut_opts.destroyed = true;
}
CU_ASSERT(ut_dev->num_channels == 0);
ut_memset4(ut_dev, 0xdeadf1ea, sizeof(*ut_dev));
free(ut_dev);
}
static void static void
ut_esnap_destroy(struct spdk_bs_dev *bs_dev) ut_esnap_destroy(struct spdk_bs_dev *bs_dev)
{ {
free(bs_dev); spdk_io_device_unregister(bs_dev, ut_esnap_dev_free);
}
static bool
ut_esnap_translate_lba(struct spdk_bs_dev *dev, uint64_t lba, uint64_t *base_lba)
{
*base_lba = lba;
return true;
} }
static struct spdk_bs_dev * static struct spdk_bs_dev *
@ -86,7 +298,17 @@ ut_esnap_dev_alloc(const struct ut_esnap_opts *opts)
bs_dev->blocklen = opts->block_size; bs_dev->blocklen = opts->block_size;
bs_dev->blockcnt = opts->num_blocks; bs_dev->blockcnt = opts->num_blocks;
bs_dev->create_channel = ut_esnap_create_channel;
bs_dev->destroy_channel = ut_esnap_destroy_channel;
bs_dev->destroy = ut_esnap_destroy; bs_dev->destroy = ut_esnap_destroy;
bs_dev->read = ut_esnap_read;
bs_dev->readv = ut_esnap_readv;
bs_dev->readv_ext = ut_esnap_readv_ext;
bs_dev->is_zeroes = ut_esnap_is_zeroes;
bs_dev->translate_lba = ut_esnap_translate_lba;
spdk_io_device_register(ut_dev, ut_esnap_io_channel_create, ut_esnap_io_channel_destroy,
sizeof(struct ut_esnap_channel), opts->name);
return bs_dev; return bs_dev;
} }
@ -134,3 +356,19 @@ ut_esnap_create_with_count(void *bs_ctx, void *blob_ctx, struct spdk_blob *blob,
return ut_esnap_create(NULL, NULL, blob, id, id_len, bs_devp); return ut_esnap_create(NULL, NULL, blob, id, id_len, bs_devp);
} }
static struct ut_esnap_channel *
ut_esnap_get_io_channel(struct spdk_io_channel *ch, spdk_blob_id blob_id)
{
struct spdk_bs_channel *bs_channel = spdk_io_channel_get_ctx(ch);
struct blob_esnap_channel find = {};
struct blob_esnap_channel *esnap_channel;
find.blob_id = blob_id;
esnap_channel = RB_FIND(blob_esnap_channel_tree, &bs_channel->esnap_channels, &find);
if (esnap_channel == NULL) {
return NULL;
}
return spdk_io_channel_get_ctx(esnap_channel->channel);
}