diff --git a/include/spdk/blob.h b/include/spdk/blob.h index 5b71f0a09..445032c48 100644 --- a/include/spdk/blob.h +++ b/include/spdk/blob.h @@ -1120,6 +1120,17 @@ struct spdk_bs_type spdk_bs_get_bstype(struct spdk_blob_store *bs); */ void spdk_bs_set_bstype(struct spdk_blob_store *bs, struct spdk_bs_type bstype); +/** + * Replace the existing external snapshot device. + * + * \param blob The blob that is getting a new external snapshot device. + * \param back_bs_dev The new blobstore device to use as an external snapshot. + * \param cb_fn Callback to be called when complete. + * \param cb_arg Callback argument used with cb_fn. + */ +void spdk_blob_set_esnap_bs_dev(struct spdk_blob *blob, struct spdk_bs_dev *back_bs_dev, + spdk_blob_op_complete cb_fn, void *cb_arg); + #ifdef __cplusplus } #endif diff --git a/lib/blob/blobstore.c b/lib/blob/blobstore.c index 7d883e502..55ecb9cea 100644 --- a/lib/blob/blobstore.c +++ b/lib/blob/blobstore.c @@ -8986,5 +8986,98 @@ blob_esnap_destroy_bs_channel(struct spdk_bs_channel *ch) spdk_thread_get_name(spdk_get_thread())); } +struct set_bs_dev_ctx { + struct spdk_blob *blob; + struct spdk_bs_dev *back_bs_dev; + spdk_blob_op_complete cb_fn; + void *cb_arg; + int bserrno; +}; + +static void +blob_set_back_bs_dev_done(void *_ctx, int bserrno) +{ + struct set_bs_dev_ctx *ctx = _ctx; + + if (bserrno != 0) { + /* Even though the unfreeze failed, the update may have succeed. */ + SPDK_ERRLOG("blob 0x%" PRIx64 ": unfreeze failed with error %d\n", ctx->blob->id, + bserrno); + } + ctx->cb_fn(ctx->cb_arg, ctx->bserrno); + free(ctx); +} + +static void +blob_frozen_set_back_bs_dev(void *_ctx, struct spdk_blob *blob, int bserrno) +{ + struct set_bs_dev_ctx *ctx = _ctx; + + if (bserrno != 0) { + SPDK_ERRLOG("blob 0x%" PRIx64 ": failed to release old back_bs_dev with error %d\n", + blob->id, bserrno); + ctx->bserrno = bserrno; + blob_unfreeze_io(blob, blob_set_back_bs_dev_done, ctx); + return; + } + + if (blob->back_bs_dev != NULL) { + blob->back_bs_dev->destroy(blob->back_bs_dev); + } + + SPDK_NOTICELOG("blob 0x%" PRIx64 ": hotplugged back_bs_dev\n", blob->id); + blob->back_bs_dev = ctx->back_bs_dev; + ctx->bserrno = 0; + + blob_unfreeze_io(blob, blob_set_back_bs_dev_done, ctx); +} + +static void +blob_frozen_destroy_esnap_channels(void *_ctx, int bserrno) +{ + struct set_bs_dev_ctx *ctx = _ctx; + struct spdk_blob *blob = ctx->blob; + + if (bserrno != 0) { + SPDK_ERRLOG("blob 0x%" PRIx64 ": failed to freeze with error %d\n", blob->id, + bserrno); + ctx->cb_fn(ctx->cb_arg, bserrno); + free(ctx); + return; + } + + /* + * This does not prevent future reads from the esnap device because any future IO will + * lazily create a new esnap IO channel. + */ + blob_esnap_destroy_bs_dev_channels(blob, true, blob_frozen_set_back_bs_dev, ctx); +} + +void +spdk_blob_set_esnap_bs_dev(struct spdk_blob *blob, struct spdk_bs_dev *back_bs_dev, + spdk_blob_op_complete cb_fn, void *cb_arg) +{ + struct set_bs_dev_ctx *ctx; + + if (!blob_is_esnap_clone(blob)) { + SPDK_ERRLOG("blob 0x%" PRIx64 ": not an esnap clone\n", blob->id); + cb_fn(cb_arg, -EINVAL); + return; + } + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + SPDK_ERRLOG("blob 0x%" PRIx64 ": out of memory while setting back_bs_dev\n", + blob->id); + cb_fn(cb_arg, -ENOMEM); + return; + } + ctx->cb_fn = cb_fn; + ctx->cb_arg = cb_arg; + ctx->back_bs_dev = back_bs_dev; + ctx->blob = blob; + blob_freeze_io(blob, blob_frozen_destroy_esnap_channels, ctx); +} + SPDK_LOG_REGISTER_COMPONENT(blob) SPDK_LOG_REGISTER_COMPONENT(blob_esnap) diff --git a/lib/blob/spdk_blob.map b/lib/blob/spdk_blob.map index 6a42d955e..e49348687 100644 --- a/lib/blob/spdk_blob.map +++ b/lib/blob/spdk_blob.map @@ -66,6 +66,7 @@ spdk_xattr_names_free; spdk_bs_get_bstype; spdk_bs_set_bstype; + spdk_blob_set_esnap_bs_dev; local: *; }; diff --git a/test/unit/lib/blob/blob.c/blob_ut.c b/test/unit/lib/blob/blob.c/blob_ut.c index 609de1e5b..be15df100 100644 --- a/test/unit/lib/blob/blob.c/blob_ut.c +++ b/test/unit/lib/blob/blob.c/blob_ut.c @@ -8420,6 +8420,91 @@ blob_esnap_clone_decouple(void) _blob_esnap_clone_hydrate(false); } +static void +blob_esnap_hotplug(void) +{ + struct spdk_blob_store *bs = g_bs; + struct ut_esnap_opts esnap1_opts, esnap2_opts; + struct spdk_blob_opts opts; + struct spdk_blob *blob; + struct spdk_bs_dev *bs_dev; + struct ut_esnap_dev *esnap_dev; + uint32_t cluster_sz = spdk_bs_get_cluster_size(bs); + uint32_t block_sz = spdk_bs_get_io_unit_size(bs); + const uint32_t esnap_num_clusters = 4; + uint64_t esnap_num_blocks = cluster_sz * esnap_num_clusters / block_sz; + bool destroyed1 = false, destroyed2 = false; + uint64_t start_thread = g_ut_thread_id; + struct spdk_io_channel *ch0, *ch1; + char buf[block_sz]; + + /* Create and open an esnap clone blob */ + ut_spdk_blob_opts_init(&opts); + ut_esnap_opts_init(block_sz, esnap_num_blocks, "esnap1", &destroyed1, &esnap1_opts); + opts.esnap_id = &esnap1_opts; + opts.esnap_id_len = sizeof(esnap1_opts); + opts.num_clusters = esnap_num_clusters; + blob = ut_blob_create_and_open(bs, &opts); + CU_ASSERT(blob != NULL); + CU_ASSERT(spdk_blob_is_esnap_clone(blob)); + SPDK_CU_ASSERT_FATAL(blob->back_bs_dev != NULL); + esnap_dev = (struct ut_esnap_dev *)blob->back_bs_dev; + CU_ASSERT(strcmp(esnap_dev->ut_opts.name, "esnap1") == 0); + + /* Replace the external snapshot */ + ut_esnap_opts_init(block_sz, esnap_num_blocks, "esnap2", &destroyed2, &esnap2_opts); + bs_dev = ut_esnap_dev_alloc(&esnap2_opts); + CU_ASSERT(!destroyed1); + CU_ASSERT(!destroyed2); + g_bserrno = 0xbad; + spdk_blob_set_esnap_bs_dev(blob, bs_dev, bs_op_complete, NULL); + poll_threads(); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(destroyed1); + CU_ASSERT(!destroyed2); + SPDK_CU_ASSERT_FATAL(blob->back_bs_dev != NULL); + esnap_dev = (struct ut_esnap_dev *)blob->back_bs_dev; + CU_ASSERT(strcmp(esnap_dev->ut_opts.name, "esnap2") == 0); + + /* Create a couple channels */ + set_thread(0); + ch0 = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(ch0 != NULL); + spdk_blob_io_read(blob, ch0, buf, 0, 1, bs_op_complete, NULL); + set_thread(1); + ch1 = spdk_bs_alloc_io_channel(bs); + CU_ASSERT(ch1 != NULL); + spdk_blob_io_read(blob, ch1, buf, 0, 1, bs_op_complete, NULL); + set_thread(start_thread); + poll_threads(); + CU_ASSERT(esnap_dev->num_channels == 2); + + /* Replace the external snapshot */ + ut_esnap_opts_init(block_sz, esnap_num_blocks, "esnap1a", &destroyed1, &esnap1_opts); + bs_dev = ut_esnap_dev_alloc(&esnap1_opts); + destroyed1 = destroyed2 = false; + g_bserrno = 0xbad; + spdk_blob_set_esnap_bs_dev(blob, bs_dev, bs_op_complete, NULL); + poll_threads(); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(!destroyed1); + CU_ASSERT(destroyed2); + SPDK_CU_ASSERT_FATAL(blob->back_bs_dev != NULL); + esnap_dev = (struct ut_esnap_dev *)blob->back_bs_dev; + CU_ASSERT(strcmp(esnap_dev->ut_opts.name, "esnap1a") == 0); + + /* Clean up */ + set_thread(0); + spdk_bs_free_io_channel(ch0); + set_thread(1); + spdk_bs_free_io_channel(ch1); + set_thread(start_thread); + g_bserrno = 0xbad; + spdk_blob_close(blob, bs_op_complete, NULL); + poll_threads(); + CU_ASSERT(g_bserrno == 0); +} + static void suite_bs_setup(void) { @@ -8630,6 +8715,7 @@ main(int argc, char **argv) CU_ADD_TEST(suite_esnap_bs, blob_esnap_clone_inflate); CU_ADD_TEST(suite_esnap_bs, blob_esnap_clone_decouple); CU_ADD_TEST(suite_esnap_bs, blob_esnap_clone_reload); + CU_ADD_TEST(suite_esnap_bs, blob_esnap_hotplug); allocate_threads(2); set_thread(0);