blobstore: Make possible to remove snapshot if there is only one clone

Starting with this patch it is possible to remove a
snapshot if there is only a one clone created from it.

In such case snapshot can be removed without any data
copying. This is achieved with following steps (case
with only one clone):
1. Open snapshot (Snapshot1) that shall be removed
2. Check if the Snapshot1 has no more than 1 clone (Clone1)
3. Remove Clone1 entry from Snapshot1
4. If the Snapshot1 has a parent snapshot (Snapshot2):
 4a. Add Clone1 entry to the Snapshot2 clones list
 4b. Remove Snapshot1 entry from Snapshot2 clones list
5. Open Clone1 blob
6. Freeze I/O operations on Clone1
7. Temporarily override md_ro flag for Snapshot1 and Clone1
   for MD modification
8. Merge Snapshot1 and Clone1 clusters maps into Clone1
   clusters map
9a. If Snapshot2 is present switch parent ID and backing
    bs_dev on Clone1
9b. If Snapshot2 is not present set parent ID to
    SPDK_BLOBID_INVALID and backing bs_dev to zeroes_dev
10. Sync MD on Clone1
11. Sync MD on Snapshot1
12. Restore MD flags for Clone1 and Snapshot1
13. Unfreeze I/O on Clone1
14. Close Clone1 blob
15. Remove Snapshot1

Signed-off-by: Maciej Szwed <maciej.szwed@intel.com>
Change-Id: I800724b981af894e01e1912d0077c5b34a2ae634
Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/445576
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Tomasz Zawadzki <tomasz.zawadzki@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Reviewed-by: Darek Stojaczyk <dariusz.stojaczyk@intel.com>
This commit is contained in:
Maciej Szwed 2019-05-21 09:19:03 +02:00 committed by Ben Walker
parent 48cec0b127
commit 622127d7e1
5 changed files with 790 additions and 73 deletions

View File

