diff --git a/include/spdk_internal/lvolstore.h b/include/spdk_internal/lvolstore.h index 6a9b9aca6..7fc7b81d6 100644 --- a/include/spdk_internal/lvolstore.h +++ b/include/spdk_internal/lvolstore.h @@ -10,6 +10,7 @@ #include "spdk/blob.h" #include "spdk/lvol.h" #include "spdk/queue.h" +#include "spdk/tree.h" #include "spdk/uuid.h" /* Default size of blobstore cluster */ @@ -38,6 +39,8 @@ struct spdk_lvol_req { spdk_lvol_op_complete cb_fn; void *cb_arg; struct spdk_lvol *lvol; + /* Only set while lvol is being deleted and has a clone. */ + struct spdk_lvol *clone_lvol; size_t sz; struct spdk_io_channel *channel; char name[SPDK_LVOL_NAME_MAX]; @@ -62,8 +65,11 @@ struct spdk_lvol_with_handle_req { spdk_lvol_op_with_handle_complete cb_fn; void *cb_arg; struct spdk_lvol *lvol; + struct spdk_lvol *origlvol; }; +struct spdk_lvs_degraded_lvol_set; + struct spdk_lvol_store { struct spdk_bs_dev *bs_dev; struct spdk_blob_store *blobstore; @@ -81,6 +87,8 @@ struct spdk_lvol_store { char name[SPDK_LVS_NAME_MAX]; char new_name[SPDK_LVS_NAME_MAX]; spdk_bs_esnap_dev_create esnap_bs_dev_create; + RB_HEAD(degraded_lvol_sets_tree, spdk_lvs_degraded_lvol_set) degraded_lvol_sets_tree; + struct spdk_thread *thread; }; struct spdk_lvol { @@ -95,7 +103,9 @@ struct spdk_lvol { int ref_count; bool action_in_progress; enum blob_clear_method clear_method; - TAILQ_ENTRY(spdk_lvol) link; + TAILQ_ENTRY(spdk_lvol) link; + struct spdk_lvs_degraded_lvol_set *degraded_set; + TAILQ_ENTRY(spdk_lvol) degraded_link; }; struct lvol_store_bdev *vbdev_lvol_store_first(void); @@ -107,4 +117,8 @@ void spdk_lvol_resize(struct spdk_lvol *lvol, uint64_t sz, spdk_lvol_op_complete void spdk_lvol_set_read_only(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn, void *cb_arg); +int spdk_lvs_esnap_missing_add(struct spdk_lvol_store *lvs, struct spdk_lvol *lvol, + const void *esnap_id, uint32_t id_len); +void spdk_lvs_esnap_missing_remove(struct spdk_lvol *lvol); + #endif /* SPDK_INTERNAL_LVOLSTORE_H */ diff --git a/lib/lvol/lvol.c b/lib/lvol/lvol.c index e119e91b7..a059698ee 100644 --- a/lib/lvol/lvol.c +++ b/lib/lvol/lvol.c @@ -9,6 +9,7 @@ #include "spdk/string.h" #include "spdk/thread.h" #include "spdk/blob_bdev.h" +#include "spdk/tree.h" #include "spdk/util.h" /* Default blob channel opts for lvol */ @@ -18,6 +19,14 @@ SPDK_LOG_REGISTER_COMPONENT(lvol) +struct spdk_lvs_degraded_lvol_set { + struct spdk_lvol_store *lvol_store; + const void *esnap_id; + uint32_t id_len; + TAILQ_HEAD(degraded_lvols, spdk_lvol) lvols; + RB_ENTRY(spdk_lvs_degraded_lvol_set) node; +}; + static TAILQ_HEAD(, spdk_lvol_store) g_lvol_stores = TAILQ_HEAD_INITIALIZER(g_lvol_stores); static pthread_mutex_t g_lvol_stores_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -25,6 +34,11 @@ static inline int lvs_opts_copy(const struct spdk_lvs_opts *src, struct spdk_lvs static int lvs_esnap_bs_dev_create(void *bs_ctx, void *blob_ctx, struct spdk_blob *blob, const void *esnap_id, uint32_t id_len, struct spdk_bs_dev **_bs_dev); +static struct spdk_lvol *lvs_get_lvol_by_blob_id(struct spdk_lvol_store *lvs, spdk_blob_id blob_id); +static void lvs_degraded_lvol_set_add(struct spdk_lvs_degraded_lvol_set *degraded_set, + struct spdk_lvol *lvol); +static void lvs_degraded_lvol_set_remove(struct spdk_lvs_degraded_lvol_set *degraded_set, + struct spdk_lvol *lvol); static int add_lvs_to_list(struct spdk_lvol_store *lvs) @@ -63,6 +77,8 @@ lvs_alloc(void) TAILQ_INIT(&lvs->retry_open_lvols); lvs->load_esnaps = false; + RB_INIT(&lvs->degraded_lvol_sets_tree); + lvs->thread = spdk_get_thread(); return lvs; } @@ -76,6 +92,8 @@ lvs_free(struct spdk_lvol_store *lvs) } pthread_mutex_unlock(&g_lvol_stores_mutex); + assert(RB_EMPTY(&lvs->degraded_lvol_sets_tree)); + free(lvs); } @@ -321,7 +339,7 @@ lvs_read_uuid(void *cb_arg, struct spdk_blob *blob, int lvolerrno) rc = spdk_blob_get_xattr_value(blob, "uuid", (const void **)&attr, &value_len); if (rc != 0 || value_len != SPDK_UUID_STRING_LEN || attr[SPDK_UUID_STRING_LEN - 1] != '\0') { - SPDK_INFOLOG(lvol, "missing or incorrect UUID\n"); + SPDK_INFOLOG(lvol, "degraded_set or incorrect UUID\n"); req->lvserrno = -EINVAL; spdk_blob_close(blob, close_super_blob_with_error_cb, req); return; @@ -336,7 +354,7 @@ lvs_read_uuid(void *cb_arg, struct spdk_blob *blob, int lvolerrno) rc = spdk_blob_get_xattr_value(blob, "name", (const void **)&attr, &value_len); if (rc != 0 || value_len > SPDK_LVS_NAME_MAX) { - SPDK_INFOLOG(lvol, "missing or invalid name\n"); + SPDK_INFOLOG(lvol, "degraded_set or invalid name\n"); req->lvserrno = -EINVAL; spdk_blob_close(blob, close_super_blob_with_error_cb, req); return; @@ -878,6 +896,7 @@ spdk_lvs_unload(struct spdk_lvol_store *lvs, spdk_lvs_op_complete cb_fn, } TAILQ_FOREACH_SAFE(lvol, &lvs->lvols, link, tmp) { + spdk_lvs_esnap_missing_remove(lvol); TAILQ_REMOVE(&lvs->lvols, lvol, link); lvol_free(lvol); } @@ -1002,6 +1021,7 @@ lvol_delete_blob_cb(void *cb_arg, int lvolerrno) { struct spdk_lvol_req *req = cb_arg; struct spdk_lvol *lvol = req->lvol; + struct spdk_lvol *clone_lvol = req->clone_lvol; if (lvolerrno < 0) { SPDK_ERRLOG("Could not remove blob on lvol gracefully - forced removal\n"); @@ -1009,6 +1029,22 @@ lvol_delete_blob_cb(void *cb_arg, int lvolerrno) SPDK_INFOLOG(lvol, "Lvol %s deleted\n", lvol->unique_id); } + if (lvol->degraded_set != NULL) { + if (clone_lvol != NULL) { + /* + * A degraded esnap clone that has a blob clone has been deleted. clone_lvol + * becomes an esnap clone and needs to be associated with the + * spdk_lvs_degraded_lvol_set. + */ + struct spdk_lvs_degraded_lvol_set *degraded_set = lvol->degraded_set; + + lvs_degraded_lvol_set_remove(degraded_set, lvol); + lvs_degraded_lvol_set_add(degraded_set, clone_lvol); + } else { + spdk_lvs_esnap_missing_remove(lvol); + } + } + TAILQ_REMOVE(&lvol->lvol_store->lvols, lvol, link); lvol_free(lvol); req->cb_fn(req->cb_arg, lvolerrno); @@ -1072,6 +1108,18 @@ lvol_create_cb(void *cb_arg, spdk_blob_id blobid, int lvolerrno) opts.esnap_ctx = req->lvol; bs = req->lvol->lvol_store->blobstore; + if (req->origlvol != NULL && req->origlvol->degraded_set != NULL) { + /* + * A snapshot was created from a degraded esnap clone. The new snapshot is now a + * degraded esnap clone. The previous clone is now a regular clone of a blob. Update + * the set of directly-related clones to the missing external snapshot. + */ + struct spdk_lvs_degraded_lvol_set *degraded_set = req->origlvol->degraded_set; + + lvs_degraded_lvol_set_remove(degraded_set, req->origlvol); + lvs_degraded_lvol_set_add(degraded_set, req->lvol); + } + spdk_bs_open_blob_ext(bs, blobid, &opts, lvol_create_open_cb, req); } @@ -1290,6 +1338,7 @@ spdk_lvol_create_snapshot(struct spdk_lvol *origlvol, const char *snapshot_name, snapshot_xattrs.names = xattr_names; snapshot_xattrs.get_value = lvol_get_xattr_value; req->lvol = newlvol; + req->origlvol = origlvol; req->cb_fn = cb_fn; req->cb_arg = cb_arg; @@ -1496,6 +1545,10 @@ spdk_lvol_destroy(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn, void *cb_ { struct spdk_lvol_req *req; struct spdk_blob_store *bs; + struct spdk_lvol_store *lvs = lvol->lvol_store; + spdk_blob_id clone_id; + size_t count = 1; + int rc; assert(cb_fn != NULL); @@ -1525,6 +1578,18 @@ spdk_lvol_destroy(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn, void *cb_ req->lvol = lvol; bs = lvol->lvol_store->blobstore; + rc = spdk_blob_get_clones(lvs->blobstore, lvol->blob_id, &clone_id, &count); + if (rc == 0 && count == 1) { + req->clone_lvol = lvs_get_lvol_by_blob_id(lvs, clone_id); + } else if (rc == -ENOMEM) { + SPDK_INFOLOG(lvol, "lvol %s: cannot destroy: has %" PRIu64 " clones\n", + lvol->unique_id, count); + free(req); + assert(count > 1); + cb_fn(cb_arg, -EBUSY); + return; + } + spdk_bs_delete_blob(bs, lvol->blob_id, lvol_delete_blob_cb, req); } @@ -1758,3 +1823,115 @@ lvs_esnap_bs_dev_create(void *bs_ctx, void *blob_ctx, struct spdk_blob *blob, return lvs->esnap_bs_dev_create(lvs, lvol, blob, esnap_id, id_len, bs_dev); } + +/* + * The theory of missing external snapshots + * + * The lvs->esnap_bs_dev_create() callback may be unable to create an external snapshot bs_dev when + * it is called. This can happen, for instance, as when the device containing the lvolstore is + * examined prior to spdk_bdev_register() being called on a bdev that acts as an external snapshot. + * In such a case, the esnap_bs_dev_create() callback will call spdk_lvs_esnap_missing_add(). + * + * Missing external snapshots are tracked in a per-lvolstore tree, lvs->degraded_lvol_sets_tree. + * Each tree node (struct spdk_lvs_degraded_lvol_set) contains a tailq of lvols that are missing + * that particular external snapshot. + */ +static int +lvs_esnap_name_cmp(struct spdk_lvs_degraded_lvol_set *m1, struct spdk_lvs_degraded_lvol_set *m2) +{ + if (m1->id_len == m2->id_len) { + return memcmp(m1->esnap_id, m2->esnap_id, m1->id_len); + } + return (m1->id_len > m2->id_len) ? 1 : -1; +} + +RB_GENERATE_STATIC(degraded_lvol_sets_tree, spdk_lvs_degraded_lvol_set, node, lvs_esnap_name_cmp) + +static void +lvs_degraded_lvol_set_add(struct spdk_lvs_degraded_lvol_set *degraded_set, struct spdk_lvol *lvol) +{ + assert(lvol->lvol_store->thread == spdk_get_thread()); + + lvol->degraded_set = degraded_set; + TAILQ_INSERT_TAIL(°raded_set->lvols, lvol, degraded_link); +} + +static void +lvs_degraded_lvol_set_remove(struct spdk_lvs_degraded_lvol_set *degraded_set, + struct spdk_lvol *lvol) +{ + assert(lvol->lvol_store->thread == spdk_get_thread()); + + lvol->degraded_set = NULL; + TAILQ_REMOVE(°raded_set->lvols, lvol, degraded_link); + /* degraded_set->lvols may be empty. Caller should check if not immediately adding a new + * lvol. */ +} + +/* + * Record in lvs->degraded_lvol_sets_tree that a bdev of the specified name is needed by the + * specified lvol. + */ +int +spdk_lvs_esnap_missing_add(struct spdk_lvol_store *lvs, struct spdk_lvol *lvol, + const void *esnap_id, uint32_t id_len) +{ + struct spdk_lvs_degraded_lvol_set find, *degraded_set; + + assert(lvs->thread == spdk_get_thread()); + + find.esnap_id = esnap_id; + find.id_len = id_len; + degraded_set = RB_FIND(degraded_lvol_sets_tree, &lvs->degraded_lvol_sets_tree, &find); + if (degraded_set == NULL) { + degraded_set = calloc(1, sizeof(*degraded_set)); + if (degraded_set == NULL) { + SPDK_ERRLOG("lvol %s: cannot create degraded_set node: out of memory\n", + lvol->unique_id); + return -ENOMEM; + } + degraded_set->esnap_id = calloc(1, id_len); + if (degraded_set->esnap_id == NULL) { + free(degraded_set); + SPDK_ERRLOG("lvol %s: cannot create degraded_set node: out of memory\n", + lvol->unique_id); + return -ENOMEM; + } + memcpy((void *)degraded_set->esnap_id, esnap_id, id_len); + degraded_set->id_len = id_len; + degraded_set->lvol_store = lvs; + TAILQ_INIT(°raded_set->lvols); + RB_INSERT(degraded_lvol_sets_tree, &lvs->degraded_lvol_sets_tree, degraded_set); + } + + lvs_degraded_lvol_set_add(degraded_set, lvol); + + return 0; +} + +/* + * Remove the record of the specified lvol needing a degraded_set bdev. + */ +void +spdk_lvs_esnap_missing_remove(struct spdk_lvol *lvol) +{ + struct spdk_lvol_store *lvs = lvol->lvol_store; + struct spdk_lvs_degraded_lvol_set *degraded_set = lvol->degraded_set; + + assert(lvs->thread == spdk_get_thread()); + + if (degraded_set == NULL) { + return; + } + + lvs_degraded_lvol_set_remove(degraded_set, lvol); + + if (!TAILQ_EMPTY(°raded_set->lvols)) { + return; + } + + RB_REMOVE(degraded_lvol_sets_tree, &lvs->degraded_lvol_sets_tree, degraded_set); + + free((char *)degraded_set->esnap_id); + free(degraded_set); +} diff --git a/lib/lvol/spdk_lvol.map b/lib/lvol/spdk_lvol.map index 1cb1de998..45c323462 100644 --- a/lib/lvol/spdk_lvol.map +++ b/lib/lvol/spdk_lvol.map @@ -26,6 +26,8 @@ # internal functions spdk_lvol_resize; spdk_lvol_set_read_only; + spdk_lvs_esnap_missing_add; + spdk_lvs_esnap_missing_remove; local: *; }; diff --git a/mk/spdk.lib_deps.mk b/mk/spdk.lib_deps.mk index 35d39d48d..32751d7a5 100644 --- a/mk/spdk.lib_deps.mk +++ b/mk/spdk.lib_deps.mk @@ -52,7 +52,7 @@ DEPDIRS-accel := log util thread json rpc jsonrpc dma DEPDIRS-jsonrpc := log util json DEPDIRS-virtio := log util json thread vfio_user -DEPDIRS-lvol := log util blob +DEPDIRS-lvol := log util blob thread DEPDIRS-rpc := log util json jsonrpc DEPDIRS-net := log util $(JSON_LIBS) diff --git a/module/bdev/lvol/vbdev_lvol.c b/module/bdev/lvol/vbdev_lvol.c index 2a31179a8..f965646ba 100644 --- a/module/bdev/lvol/vbdev_lvol.c +++ b/module/bdev/lvol/vbdev_lvol.c @@ -734,6 +734,18 @@ vbdev_lvol_dump_info_json(void *ctx, struct spdk_json_write_ctx *w) } + spdk_json_write_named_bool(w, "esnap_clone", spdk_blob_is_esnap_clone(blob)); + + if (spdk_blob_is_esnap_clone(blob)) { + const char *name; + size_t name_len; + + rc = spdk_blob_get_esnap_id(blob, (const void **)&name, &name_len); + if (rc == 0 && name != NULL && strnlen(name, name_len) + 1 == name_len) { + spdk_json_write_named_string(w, "external_snapshot_name", name); + } + } + end: spdk_json_write_object_end(w); 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 813155cdd..106d076e7 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 @@ -39,6 +39,9 @@ bool g_ext_api_called; DEFINE_STUB_V(spdk_bdev_module_fini_start_done, (void)); DEFINE_STUB(spdk_bdev_get_memory_domains, int, (struct spdk_bdev *bdev, struct spdk_memory_domain **domains, int array_size), 0); +DEFINE_STUB(spdk_blob_get_esnap_id, int, + (struct spdk_blob *blob, const void **id, size_t *len), -ENOTSUP); +DEFINE_STUB(spdk_blob_is_esnap_clone, bool, (const struct spdk_blob *blob), false); const struct spdk_bdev_aliases_list * spdk_bdev_get_aliases(const struct spdk_bdev *bdev) diff --git a/test/unit/lib/lvol/lvol.c/lvol_ut.c b/test/unit/lib/lvol/lvol.c/lvol_ut.c index 4016e09f7..d166b23bb 100644 --- a/test/unit/lib/lvol/lvol.c/lvol_ut.c +++ b/test/unit/lib/lvol/lvol.c/lvol_ut.c @@ -28,11 +28,13 @@ #define SPDK_BLOB_THIN_PROV (1ULL << 0) + DEFINE_STUB(spdk_bdev_get_name, const char *, (const struct spdk_bdev *bdev), NULL); DEFINE_STUB(spdk_bdev_get_by_name, struct spdk_bdev *, (const char *name), NULL); DEFINE_STUB(spdk_bdev_create_bs_dev_ro, int, (const char *bdev_name, spdk_bdev_event_cb_t event_cb, void *event_ctx, struct spdk_bs_dev **bs_dev), -ENOTSUP); +DEFINE_STUB(spdk_blob_is_esnap_clone, bool, (const struct spdk_blob *blob), false); const char *uuid = "828d9766-ae50-11e7-bd8d-001e67edf350"; @@ -235,8 +237,6 @@ spdk_blob_is_thin_provisioned(struct spdk_blob *blob) return blob->thin_provisioned; } -DEFINE_STUB(spdk_blob_get_clones, int, (struct spdk_blob_store *bs, spdk_blob_id blobid, - spdk_blob_id *ids, size_t *count), 0); DEFINE_STUB(spdk_bs_get_page_size, uint64_t, (struct spdk_blob_store *bs), BS_PAGE_SIZE); int @@ -509,6 +509,25 @@ spdk_blob_get_esnap_id(struct spdk_blob *blob, const void **id, size_t *len) return g_spdk_blob_get_esnap_id_errno; } +static spdk_blob_id g_spdk_blob_get_clones_snap_id = 0xbad; +static size_t g_spdk_blob_get_clones_count; +static spdk_blob_id *g_spdk_blob_get_clones_ids; +int +spdk_blob_get_clones(struct spdk_blob_store *bs, spdk_blob_id blob_id, spdk_blob_id *ids, + size_t *count) +{ + if (blob_id != g_spdk_blob_get_clones_snap_id) { + *count = 0; + return 0; + } + if (ids == NULL || *count < g_spdk_blob_get_clones_count) { + *count = g_spdk_blob_get_clones_count; + return -ENOMEM; + } + memcpy(ids, g_spdk_blob_get_clones_ids, g_spdk_blob_get_clones_count * sizeof(*ids)); + return 0; +} + static void lvol_store_op_with_handle_complete(void *cb_arg, struct spdk_lvol_store *lvol_store, int lvserrno) { @@ -543,11 +562,21 @@ op_complete(void *cb_arg, int lvserrno) static struct ut_cb_res * ut_cb_res_clear(struct ut_cb_res *res) { + memset(res, 0, sizeof(*res)); res->data = (void *)(uintptr_t)(-1); res->err = 0xbad; return res; } +static bool +ut_cb_res_untouched(const struct ut_cb_res *res) +{ + struct ut_cb_res pristine; + + ut_cb_res_clear(&pristine); + return !memcmp(&pristine, res, sizeof(pristine)); +} + static void lvs_init_unload_success(void) { @@ -2435,6 +2464,169 @@ lvol_esnap_load_esnaps(void) g_esnap_bs_dev_errno = -ENOTSUP; } +struct ut_degraded_dev { + struct spdk_bs_dev bs_dev; + struct spdk_lvol *lvol; +}; + +static void +ut_destroy_degraded(struct spdk_bs_dev *ddev) +{ + free(ddev); +} + +static int +ut_create_degraded(struct spdk_lvol_store *lvs, struct spdk_lvol *lvol, + struct spdk_blob *blob, const char *name, struct spdk_bs_dev **bs_dev) +{ + struct ut_degraded_dev *ddev; + + ddev = calloc(1, sizeof(*ddev)); + SPDK_CU_ASSERT_FATAL(ddev != NULL); + + ddev->lvol = lvol; + ddev->bs_dev.destroy = ut_destroy_degraded; + ddev->bs_dev.blockcnt = UINT64_MAX / 512; + ddev->bs_dev.blocklen = 512; + *bs_dev = &ddev->bs_dev; + return 0; +} + +static void +lvol_esnap_missing(void) +{ + struct lvol_ut_bs_dev dev; + struct spdk_lvs_opts opts; + struct spdk_blob blob = { .id = 42 }; + struct ut_cb_res cb_res; + struct spdk_lvol_store *lvs; + struct spdk_lvol *lvol1, *lvol2; + struct spdk_bs_dev *bs_dev; + struct spdk_bdev esnap_bdev; + struct spdk_lvs_degraded_lvol_set *degraded_set; + const char *name1 = "lvol1"; + const char *name2 = "lvol2"; + char uuid_str[SPDK_UUID_STRING_LEN]; + int rc; + + /* Create an lvstore */ + init_dev(&dev); + spdk_lvs_opts_init(&opts); + snprintf(opts.name, sizeof(opts.name), "lvs"); + g_lvserrno = -1; + rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL); + CU_ASSERT(rc == 0); + CU_ASSERT(g_lvserrno == 0); + SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL); + lvs = g_lvol_store; + lvs->load_esnaps = true; + + /* Pre-populate the lvstore with a degraded device */ + lvol1 = lvol_alloc(lvs, name1, true, LVOL_CLEAR_WITH_DEFAULT); + SPDK_CU_ASSERT_FATAL(lvol1 != NULL); + lvol1->blob_id = blob.id; + TAILQ_REMOVE(&lvs->pending_lvols, lvol1, link); + TAILQ_INSERT_TAIL(&lvs->lvols, lvol1, link); + rc = ut_create_degraded(lvs, lvol1, &blob, name1, &bs_dev); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(bs_dev != NULL); + + /* A clone with a missing external snapshot prevents a conflicting clone's creation */ + init_bdev(&esnap_bdev, "bdev1", BS_CLUSTER_SIZE); + CU_ASSERT(spdk_uuid_fmt_lower(uuid_str, sizeof(uuid_str), &esnap_bdev.uuid) == 0); + MOCK_SET(spdk_bdev_get_by_name, &esnap_bdev); + rc = spdk_lvol_create_esnap_clone(uuid_str, sizeof(uuid_str), 1, g_lvol_store, name1, + lvol_op_with_handle_complete, ut_cb_res_clear(&cb_res)); + CU_ASSERT(rc == -EEXIST); + CU_ASSERT(ut_cb_res_untouched(&cb_res)); + MOCK_CLEAR(spdk_bdev_get_by_name); + + /* A clone with a missing external snapshot prevents a conflicting lvol's creation */ + rc = spdk_lvol_create(lvs, name1, 10, false, LVOL_CLEAR_WITH_DEFAULT, + lvol_op_with_handle_complete, ut_cb_res_clear(&cb_res)); + CU_ASSERT(rc == -EEXIST); + CU_ASSERT(ut_cb_res_untouched(&cb_res)); + + /* Using a unique lvol name allows the clone to be created. */ + MOCK_SET(spdk_bdev_get_by_name, &esnap_bdev); + MOCK_SET(spdk_blob_is_esnap_clone, true); + rc = spdk_lvol_create_esnap_clone(uuid_str, sizeof(uuid_str), 1, g_lvol_store, name2, + lvol_op_with_handle_complete, ut_cb_res_clear(&cb_res)); + SPDK_CU_ASSERT_FATAL(rc == 0); + CU_ASSERT(cb_res.err == 0); + SPDK_CU_ASSERT_FATAL(cb_res.data != NULL); + lvol2 = cb_res.data; + CU_ASSERT(lvol2->degraded_set == NULL); + spdk_lvol_close(lvol2, op_complete, ut_cb_res_clear(&cb_res)); + CU_ASSERT(cb_res.err == 0); + spdk_lvol_destroy(lvol2, op_complete, ut_cb_res_clear(&cb_res)); + CU_ASSERT(cb_res.err == 0); + MOCK_CLEAR(spdk_blob_is_esnap_clone); + MOCK_CLEAR(spdk_bdev_get_by_name); + + /* Destroying the esnap clone removes it from the degraded_set esnaps tree. */ + spdk_lvol_destroy(lvol1, op_complete, ut_cb_res_clear(&cb_res)); + CU_ASSERT(cb_res.err == 0); + CU_ASSERT(RB_EMPTY(&lvs->degraded_lvol_sets_tree)); + bs_dev->destroy(bs_dev); + + /* Create a missing device again */ + lvol1 = lvol_alloc(lvs, name1, true, LVOL_CLEAR_WITH_DEFAULT); + SPDK_CU_ASSERT_FATAL(lvol1 != NULL); + lvol1->blob_id = blob.id; + TAILQ_REMOVE(&lvs->pending_lvols, lvol1, link); + TAILQ_INSERT_TAIL(&lvs->lvols, lvol1, link); + rc = ut_create_degraded(lvs, lvol1, &blob, name1, &bs_dev); + CU_ASSERT(rc == 0); + SPDK_CU_ASSERT_FATAL(bs_dev != NULL); + lvol1->blob = &blob; + rc = spdk_lvs_esnap_missing_add(lvs, lvol1, esnap_bdev.name, strlen(esnap_bdev.name) + 1); + CU_ASSERT(rc == 0); + lvol1->ref_count = 1; + + /* + * Creating a snapshot of lvol1 makes lvol1 a clone of the new snapshot. What was a clone of + * the external snapshot is now a clone of the snapshot. The snapshot is a clone of the + * external snapshot. Now the snapshot is degraded_set its external snapshot. + */ + degraded_set = lvol1->degraded_set; + CU_ASSERT(degraded_set != NULL); + spdk_lvol_create_snapshot(lvol1, name2, lvol_op_with_handle_complete, + ut_cb_res_clear(&cb_res)); + CU_ASSERT(cb_res.err == 0); + SPDK_CU_ASSERT_FATAL(cb_res.data != NULL); + lvol2 = cb_res.data; + CU_ASSERT(lvol1->degraded_set == NULL); + CU_ASSERT(lvol2->degraded_set == degraded_set); + + /* + * Removing the snapshot (lvol2) makes the first lvol (lvol1) back into a clone of an + * external snapshot. + */ + MOCK_SET(spdk_blob_is_esnap_clone, true); + g_spdk_blob_get_clones_snap_id = lvol2->blob_id; + g_spdk_blob_get_clones_ids = &lvol1->blob_id; + g_spdk_blob_get_clones_count = 1; + spdk_lvol_close(lvol2, op_complete, ut_cb_res_clear(&cb_res)); + CU_ASSERT(cb_res.err == 0); + spdk_lvol_destroy(lvol2, op_complete, ut_cb_res_clear(&cb_res)); + CU_ASSERT(cb_res.err == 0); + CU_ASSERT(lvol1->degraded_set == degraded_set); + g_spdk_blob_get_clones_snap_id = 0xbad; + g_spdk_blob_get_clones_ids = NULL; + g_spdk_blob_get_clones_count = 0; + + /* Clean up */ + spdk_lvol_close(lvol1, op_complete, ut_cb_res_clear(&cb_res)); + CU_ASSERT(cb_res.err == 0); + spdk_lvol_destroy(lvol1, op_complete, ut_cb_res_clear(&cb_res)); + CU_ASSERT(cb_res.err == 0); + bs_dev->destroy(bs_dev); + rc = spdk_lvs_destroy(g_lvol_store, op_complete, NULL); + CU_ASSERT(rc == 0); + MOCK_CLEAR(spdk_blob_is_esnap_clone); +} + int main(int argc, char **argv) { @@ -2477,6 +2669,7 @@ main(int argc, char **argv) CU_ADD_TEST(suite, lvol_esnap_create_bad_args); CU_ADD_TEST(suite, lvol_esnap_create_delete); CU_ADD_TEST(suite, lvol_esnap_load_esnaps); + CU_ADD_TEST(suite, lvol_esnap_missing); allocate_threads(1); set_thread(0);