Commit ede6d97e authored by Piotr Pelplinski's avatar Piotr Pelplinski Committed by Daniel Verkamp
Browse files

blobstore: add inflate call



Inflate call can be used on thin provisioned blob or clones.

Function allocates all unallocated clusters on specified blob and:
 - For clones, copies data from backing blob.
 - For thin provisioned blobs, clusters are zeroed.

After this call all dependency from specified blob is removed
what allows deletion i.e. snapshots.

Signed-off-by: default avatarPiotr Pelplinski <piotr.pelplinski@intel.com>
Signed-off-by: default avatarTomasz Kulasek <tomaszx.kulasek@intel.com>
Change-Id: Ibff569e45b12068b2fb46557156be348b36c252b
Reviewed-on: https://review.gerrithub.io/399367


Tested-by: default avatarSPDK Automated Test System <sys_sgsw@intel.com>
Reviewed-by: default avatarJim Harris <james.r.harris@intel.com>
Reviewed-by: default avatarShuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
Reviewed-by: default avatarBen Walker <benjamin.walker@intel.com>
Reviewed-by: default avatarDaniel Verkamp <daniel.verkamp@intel.com>
parent b1ccb095
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -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.
 *
+147 −0
Original line number Diff line number Diff line
@@ -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)
+260 −0
Original line number Diff line number Diff line
@@ -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();