@ -579,12 +579,14 @@ void
vbdev_lvol_destroy(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn, void *cb_arg)
{
struct vbdev_lvol_destroy_ctx *ctx;
size_t count;
assert(lvol != NULL);
assert(cb_fn != NULL);
/* Check if it is possible to delete lvol */
if (spdk_lvol_deletable(lvol) == false) {
spdk_blob_get_clones(lvol->lvol_store->blobstore, lvol->blob_id, NULL, &count);
if (count > 1) {
/* throw an error */
SPDK_ERRLOG("Cannot delete lvol\n");
cb_fn(cb_arg, -EPERM);

View File

@ -5138,14 +5138,6 @@ spdk_blob_resize(struct spdk_blob *blob, uint64_t sz, spdk_blob_op_complete cb_f
/* START spdk_bs_delete_blob */
static void
_spdk_bs_delete_ebusy_close_cpl(void *cb_arg, int bserrno)
{
spdk_bs_sequence_t *seq = cb_arg;
spdk_bs_sequence_finish(seq, -EBUSY);
}
static void
_spdk_bs_delete_close_cpl(void *cb_arg, int bserrno)
{
@ -5180,6 +5172,301 @@ _spdk_bs_delete_persist_cpl(spdk_bs_sequence_t *seq, void *cb_arg, int bserrno)
spdk_blob_close(blob, _spdk_bs_delete_close_cpl, seq);
}
struct delete_snapshot_ctx {
struct spdk_blob_list *parent_snapshot_entry;
struct spdk_blob *snapshot;
bool snapshot_md_ro;
struct spdk_blob *clone;
bool clone_md_ro;
spdk_blob_op_with_handle_complete cb_fn;
void *cb_arg;
int bserrno;
};
static void
_spdk_delete_blob_cleanup_finish(void *cb_arg, int bserrno)
{
struct delete_snapshot_ctx *ctx = cb_arg;
if (bserrno != 0) {
SPDK_ERRLOG("Snapshot cleanup error %d\n", bserrno);
}
assert(ctx != NULL);
if (bserrno != 0 && ctx->bserrno == 0) {
ctx->bserrno = bserrno;
}
ctx->cb_fn(ctx->cb_arg, ctx->snapshot, ctx->bserrno);
free(ctx);
}
static void
_spdk_delete_snapshot_cleanup_snapshot(void *cb_arg, int bserrno)
{
struct delete_snapshot_ctx *ctx = cb_arg;
if (bserrno != 0) {
ctx->bserrno = bserrno;
SPDK_ERRLOG("Clone cleanup error %d\n", bserrno);
}
/* open_ref == 1 menas that only deletion context has opened this snapshot
* open_ref == 2 menas that clone has opened this snapshot as well,
* so we have to add it back to the blobs list */
if (ctx->snapshot->open_ref == 2) {
TAILQ_INSERT_HEAD(&ctx->snapshot->bs->blobs, ctx->snapshot, link);
}
ctx->snapshot->locked_operation_in_progress = false;
ctx->snapshot->md_ro = ctx->snapshot_md_ro;
spdk_blob_close(ctx->snapshot, _spdk_delete_blob_cleanup_finish, ctx);
}
static void
_spdk_delete_snapshot_cleanup_clone(void *cb_arg, int bserrno)
{
struct delete_snapshot_ctx *ctx = cb_arg;
ctx->clone->locked_operation_in_progress = false;
ctx->clone->md_ro = ctx->clone_md_ro;
spdk_blob_close(ctx->clone, _spdk_delete_snapshot_cleanup_snapshot, ctx);
}
static void
_spdk_delete_snapshot_unfreeze_cpl(void *cb_arg, int bserrno)
{
struct delete_snapshot_ctx *ctx = cb_arg;
if (bserrno) {
ctx->bserrno = bserrno;
_spdk_delete_snapshot_cleanup_clone(ctx, 0);
return;
}
ctx->clone->locked_operation_in_progress = false;
spdk_blob_close(ctx->clone, _spdk_delete_blob_cleanup_finish, ctx);
}
static void
_spdk_delete_snapshot_sync_snapshot_cpl(void *cb_arg, int bserrno)
{
struct delete_snapshot_ctx *ctx = cb_arg;
struct spdk_blob_list *parent_snapshot_entry = NULL;
struct spdk_blob_list *snapshot_entry = NULL;
struct spdk_blob_list *clone_entry = NULL;
struct spdk_blob_list *snapshot_clone_entry = NULL;
if (bserrno) {
SPDK_ERRLOG("Failed to sync MD on blob\n");
ctx->bserrno = bserrno;
_spdk_delete_snapshot_cleanup_clone(ctx, 0);
return;
}
/* Get snapshot entry for the snapshot we want to remove */
snapshot_entry = _spdk_bs_get_snapshot_entry(ctx->snapshot->bs, ctx->snapshot->id);
assert(snapshot_entry != NULL);
/* Remove clone entry in this snapshot (at this point there can be only one clone) */
clone_entry = TAILQ_FIRST(&snapshot_entry->clones);
assert(clone_entry != NULL);
TAILQ_REMOVE(&snapshot_entry->clones, clone_entry, link);
snapshot_entry->clone_count--;
assert(TAILQ_EMPTY(&snapshot_entry->clones));
if (ctx->snapshot->parent_id != SPDK_BLOBID_INVALID) {
/* This snapshot is at the same time a clone of another snapshot - we need to
* update parent snapshot (remove current clone, add new one inherited from
* the snapshot that is being removed) */
/* Get snapshot entry for parent snapshot and clone entry within that snapshot for
* snapshot that we are removing */
_spdk_blob_get_snapshot_and_clone_entries(ctx->snapshot, &parent_snapshot_entry,
&snapshot_clone_entry);
/* Switch clone entry in parent snapshot */
TAILQ_INSERT_TAIL(&parent_snapshot_entry->clones, clone_entry, link);
TAILQ_REMOVE(&parent_snapshot_entry->clones, snapshot_clone_entry, link);
free(snapshot_clone_entry);
} else {
/* No parent snapshot - just remove clone entry */
free(clone_entry);
}
/* Restore md_ro flags */
ctx->clone->md_ro = ctx->clone_md_ro;
ctx->snapshot->md_ro = ctx->snapshot_md_ro;
_spdk_blob_unfreeze_io(ctx->clone, _spdk_delete_snapshot_unfreeze_cpl, ctx);
}
static void
_spdk_delete_snapshot_sync_clone_cpl(void *cb_arg, int bserrno)
{
struct delete_snapshot_ctx *ctx = cb_arg;
uint64_t i;
ctx->snapshot->md_ro = false;
if (bserrno) {
SPDK_ERRLOG("Failed to sync MD on clone\n");
ctx->bserrno = bserrno;
/* Restore snapshot to previous state */
bserrno = _spdk_blob_remove_xattr(ctx->snapshot, SNAPSHOT_PENDING_REMOVAL, true);
if (bserrno != 0) {
_spdk_delete_snapshot_cleanup_clone(ctx, bserrno);
return;
}
spdk_blob_sync_md(ctx->snapshot, _spdk_delete_snapshot_cleanup_clone, ctx);
return;
}
/* Clear cluster map entries for snapshot */
for (i = 0; i < ctx->snapshot->active.num_clusters && i < ctx->clone->active.num_clusters; i++) {
if (ctx->clone->active.clusters[i] == ctx->snapshot->active.clusters[i]) {
ctx->snapshot->active.clusters[i] = 0;
}
}
ctx->snapshot->state = SPDK_BLOB_STATE_DIRTY;
if (ctx->parent_snapshot_entry != NULL) {
ctx->snapshot->back_bs_dev = NULL;
}
spdk_blob_sync_md(ctx->snapshot, _spdk_delete_snapshot_sync_snapshot_cpl, ctx);
}
static void
_spdk_delete_snapshot_sync_snapshot_xattr_cpl(void *cb_arg, int bserrno)
{
struct delete_snapshot_ctx *ctx = cb_arg;
uint64_t i;
/* Temporarily override md_ro flag for clone for MD modification */
ctx->clone_md_ro = ctx->clone->md_ro;
ctx->clone->md_ro = false;
if (bserrno) {
SPDK_ERRLOG("Failed to sync MD with xattr on blob\n");
ctx->bserrno = bserrno;
_spdk_delete_snapshot_cleanup_clone(ctx, 0);
return;
}
/* Copy snapshot map to clone map (only unallocated clusters in clone) */
for (i = 0; i < ctx->snapshot->active.num_clusters && i < ctx->clone->active.num_clusters; i++) {
if (ctx->clone->active.clusters[i] == 0) {
ctx->clone->active.clusters[i] = ctx->snapshot->active.clusters[i];
}
}
/* Delete old backing bs_dev from clone (related to snapshot that will be removed) */
ctx->clone->back_bs_dev->destroy(ctx->clone->back_bs_dev);
/* Set/remove snapshot xattr and switch parent ID and backing bs_dev on clone... */
if (ctx->parent_snapshot_entry != NULL) {
/* ...to parent snapshot */
ctx->clone->parent_id = ctx->parent_snapshot_entry->id;
ctx->clone->back_bs_dev = ctx->snapshot->back_bs_dev;
_spdk_blob_set_xattr(ctx->clone, BLOB_SNAPSHOT, &ctx->parent_snapshot_entry->id,
sizeof(spdk_blob_id),
true);
} else {
/* ...to blobid invalid and zeroes dev */
ctx->clone->parent_id = SPDK_BLOBID_INVALID;
ctx->clone->back_bs_dev = spdk_bs_create_zeroes_dev();
_spdk_blob_remove_xattr(ctx->clone, BLOB_SNAPSHOT, true);
}
spdk_blob_sync_md(ctx->clone, _spdk_delete_snapshot_sync_clone_cpl, ctx);
}
static void
_spdk_delete_snapshot_freeze_io_cb(void *cb_arg, int bserrno)
{
struct delete_snapshot_ctx *ctx = cb_arg;
if (bserrno) {
SPDK_ERRLOG("Failed to freeze I/O on clone\n");
ctx->bserrno = bserrno;
_spdk_delete_snapshot_cleanup_clone(ctx, 0);
return;
}
/* Temporarily override md_ro flag for snapshot for MD modification */
ctx->snapshot_md_ro = ctx->snapshot->md_ro;
ctx->snapshot->md_ro = false;
/* Mark blob as pending for removal for power failure safety, use clone id for recovery */
ctx->bserrno = _spdk_blob_set_xattr(ctx->snapshot, SNAPSHOT_PENDING_REMOVAL, &ctx->clone->id,
sizeof(spdk_blob_id), true);
if (ctx->bserrno != 0) {
_spdk_delete_snapshot_cleanup_clone(ctx, 0);
return;
}
spdk_blob_sync_md(ctx->snapshot, _spdk_delete_snapshot_sync_snapshot_xattr_cpl, ctx);
}
static void
_spdk_delete_snapshot_open_clone_cb(void *cb_arg, struct spdk_blob *clone, int bserrno)
{
struct delete_snapshot_ctx *ctx = cb_arg;
if (bserrno) {
SPDK_ERRLOG("Failed to open clone\n");
ctx->bserrno = bserrno;
_spdk_delete_snapshot_cleanup_snapshot(ctx, 0);
return;
}
ctx->clone = clone;
if (clone->locked_operation_in_progress) {
SPDK_DEBUGLOG(SPDK_LOG_BLOB, "Cannot remove blob - another operation in progress on its clone\n");
ctx->bserrno = -EBUSY;
spdk_blob_close(ctx->clone, _spdk_delete_snapshot_cleanup_snapshot, ctx);
return;
}
clone->locked_operation_in_progress = true;
_spdk_blob_freeze_io(clone, _spdk_delete_snapshot_freeze_io_cb, ctx);
}
static void
_spdk_update_clone_on_snapshot_deletion(struct spdk_blob *snapshot, struct delete_snapshot_ctx *ctx)
{
struct spdk_blob_list *snapshot_entry = NULL;
struct spdk_blob_list *clone_entry = NULL;
struct spdk_blob_list *snapshot_clone_entry = NULL;
/* Get snapshot entry for the snapshot we want to remove */
snapshot_entry = _spdk_bs_get_snapshot_entry(snapshot->bs, snapshot->id);
assert(snapshot_entry != NULL);
/* Get clone of the snapshot (at this point there can be only one clone) */
clone_entry = TAILQ_FIRST(&snapshot_entry->clones);
assert(snapshot_entry->clone_count == 1);
assert(clone_entry != NULL);
/* Get snapshot entry for parent snapshot and clone entry within that snapshot for
* snapshot that we are removing */
_spdk_blob_get_snapshot_and_clone_entries(snapshot, &ctx->parent_snapshot_entry,
&snapshot_clone_entry);
spdk_bs_open_blob(snapshot->bs, clone_entry->id, _spdk_delete_snapshot_open_clone_cb, ctx);
}
static void
_spdk_bs_delete_blob_finish(void *cb_arg, struct spdk_blob *blob, int bserrno)
{
@ -5210,32 +5497,67 @@ _spdk_bs_delete_blob_finish(void *cb_arg, struct spdk_blob *blob, int bserrno)
}
static int
_spdk_bs_is_blob_deletable(struct spdk_blob *blob)
_spdk_bs_is_blob_deletable(struct spdk_blob *blob, bool *update_clone)
{
struct spdk_blob_list *snapshot_entry = NULL;
if (blob->open_ref > 1) {
/* Someone has this blob open (besides this delete context). */
return -EBUSY;
}
struct spdk_blob_list *clone_entry = NULL;
struct spdk_blob *clone = NULL;
bool has_one_clone = false;
/* Check if this is a snapshot with clones */
snapshot_entry = _spdk_bs_get_snapshot_entry(blob->bs, blob->id);
if (snapshot_entry != NULL) {
/* If snapshot have clones, we cannot remove it */
if (!TAILQ_EMPTY(&snapshot_entry->clones)) {
SPDK_ERRLOG("Cannot remove snapshot with clones\n");
if (snapshot_entry->clone_count > 1) {
SPDK_ERRLOG("Cannot remove snapshot with more than one clone\n");
return -EBUSY;
} else if (snapshot_entry->clone_count == 1) {
has_one_clone = true;
}
}
/* Check if someone has this blob open (besides this delete context):
* - open_ref = 1 - only this context opened blob, so it is ok to remove it
* - open_ref <= 2 && has_one_clone = true - clone is holding snapshot
* and that is ok, because we will update it accordingly */
if (blob->open_ref <= 2 && has_one_clone) {
clone_entry = TAILQ_FIRST(&snapshot_entry->clones);
assert(clone_entry != NULL);
clone = _spdk_blob_lookup(blob->bs, clone_entry->id);
if (blob->open_ref == 2 && clone == NULL) {
/* Clone is closed and someone else opened this blob */
SPDK_ERRLOG("Cannot remove snapshot because it is open\n");
return -EBUSY;
}
*update_clone = true;
return 0;
}
if (blob->open_ref > 1) {
SPDK_ERRLOG("Cannot remove snapshot because it is open\n");
return -EBUSY;
}
assert(has_one_clone == false);
*update_clone = false;
return 0;
}
static void
_spdk_bs_delete_enomem_close_cpl(void *cb_arg, int bserrno)
{
spdk_bs_sequence_t *seq = cb_arg;
spdk_bs_sequence_finish(seq, -ENOMEM);
}
static void
_spdk_bs_delete_open_cpl(void *cb_arg, struct spdk_blob *blob, int bserrno)
{
spdk_bs_sequence_t *seq = cb_arg;
struct delete_snapshot_ctx *ctx;
bool update_clone = false;
if (bserrno != 0) {
spdk_bs_sequence_finish(seq, bserrno);
@ -5244,17 +5566,27 @@ _spdk_bs_delete_open_cpl(void *cb_arg, struct spdk_blob *blob, int bserrno)
_spdk_blob_verify_md_op(blob);
bserrno = _spdk_bs_is_blob_deletable(blob);
if (bserrno) {
spdk_blob_close(blob, _spdk_bs_delete_ebusy_close_cpl, seq);
ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL) {
spdk_blob_close(blob, _spdk_bs_delete_enomem_close_cpl, seq);
return;
}
_spdk_bs_blob_list_remove(blob);
ctx->snapshot = blob;
ctx->cb_fn = _spdk_bs_delete_blob_finish;
ctx->cb_arg = seq;
/* Check if blob can be removed and if it is a snapshot with clone on top of it */
ctx->bserrno = _spdk_bs_is_blob_deletable(blob, &update_clone);
if (ctx->bserrno) {
spdk_blob_close(blob, _spdk_delete_blob_cleanup_finish, ctx);
return;
}
if (blob->locked_operation_in_progress) {
SPDK_DEBUGLOG(SPDK_LOG_BLOB, "Cannot remove blob - another operation in progress\n");
spdk_blob_close(blob, _spdk_bs_delete_ebusy_close_cpl, seq);
ctx->bserrno = -EBUSY;
spdk_blob_close(blob, _spdk_delete_blob_cleanup_finish, ctx);
return;
}
@ -5266,7 +5598,15 @@ _spdk_bs_delete_open_cpl(void *cb_arg, struct spdk_blob *blob, int bserrno)
*/
TAILQ_REMOVE(&blob->bs->blobs, blob, link);
_spdk_bs_delete_blob_finish(seq, blob, 0);
if (update_clone) {
/* This blob is a snapshot with active clone - update clone first */
_spdk_update_clone_on_snapshot_deletion(blob, ctx);
} else {
/* This blob does not have any clones - just remove it */
_spdk_bs_blob_list_remove(blob);
_spdk_bs_delete_blob_finish(seq, blob, 0);
free(ctx);
}
}
void

View File

@ -1300,12 +1300,8 @@ class TestCases(object):
fail_count += self.c.snapshot_lvol_bdev(clone_bdev['name'], snapshot_name2)
snapshot_bdev2 = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name2)
# Try to destroy snapshots with clones and check if it fails
# Try to destroy snapshot with 2 clones and check if it fails
ret_value = self.c.destroy_lvol_bdev(snapshot_bdev['name'])
if ret_value == 0:
print("ERROR: Delete snapshot should fail but didn't")
fail_count += 1
ret_value = self.c.destroy_lvol_bdev(snapshot_bdev2['name'])
if ret_value == 0:
print("ERROR: Delete snapshot should fail but didn't")
fail_count += 1
@ -2707,12 +2703,6 @@ class TestCases(object):
fail_count += self.c.snapshot_lvol_bdev(lvol_bdev['name'], snapshot_name)
snapshot_bdev = self.c.get_lvol_bdev_with_name(self.lvs_name + "/" + snapshot_name)
# Try to destroy snapshot and check if it fails
ret_value = self.c.destroy_lvol_bdev(snapshot_bdev['name'])
if ret_value == 0:
print("ERROR: Delete snapshot should fail but didn't")
fail_count += 1
# Decouple parent lvol bdev
fail_count += self.c.decouple_parent_lvol_bdev(lvol_bdev['name'])
lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0)
@ -2810,12 +2800,6 @@ class TestCases(object):
fail_count += self.run_fio_test(nbd_name, begin_fill * MEGABYTE,
fill_range * MEGABYTE, "read", pattern[i])
# Delete snapshot and check if it fails
ret_value = self.c.destroy_lvol_bdev(snapshot_bdev2['name'])
if ret_value == 0:
print("ERROR: Delete snapshot should fail but didn't")
fail_count += 1
# Decouple parent
fail_count += self.c.decouple_parent_lvol_bdev(lvol_bdev['name'])
lvol_bdev = self.c.get_lvol_bdev_with_name(uuid_bdev0)

View File

@ -61,7 +61,6 @@ bool lvol_already_opened = false;
bool g_examine_done = false;
bool g_bdev_alias_already_exists = false;
bool g_lvs_with_name_already_exists = false;
bool g_lvol_deletable = true;
int
spdk_bdev_alias_add(struct spdk_bdev *bdev, const char *alias)
@ -446,7 +445,7 @@ spdk_lvol_close(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn, void *cb_ar
bool
spdk_lvol_deletable(struct spdk_lvol *lvol)
{
return g_lvol_deletable;
return true;
}
void
@ -1034,13 +1033,6 @@ ut_lvol_destroy(void)
CU_ASSERT(g_lvolerrno == 0);
lvol2 = g_lvol;
/* Unsuccessful lvols destroy */
g_lvol_deletable = false;
vbdev_lvol_destroy(lvol, lvol_store_op_complete, NULL);
CU_ASSERT(g_lvol != NULL);
CU_ASSERT(g_lvserrno == -EPERM);
g_lvol_deletable = true;
/* Successful lvols destroy */
vbdev_lvol_destroy(lvol, lvol_store_op_complete, NULL);
CU_ASSERT(g_lvol == NULL);

View File

@ -5432,12 +5432,10 @@ _blob_inflate_rw(bool decouple_parent)
poll_threads();
CU_ASSERT(g_bserrno == 0);
/* Try to delete base snapshot (for decouple_parent should fail while
* dependency still exists) */
/* Try to delete base snapshot */
spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(decouple_parent || g_bserrno == 0);
CU_ASSERT(!decouple_parent || g_bserrno != 0);
CU_ASSERT(g_bserrno == 0);
/* Reopen blob after snapshot deletion */
spdk_bs_open_blob(bs, blobid, blob_op_with_handle_complete, NULL);
@ -5716,15 +5714,11 @@ blob_relations(void)
poll_threads();
CU_ASSERT(g_bserrno == 0);
/* Try to delete snapshot with created clones */
/* Try to delete snapshot with more than 1 clone */
spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno != 0);
spdk_bs_delete_blob(bs, snapshotid2, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno != 0);
spdk_bs_unload(bs, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
@ -5796,12 +5790,428 @@ blob_relations(void)
CU_ASSERT(rc == 0);
CU_ASSERT(count == 0);
/* Try to delete all blobs in the worse possible order */
/* Try to delete blob that user should not be able to remove */
spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno != 0);
/* Remove all blobs */
spdk_bs_delete_blob(bs, cloneid, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, snapshotid2, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, cloneid2, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_unload(bs, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
g_bs = NULL;
}
/**
* Snapshot-clones relation test 2
*
* snapshot1
* |
* snapshot2
* |
* +-----+-----+
* | |
* blob(ro) snapshot3
* | |
* | snapshot4
* | | |
* clone2 clone clone3
*/
static void
blob_relations2(void)
{
struct spdk_blob_store *bs;
struct spdk_bs_dev *dev;
struct spdk_bs_opts bs_opts;
struct spdk_blob_opts opts;
struct spdk_blob *blob, *snapshot1, *snapshot2, *snapshot3, *snapshot4, *clone, *clone2;
spdk_blob_id blobid, snapshotid1, snapshotid2, snapshotid3, snapshotid4, cloneid, cloneid2,
cloneid3;
int rc;
size_t count;
spdk_blob_id ids[10] = {};
dev = init_dev();
spdk_bs_opts_init(&bs_opts);
snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "TESTTYPE");
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);
bs = g_bs;
/* 1. 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);
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 == 0);
SPDK_CU_ASSERT_FATAL(g_blob != NULL);
blob = g_blob;
/* 2. Create snapshot1 */
spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
snapshotid1 = g_blobid;
spdk_bs_open_blob(bs, snapshotid1, blob_op_with_handle_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
SPDK_CU_ASSERT_FATAL(g_blob != NULL);
snapshot1 = g_blob;
CU_ASSERT(snapshot1->parent_id == SPDK_BLOBID_INVALID);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid1) == SPDK_BLOBID_INVALID);
CU_ASSERT(blob->parent_id == snapshotid1);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshotid1);
/* Check if blob is the clone of snapshot1 */
CU_ASSERT(blob->parent_id == snapshotid1);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshotid1);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, snapshotid1, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 1);
CU_ASSERT(ids[0] == blobid);
/* 3. Create another snapshot */
spdk_bs_create_snapshot(bs, blobid, NULL, blob_op_with_id_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
snapshotid2 = g_blobid;
spdk_bs_open_blob(bs, snapshotid2, blob_op_with_handle_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
SPDK_CU_ASSERT_FATAL(g_blob != NULL);
snapshot2 = g_blob;
CU_ASSERT(spdk_blob_is_clone(snapshot2));
CU_ASSERT(snapshot2->parent_id == snapshotid1);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid2) == snapshotid1);
/* Check if snapshot2 is the clone of snapshot1 and blob
* is a child of snapshot2 */
CU_ASSERT(blob->parent_id == snapshotid2);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshotid2);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, snapshotid2, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 1);
CU_ASSERT(ids[0] == blobid);
/* 4. Create clone from snapshot */
spdk_bs_create_clone(bs, snapshotid2, NULL, blob_op_with_id_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
cloneid = g_blobid;
spdk_bs_open_blob(bs, cloneid, blob_op_with_handle_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
SPDK_CU_ASSERT_FATAL(g_blob != NULL);
clone = g_blob;
CU_ASSERT(clone->parent_id == snapshotid2);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid) == snapshotid2);
/* Check if clone is on the snapshot's list */
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, snapshotid2, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 2);
CU_ASSERT(ids[0] == blobid || ids[1] == blobid);
CU_ASSERT(ids[0] == cloneid || ids[1] == cloneid);
/* 5. Create snapshot of the clone */
spdk_bs_create_snapshot(bs, cloneid, NULL, blob_op_with_id_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
snapshotid3 = g_blobid;
spdk_bs_open_blob(bs, snapshotid3, blob_op_with_handle_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
SPDK_CU_ASSERT_FATAL(g_blob != NULL);
snapshot3 = g_blob;
CU_ASSERT(snapshot3->parent_id == snapshotid2);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid3) == snapshotid2);
/* Check if clone is converted to the clone of snapshot3 and snapshot3
* is a child of snapshot2 */
CU_ASSERT(clone->parent_id == snapshotid3);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid) == snapshotid3);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, snapshotid3, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 1);
CU_ASSERT(ids[0] == cloneid);
/* 6. Create another snapshot of the clone */
spdk_bs_create_snapshot(bs, cloneid, NULL, blob_op_with_id_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
snapshotid4 = g_blobid;
spdk_bs_open_blob(bs, snapshotid4, blob_op_with_handle_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
SPDK_CU_ASSERT_FATAL(g_blob != NULL);
snapshot4 = g_blob;
CU_ASSERT(snapshot4->parent_id == snapshotid3);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid4) == snapshotid3);
/* Check if clone is converted to the clone of snapshot4 and snapshot4
* is a child of snapshot3 */
CU_ASSERT(clone->parent_id == snapshotid4);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid) == snapshotid4);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, snapshotid4, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 1);
CU_ASSERT(ids[0] == cloneid);
/* 7. Remove snapshot 4 */
spdk_blob_close(snapshot4, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, snapshotid4, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
/* Check if relations are back to state from before creating snapshot 4 */
CU_ASSERT(clone->parent_id == snapshotid3);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid) == snapshotid3);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, snapshotid3, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 1);
CU_ASSERT(ids[0] == cloneid);
/* 8. Create second clone of snapshot 3 and try to remove snapshot 3 */
spdk_bs_create_clone(bs, snapshotid3, NULL, blob_op_with_id_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
cloneid3 = g_blobid;
spdk_bs_delete_blob(bs, snapshotid3, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno != 0);
/* 9. Open snapshot 3 again and try to remove it while clone 3 is closed */
spdk_bs_open_blob(bs, snapshotid3, blob_op_with_handle_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
SPDK_CU_ASSERT_FATAL(g_blob != NULL);
snapshot3 = g_blob;
spdk_bs_delete_blob(bs, snapshotid3, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno != 0);
spdk_blob_close(snapshot3, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, cloneid3, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
/* 10. Remove snapshot 1 */
spdk_blob_close(snapshot1, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, snapshotid1, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
/* Check if relations are back to state from before creating snapshot 4 (before step 6) */
CU_ASSERT(snapshot2->parent_id == SPDK_BLOBID_INVALID);
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid2) == SPDK_BLOBID_INVALID);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, snapshotid2, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 2);
CU_ASSERT(ids[0] == blobid || ids[1] == blobid);
CU_ASSERT(ids[0] == snapshotid3 || ids[1] == snapshotid3);
/* 11. Try to create clone from read only blob */
/* Mark blob as read only */
spdk_blob_set_read_only(blob);
spdk_blob_sync_md(blob, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
/* Create clone from read only blob */
spdk_bs_create_clone(bs, blobid, NULL, blob_op_with_id_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
CU_ASSERT(g_blobid != SPDK_BLOBID_INVALID);
cloneid2 = g_blobid;
spdk_bs_open_blob(bs, cloneid2, blob_op_with_handle_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
SPDK_CU_ASSERT_FATAL(g_blob != NULL);
clone2 = g_blob;
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid2) == blobid);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, blobid, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 1);
CU_ASSERT(ids[0] == cloneid2);
/* Close blobs */
spdk_blob_close(clone2, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_blob_close(blob, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_blob_close(clone, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_blob_close(snapshot2, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_blob_close(snapshot3, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_unload(bs, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
g_bs = NULL;
/* Load an existing blob store */
dev = init_dev();
snprintf(bs_opts.bstype.bstype, sizeof(bs_opts.bstype.bstype), "TESTTYPE");
spdk_bs_load(dev, NULL, bs_op_with_handle_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
SPDK_CU_ASSERT_FATAL(g_bs != NULL);
bs = g_bs;
/* Verify structure of loaded blob store */
/* snapshot2 */
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid2) == SPDK_BLOBID_INVALID);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, snapshotid2, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 2);
CU_ASSERT(ids[0] == blobid || ids[1] == blobid);
CU_ASSERT(ids[0] == snapshotid3 || ids[1] == snapshotid3);
/* blob */
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, blobid) == snapshotid2);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, blobid, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 1);
CU_ASSERT(ids[0] == cloneid2);
/* clone */
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid) == snapshotid3);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, cloneid, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 0);
/* snapshot3 */
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, snapshotid3) == snapshotid2);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, snapshotid3, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 1);
CU_ASSERT(ids[0] == cloneid);
/* clone2 */
CU_ASSERT(spdk_blob_get_parent_snapshot(bs, cloneid2) == blobid);
count = SPDK_COUNTOF(ids);
rc = spdk_blob_get_clones(bs, cloneid2, ids, &count);
CU_ASSERT(rc == 0);
CU_ASSERT(count == 0);
/* Try to delete all blobs in the worse possible order */
spdk_bs_delete_blob(bs, snapshotid2, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno != 0);
spdk_bs_delete_blob(bs, snapshotid3, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, snapshotid2, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno != 0);
@ -5814,26 +6224,14 @@ blob_relations(void)
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno != 0);
spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno != 0);
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, cloneid2, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, blobid, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_delete_blob(bs, snapshotid, blob_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
spdk_bs_unload(bs, bs_op_complete, NULL);
poll_threads();
CU_ASSERT(g_bserrno == 0);
@ -6800,6 +7198,7 @@ int main(int argc, char **argv)
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_relations", blob_relations) == NULL ||
CU_add_test(suite, "blob_relations2", blob_relations2) == NULL ||
CU_add_test(suite, "blob_inflate_rw", blob_inflate_rw) == NULL ||
CU_add_test(suite, "blob_snapshot_freeze_io", blob_snapshot_freeze_io) == NULL ||
CU_add_test(suite, "blob_operation_split_rw", blob_operation_split_rw) == NULL ||