diff --git a/include/spdk/blob.h b/include/spdk/blob.h index bc5757b7d..44c7f10a9 100644 --- a/include/spdk/blob.h +++ b/include/spdk/blob.h @@ -467,6 +467,19 @@ bool spdk_blob_is_thin_provisioned(struct spdk_blob *blob); void spdk_bs_delete_blob(struct spdk_blob_store *bs, spdk_blob_id blobid, spdk_blob_op_complete cb_fn, void *cb_arg); +/** + * Allocate all unallocated clusters in this blob and copy data from backing blob. + * This call removes dependency on backing blob. + * + * \param bs blobstore. + * \param channel IO channel used to inflate blob. + * \param blobid The id of the blob to inflate. + * \param cb_fn Called when the operation is complete. + * \param cb_arg Argument passed to function cb_fn. + */ +void spdk_bs_inflate_blob(struct spdk_blob_store *bs, struct spdk_io_channel *channel, + spdk_blob_id blobid, spdk_blob_op_complete cb_fn, void *cb_arg); + /** * Open a blob from the given blobstore. * diff --git a/lib/blob/blobstore.c b/lib/blob/blobstore.c index 6bdd16c82..aad1506f2 100644 --- a/lib/blob/blobstore.c +++ b/lib/blob/blobstore.c @@ -1699,6 +1699,11 @@ _spdk_blob_request_submit_op_single(struct spdk_io_channel *_ch, struct spdk_blo /* Write to the blob */ spdk_bs_batch_t *batch; + if (lba_count == 0) { + cb_fn(cb_arg, 0); + return; + } + batch = spdk_bs_batch_open(_ch, &cpl); if (!batch) { cb_fn(cb_arg, -ENOMEM); @@ -3706,6 +3711,11 @@ struct spdk_clone_snapshot_ctx { struct spdk_bs_cpl cpl; int bserrno; + struct spdk_io_channel *channel; + + /* Current cluster for inflate operation */ + uint64_t cluster; + struct { spdk_blob_id id; struct spdk_blob *blob; @@ -3738,6 +3748,9 @@ _spdk_bs_clone_snapshot_cleanup_finish(void *cb_arg, int bserrno) case SPDK_BS_CPL_TYPE_BLOBID: cpl->u.blobid.cb_fn(cpl->u.blobid.cb_arg, cpl->u.blobid.blobid, ctx->bserrno); break; + case SPDK_BS_CPL_TYPE_BLOB_BASIC: + cpl->u.blob_basic.cb_fn(cpl->u.blob_basic.cb_arg, ctx->bserrno); + break; default: SPDK_UNREACHABLE(); break; @@ -4072,6 +4085,140 @@ void spdk_bs_create_clone(struct spdk_blob_store *bs, spdk_blob_id blobid, /* END spdk_bs_create_clone */ +/* START spdk_bs_inflate_blob */ + +static void +_spdk_bs_inflate_blob_sync(void *cb_arg, int bserrno) +{ + struct spdk_clone_snapshot_ctx *ctx = (struct spdk_clone_snapshot_ctx *)cb_arg; + struct spdk_blob *_blob = ctx->original.blob; + + if (bserrno != 0) { + _spdk_bs_clone_snapshot_origblob_cleanup(ctx, bserrno); + return; + } + + /* Destroy back_bs_dev */ + _blob->back_bs_dev->destroy(_blob->back_bs_dev); + _blob->back_bs_dev = NULL; + + _spdk_bs_clone_snapshot_origblob_cleanup(ctx, 0); +} + +static void +_spdk_bs_inflate_blob_done(void *cb_arg, int bserrno) +{ + struct spdk_clone_snapshot_ctx *ctx = (struct spdk_clone_snapshot_ctx *)cb_arg; + struct spdk_blob *_blob = ctx->original.blob; + + if (bserrno != 0) { + _spdk_bs_clone_snapshot_origblob_cleanup(ctx, bserrno); + return; + } + + _spdk_bs_blob_list_remove(_blob); + + _spdk_blob_remove_xattr(_blob, BLOB_SNAPSHOT, true); + + /* Unset thin provision */ + _blob->invalid_flags = _blob->invalid_flags & ~SPDK_BLOB_THIN_PROV; + _blob->state = SPDK_BLOB_STATE_DIRTY; + + spdk_blob_sync_md(_blob, _spdk_bs_inflate_blob_sync, ctx); +} + +static void +_spdk_bs_inflate_blob_touch_next(void *cb_arg, int bserrno) +{ + struct spdk_clone_snapshot_ctx *ctx = (struct spdk_clone_snapshot_ctx *)cb_arg; + struct spdk_blob *_blob = ctx->original.blob; + uint64_t offset; + + if (bserrno != 0) { + _spdk_bs_clone_snapshot_origblob_cleanup(ctx, bserrno); + return; + } + + for (; ctx->cluster < _blob->active.num_clusters; ctx->cluster++) { + if (_blob->active.clusters[ctx->cluster] == 0) { + break; + } + } + + if (ctx->cluster < _blob->active.num_clusters) { + offset = _spdk_bs_cluster_to_page(_blob->bs, ctx->cluster); + + /* We may safely increment a cluster before write */ + ctx->cluster++; + + /* Use zero length write to touch a cluster */ + spdk_blob_io_write(_blob, ctx->channel, NULL, offset, 0, + _spdk_bs_inflate_blob_touch_next, ctx); + } else { + _spdk_bs_inflate_blob_done(cb_arg, bserrno); + } +} + +static void +_spdk_bs_inflate_blob_open_cpl(void *cb_arg, struct spdk_blob *_blob, int bserrno) +{ + struct spdk_clone_snapshot_ctx *ctx = (struct spdk_clone_snapshot_ctx *)cb_arg; + uint64_t lfc; /* lowest free cluster */ + uint64_t i; + + if (bserrno != 0) { + _spdk_bs_clone_snapshot_cleanup_finish(ctx, bserrno); + return; + } + ctx->original.blob = _blob; + + if (spdk_blob_is_thin_provisioned(_blob) == false) { + /* This is not thin provisioned blob. No need to inflate. */ + _spdk_bs_clone_snapshot_origblob_cleanup(ctx, 0); + return; + } + + /* Do two passes - one to verify that we can obtain enough clusters + * and another to actually claim them. + */ + lfc = 0; + for (i = 0; i < _blob->active.num_clusters; i++) { + if (_blob->active.clusters[i] == 0) { + lfc = spdk_bit_array_find_first_clear(_blob->bs->used_clusters, lfc); + if (lfc >= _blob->bs->total_clusters) { + /* No more free clusters. Cannot satisfy the request */ + _spdk_bs_clone_snapshot_origblob_cleanup(ctx, -ENOSPC); + return; + } + lfc++; + } + } + + ctx->cluster = 0; + _spdk_bs_inflate_blob_touch_next(ctx, 0); +} + +void spdk_bs_inflate_blob(struct spdk_blob_store *bs, struct spdk_io_channel *channel, + spdk_blob_id blobid, spdk_blob_op_complete cb_fn, void *cb_arg) +{ + struct spdk_clone_snapshot_ctx *ctx = calloc(1, sizeof(*ctx)); + + if (!ctx) { + cb_fn(cb_arg, -ENOMEM); + return; + } + ctx->cpl.type = SPDK_BS_CPL_TYPE_BLOB_BASIC; + ctx->cpl.u.bs_basic.cb_fn = cb_fn; + ctx->cpl.u.bs_basic.cb_arg = cb_arg; + ctx->bserrno = 0; + ctx->original.id = blobid; + ctx->channel = channel; + + spdk_bs_open_blob(bs, ctx->original.id, _spdk_bs_inflate_blob_open_cpl, ctx); +} + +/* END spdk_bs_inflate_blob */ + /* START spdk_blob_resize */ void spdk_blob_resize(struct spdk_blob *blob, uint64_t sz, spdk_blob_op_complete cb_fn, void *cb_arg) diff --git a/test/unit/lib/blob/blob.c/blob_ut.c b/test/unit/lib/blob/blob.c/blob_ut.c index 4c3947812..a37700176 100644 --- a/test/unit/lib/blob/blob.c/blob_ut.c +++ b/test/unit/lib/blob/blob.c/blob_ut.c @@ -805,6 +805,92 @@ blob_clone(void) } +static void +blob_inflate(void) +{ + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob_opts opts; + struct spdk_blob *blob, *snapshot; + spdk_blob_id blobid, snapshotid; + struct spdk_io_channel *channel; + uint64_t free_clusters; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + channel = spdk_bs_alloc_io_channel(bs); + SPDK_CU_ASSERT_FATAL(channel != NULL); + + /* Create blob with 10 clusters */ + + spdk_blob_opts_init(&opts); + opts.num_clusters = 10; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + 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); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10) + CU_ASSERT(spdk_blob_is_thin_provisioned(blob) == false); + + /* Create snapshot */ + + spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + snapshotid = g_blobid; + + CU_ASSERT(spdk_blob_is_thin_provisioned(blob) == true); + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10) + + spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot = g_blob; + CU_ASSERT(snapshot->data_ro == true) + CU_ASSERT(snapshot->md_ro == true) + CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 10); + + spdk_blob_close(snapshot, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + free_clusters = spdk_bs_free_cluster_count(bs); + + /* Inflate blob */ + spdk_bs_inflate_blob(bs, channel, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* All 10 clusters should be allocated from blob store */ + CU_ASSERT(spdk_bs_free_cluster_count(bs) == free_clusters - 10); + + /* Now, it should be possible to delete snapshot */ + spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 10) + CU_ASSERT(spdk_blob_is_thin_provisioned(blob) == false); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + + spdk_bs_free_io_channel(channel); +} + static void blob_delete(void) { @@ -3762,6 +3848,178 @@ blob_snapshot_rw_iov(void) g_blobid = 0; } +static void +blob_inflate_rw(void) +{ + static uint8_t *zero; + struct spdk_blob_store *bs; + struct spdk_bs_dev *dev; + struct spdk_blob *blob, *snapshot; + struct spdk_io_channel *channel; + struct spdk_blob_opts opts; + spdk_blob_id blobid, snapshotid; + uint64_t free_clusters; + uint64_t cluster_size; + + uint64_t payload_size; + uint8_t *payload_read; + uint8_t *payload_write; + uint8_t *payload_clone; + + uint64_t pages_per_cluster; + uint64_t pages_per_payload; + + int i; + + dev = init_dev(); + + spdk_bs_init(dev, NULL, bs_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_bs != NULL); + bs = g_bs; + + free_clusters = spdk_bs_free_cluster_count(bs); + cluster_size = spdk_bs_get_cluster_size(bs); + pages_per_cluster = cluster_size / spdk_bs_get_page_size(bs); + pages_per_payload = pages_per_cluster * 5; + + payload_size = cluster_size * 5; + + payload_read = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_read != NULL); + + payload_write = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_write != NULL); + + payload_clone = malloc(payload_size); + SPDK_CU_ASSERT_FATAL(payload_clone != NULL); + + zero = calloc(1, payload_size); + SPDK_CU_ASSERT_FATAL(zero != NULL); + + channel = spdk_bs_alloc_io_channel(bs); + SPDK_CU_ASSERT_FATAL(channel != NULL); + + /* Create blob */ + spdk_blob_opts_init(&opts); + opts.thin_provision = true; + opts.num_clusters = 5; + + spdk_bs_create_blob_ext(bs, &opts, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + CU_ASSERT(free_clusters == spdk_bs_free_cluster_count(bs)); + blobid = g_blobid; + + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5); + + /* Initial read should return zeroed payload */ + memset(payload_read, 0xFF, payload_size); + spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(zero, payload_read, payload_size) == 0); + + /* Fill whole blob with a pattern */ + memset(payload_write, 0xE5, payload_size); + spdk_blob_io_write(blob, channel, payload_write, 0, pages_per_payload, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs)); + + /* Create snapshot from blob */ + spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID); + snapshotid = g_blobid; + + spdk_bs_open_blob(bs, snapshotid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + snapshot = g_blob; + CU_ASSERT(snapshot->data_ro == true) + CU_ASSERT(snapshot->md_ro == true) + + CU_ASSERT(spdk_blob_get_num_clusters(snapshot) == 5) + + /* Write every second cluster with a pattern. + * + * payload_clone stores expected result on "blob" read at the time and + * is used only to check data consistency on clone before and after + * inflation. Initially we fill it with a backing snapshots pattern + * used before. + */ + memset(payload_clone, 0xE5, payload_size); + memset(payload_write, 0xAA, payload_size); + for (i = 1; i < 5; i += 2) { + spdk_blob_io_write(blob, channel, payload_write, i * pages_per_cluster, + pages_per_cluster, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Update expected result */ + memcpy(payload_clone + (cluster_size * i), payload_write, + cluster_size); + } + CU_ASSERT(free_clusters != spdk_bs_free_cluster_count(bs)); + + /* Check data consistency on clone */ + memset(payload_read, 0xFF, payload_size); + spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, + blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_clone, payload_read, payload_size) == 0); + + /* Close all blobs */ + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_blob_close(snapshot, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Inflate blob */ + spdk_bs_inflate_blob(bs, channel, blobid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Try to delete snapshot (should pass) */ + spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + /* Reopen blob after snapshot deletion */ + spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL); + CU_ASSERT(g_bserrno == 0); + SPDK_CU_ASSERT_FATAL(g_blob != NULL); + blob = g_blob; + + CU_ASSERT(spdk_blob_get_num_clusters(blob) == 5); + + /* Check data consistency on inflated blob */ + memset(payload_read, 0xFF, payload_size); + spdk_blob_io_read(blob, channel, payload_read, 0, pages_per_payload, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + CU_ASSERT(memcmp(payload_clone, payload_read, payload_size) == 0); + + spdk_blob_close(blob, blob_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + + spdk_bs_free_io_channel(channel); + + /* Unload the blob store */ + spdk_bs_unload(g_bs, bs_op_complete, NULL); + CU_ASSERT(g_bserrno == 0); + g_bs = NULL; + g_blob = NULL; + g_blobid = 0; + + free(payload_read); + free(payload_write); + free(payload_clone); + free(zero); +} + /** * Snapshot-clones relation test * @@ -4054,6 +4312,7 @@ int main(int argc, char **argv) CU_add_test(suite, "blob_thin_provision", blob_thin_provision) == NULL || CU_add_test(suite, "blob_snapshot", blob_snapshot) == NULL || CU_add_test(suite, "blob_clone", blob_clone) == NULL || + CU_add_test(suite, "blob_inflate", blob_inflate) == NULL || CU_add_test(suite, "blob_delete", blob_delete) == NULL || CU_add_test(suite, "blob_resize", blob_resize) == NULL || CU_add_test(suite, "blob_read_only", blob_read_only) == NULL || @@ -4090,6 +4349,7 @@ int main(int argc, char **argv) CU_add_test(suite, "bs_load_iter", bs_load_iter) == NULL || CU_add_test(suite, "blob_snapshot_rw", blob_snapshot_rw) == NULL || CU_add_test(suite, "blob_snapshot_rw_iov", blob_snapshot_rw_iov) == NULL || + CU_add_test(suite, "blob_inflate_rw", blob_inflate_rw) == NULL || CU_add_test(suite, "blob_relations", blob_relations) == NULL ) { CU_cleanup_registry();