lvol: keep track of missing external snapshots

If an lvol is opened in degraded mode, keep track of the missing esnap
IDs and which lvols need them. A future commit will make use of this
information to bring lvols out of degraded mode when their external
snapshot device appears.

Change-Id: I55c16ad042a73e46e225369bfff2631958a2ed46
Signed-off-by: Mike Gerdts <mgerdts@nvidia.com>
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/16427
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
This commit is contained in:
Mike Gerdts 2023-01-17 10:12:48 -06:00 committed by Jim Harris
parent 87666f5286
commit f2dbb50516
7 changed files with 407 additions and 6 deletions

View File

@ -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 */

View File

@ -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(&degraded_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(&degraded_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(&degraded_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(&degraded_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);
}

View File

@ -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: *;
};

View File

@ -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)

View File

@ -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);

View File

@ -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)

View File

@ -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);