diff --git a/lib/bdev/lvol/vbdev_lvol.c b/lib/bdev/lvol/vbdev_lvol.c index 9793a5b1e..e15dfa836 100644 --- a/lib/bdev/lvol/vbdev_lvol.c +++ b/lib/bdev/lvol/vbdev_lvol.c @@ -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); diff --git a/lib/blob/blobstore.c b/lib/blob/blobstore.c index cb7dd96ba..99f32e27a 100644 --- a/lib/blob/blobstore.c +++ b/lib/blob/blobstore.c @@ -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 diff --git a/test/lvol/test_cases.py b/test/lvol/test_cases.py index a3f92c261..f95920043 100644 --- a/test/lvol/test_cases.py +++ b/test/lvol/test_cases.py @@ -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) diff --git a/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c b/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c index 4811d8147..ba6b3ca7b 100644 --- a/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c +++ b/test/unit/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut.c @@ -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); diff --git a/test/unit/lib/blob/blob.c/blob_ut.c b/test/unit/lib/blob/blob.c/blob_ut.c index b1599d1bb..632fed372 100644 --- a/test/unit/lib/blob/blob.c/blob_ut.c +++ b/test/unit/lib/blob/blob.c/blob_ut.c @@ -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 ||